Author Archives: yogesh.mali@gmail.com

Liquibase – Handling Database in Spring Boot

If you are building an application with Spring Boot, handling the database changes becomes a nightmare over time. The more changes you add, the more changes you have to maintain for your database. Liquibase is the best solution out there. In this post, we will show how to handle database changes using liquibase.

What is Liquibase?

Liquibase is an open-source library to track, manage, and apply database changes. Liquibase tracks the changes to database through an XML configuration where a developer will usually add changesets.

Each changeset will have an id and author attributes. Liquibase uses a changelog to track the database changes. Every changeset you add, that will get added in the changelog. Changelog is a ledger of all the changes you are doing to database.

How does Liquibase work?

To track database changes, you will write an XML file that is platform-independent. This XML file will be used on the command line to translate into scripts for your database engine.

We can also use a maven or Gradle plugin to include database changes in the build configuration.

Liquibase uses its own tables to track changes. Those tables will be part of the schema you are building for consistency purposes. It records the hash of each changeset.

How to write a changeset?

Previously, I mentioned you can write a changeset using XML. But liquibase also offers the support for JSON or YAML.

As part of this post, I will show how I add a changeset and generate scripts for the database.

Create an XML changelog file db.changelog-master.xml for our database under folder src\main\resources\db. Usually, if you start using liquibase from the start of the project, you will create an initial changelog file that will generate initial scripts. You can track every change after that through a change set.

The file without any changeset will look like below:


<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
    http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
</databaseChangeLog>

Now I can handle this master file in two ways. For each changeset, I can create a separate file and include that file in the master file OR I can add every changeset in the same master file.

Each changeset needs an author and unique id.

Now we will add changeset to this changelog file and it will look like below:


<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd
    http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">

    <changeSet author="Yogesh Mali" id="jira-ticket-01">
        <createTable tableName="user">
            <column name="id" type="int">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="guid" type="varchar(50)">
            </column>
            <column name="firstname" type="varchar(100)">
                <constraints nullable="false"/>
            </column>
            <column name="middlename" type="varchar(100)"></column>
            <column name="lastname" type="varchar(100)"></column>
            <column name="email" type="varchar(100)">
                <constraints nullable="false"/>
            </column>
            <column name="companyid" type="int"></column>
            <column name="roleid" type="int"></column>
        </createTable>
        <createTable tableName="company">
            <column name="id" type="int">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="guid" type="varchar(50)">
                <constraints nullable="false"/>
            </column>
            <column name="name" type="varchar(50)">
                <constraints nullable="false"/>
            </column>
            <column name="type" type="varchar(10)"></column>
        </createTable>
        <createTable tableName="role">
            <column name="id" type="int">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="role_name" type="varchar(20)">
                <constraints nullable="false"/>
            </column>
        </createTable>
        <addForeignKeyConstraint baseTableName="user" baseColumnNames="companyid"
                                  constraintName="company_fk" referencedTableName="company"
                                  referencedColumnNames="id" />
        <addForeignKeyConstraint baseTableName="user" baseColumnNames="roleid"
                                  constraintName="role_fk" referencedTableName="role"
                                  referencedColumnNames="id"/>
    </changeSet>
</databaseChangeLog>

Now we are ready to create liquibase Bean in our Spring Boot project. We will have to add the following property in our application.properties file.

spring.liquibase.changeLog=classpath:/db/db.changelog-master.xml.

Also, don’t forget to add database properties in application.properties file.


spring.datasource.url=jdbc:mysql://127.0.0.1/demo
spring.datasource.username = sa
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.liquibase.changeLog=classpath:/db/db.changelog-master.xml

Before we run our Spring Boot Project, add liquibase dependency in our gradle project.

compile('org.liquibase:liquibase-core:4.0.0').

Now if we run our Spring Boot project, we will see the database tables created in the log messages as follows:


2020-07-26 12:22:24.362  INFO 32412 --- [           main] liquibase.lockservice                    : Successfully acquired change log lock
2020-07-26 12:22:25.314  INFO 32412 --- [           main] liquibase.changelog                      : Creating database history table with name: blogdemo.DATABASECHANGELOG
2020-07-26 12:22:25.345  INFO 32412 --- [           main] liquibase.changelog                      : Reading from blogdemo.DATABASECHANGELOG
2020-07-26 12:22:25.427  INFO 32412 --- [           main] liquibase.changelog                      : Table user created
2020-07-26 12:22:25.443  INFO 32412 --- [           main] liquibase.changelog                      : Table company created
2020-07-26 12:22:25.458  INFO 32412 --- [           main] liquibase.changelog                      : Table role created
2020-07-26 12:22:25.520  INFO 32412 --- [           main] liquibase.changelog                      : Foreign key constraint added to user (companyid)
2020-07-26 12:22:25.588  INFO 32412 --- [           main] liquibase.changelog                      : Foreign key constraint added to user (roleid)
2020-07-26 12:22:25.588  INFO 32412 --- [           main] liquibase.changelog                      : ChangeSet db/db.changelog-master.xml::jira-ticket-01::Yogesh Mali ran successfully in 186ms
2020-07-26 12:22:25.600  INFO 32412 --- [           main] liquibase.lockservice                    : Successfully released change log lock


As part of this execution, liquibase also created the tables databasechangelog and databasechangeloglock. Liquibase uses these tables to track the changes for the database. If you add another changeset in the changelog file, liquibase will identify that changeset based on previous changes and will perform appropriate action next time you run the application.

Conclusion

In this post, I showed how to use liquibase to handle database changes in a Spring Boot project.

One thing, I didn’t discuss in this post is another database migration tool Flyway. Flyway is also an open-source database migration tool.

If you enjoyed this post, subscribe to my blog here.

References

How to Create an Application with Spring Boot and ReactJS

In this post, I will show how we can create an application with Spring Boot and ReactJS.

We will use ReactJS for frontend and Spring Boot for handling business logic in the backend. We will use MySQL as a database. The application we are building is a to-do list application.

We will cover the following instructions in this post:

  • Set up Spring Boot application
  • Set up MySQL Database
  • Details of Spring Boot Application
  • Install ReactJS and set up frontend
  • Test the application on the local environment

Set up Spring Boot Application

I usually use https://start.spring.io/ to set up a boilerplate Spring Boot application with required dependencies. As part of this project, we will use spring-data-jpa, spring-data-rest, jdbc, and web dependencies. Our Gradle file will look like below:


plugins {
	id 'org.springframework.boot' version '2.3.1.RELEASE'
	id 'io.spring.dependency-management' version '1.0.9.RELEASE'
	id 'java'
}

group = 'com.betterjavacode.'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-data-rest'
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'	
	implementation 'org.springframework.boot:spring-boot-starter-web'
	runtimeOnly 'mysql:mysql-connector-java'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

Once we created the project at https://start.spring.io, we can download it and import it in IntelliJ or Eclipse.

Set up MySQL Database

For the To-Do list application, we will need a database and a single table. This demo is a barebone application, so we won’t have any login or registration screen.

Since we will be connecting to the database from our Spring Boot application, we will need to set up our application.properties as follows:


spring.datasource.url=jdbc:mysql://127.0.0.1/todolist?autoReconnect=true&useSSL=false
spring.datasource.username = sa
spring.datasource.password=********
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.datasource.hikari.connection-test-query=SELECT 1

You can use GUI to connect to your MySQL database, I usually prefer the command-line approach.

Once you login to your MySQL database on the command line, create a database for our application.

create database todolist

We will create a database table task.

create table task (id int(6) unsigned not null auto_increment, taskname varchar(100) not null, duedate date default null, status int(1), primary key(id));

We can either have some data inserted or have our home page of the application showing the form to insert the data.

Details of Spring Boot Application

Firstly, we will create a model class for task. This class will look like below:


package com.betterjavacode.demo.models;

import com.fasterxml.jackson.annotation.JsonFormat;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Entity(name="Task")
@Table(name="task")
public class Task implements Serializable
{
    private static final long serialVersionUID = 1L;

    public Task()
    {

    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="id", nullable = false)
    private int id;

    @Column(name = "taskname", nullable=false)
    private String taskname;

    @Column(name = "duedate")
    @JsonFormat(pattern="yyyy-MM-dd")
    private Date dueDate;

    @Column(name = "status")
    private String status;

    @Override
    public String toString()
    {
        return "Task = { id = " + id + ", taskname = " + taskname + ", duedate = " + dueDate
                + ", status = " + status + "}";
    }

    public int getId ()
    {
        return id;
    }

    public void setId (int id)
    {
        this.id = id;
    }

    public String getTaskname ()
    {
        return taskname;
    }

    public void setTaskname (String taskname)
    {
        this.taskname = taskname;
    }

    public Date getDueDate ()
    {
        return dueDate;
    }

    public void setDueDate (Date dueDate)
    {
        this.dueDate = dueDate;
    }

    public String getStatus ()
    {
        return status;
    }

    public void setStatus (String status)
    {
        this.status = status;
    }
}

In this development, we will have a Controller that will handle our rest service and a manager that will handle business layer logic.

The implementation of the manager will look like below:


package com.betterjavacode.demo.managers;

import com.betterjavacode.demo.models.Task;
import com.betterjavacode.demo.repositories.TaskRepository;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.Optional;

public class TaskManagerImpl implements TaskManager
{

    @Autowired
    private TaskRepository taskRepository;

    @Override
    public Task createTask (Task task)
    {
        Task t = taskRepository.save(task);
        return t;
    }

    @Override
    public Task updateTask (Task task)
    {
        Task t = taskRepository.save(task);
        return t;
    }

    @Override
    public Task getTask (int id)
    {
        Optional task = taskRepository.findById(id);
        return task.get();
    }

    @Override
    public List getAllTasks ()
    {
        List tasks = (List) taskRepository.findAll();
        return tasks;
    }

    @Override
    public void deleteTask (int id)
    {
        taskRepository.deleteById(id);
    }
}


And our RestController will look like below:



package com.betterjavacode.demo.controllers;


import com.betterjavacode.demo.managers.TaskManager;
import com.betterjavacode.demo.models.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:8080")
public class TaskController
{
    @Autowired
    private TaskManager taskManager;

    @GetMapping("/tasks")
    public List get()
    {
        return taskManager.getAllTasks();
    }

    @PostMapping("/task")
    public Task save(@RequestBody Task task)
    {
        return taskManager.createTask(task);
    }

    @GetMapping("/task/{id}")
    public Task get(@PathVariable int id)
    {
        return taskManager.getTask(id);
    }

    @DeleteMapping("/task/{id}")
    public String delete(@PathVariable int id)
    {
        taskManager.deleteTask(id);
        return "Task with id " + id + " removed";
    }

}


Now we can test these REST APIs through POSTMAN. I will not be showing that, but if you are interested you can download postman.

Install ReactJS and Set up Frontend

For frontend UI, we will be using ReactJS. Few things, you will need to use ReactJS are npm, nodejs, and create-react-app.

Once you install these three components, we are ready to use ReactJS for our current Spring Boot application.

On command-line, go to the root directory where we have the Spring Boot application. Create a react app using the following command:

npx create-react-app frontend

If you go into frontend directory on command-line, you will be able to start the frontend and it will show you the default screen of ReactJS application.

To make sure the application works smoothly, we will need couple of libraries. We will install them as follows:

npm install react-router
npm install @material-ui/core
npm install @material-ui/icons

react-router is a standard library from React that keeps your UI in sync with the URL, provides dynamic route matching, location transitioning, and lazy code handling features.

core and icons are libraries for frontend components.

We will add a proxy in package.json so that we don’t have to write an entire URL when we make call to backend API.

proxy: http://localhost:8080

Now let’s look at component files.

  1. App.jsIn this file, we will define our app route using react router. AddTask will serve as the homepage. We will redirect a user to view page to see a list of tasks.

import React, {Component} from 'react';
import AddTask from "./Component/AddTask";
import { Route,BrowserRouter as Router} from 'react-router-dom';
import Table from "./Component/Table"

class App extends Component{
	render(){
		return(
			
				
				
			
			);
	}
}

export default App;

2. AddTask.js

This will be the entry of our application. In this file, we will have a form to add a task. Once the user enters all the required information, we will send a POST request to server-side code. This file will look like below:


import React from "react";
import Avatar from "@material-ui/core/Avatar";
import Button from "@material-ui/core/Button";
import CssBaseline from "@material-ui/core/CssBaseline";
import TextField from "@material-ui/core/TextField";
import { Link } from "react-router-dom";
import Grid from "@material-ui/core/Grid";
import GroupIcon from "@material-ui/icons/Group";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/core/styles";
import Container from "@material-ui/core/Container";

const useStyles = makeStyles(theme => ({
  paper: {
    marginTop: theme.spacing(7),
    display: "flex",
    flexDirection: "column",
    alignItems: "center"
  },
  avatar: {
    margin: theme.spacing(1),
    backgroundColor: theme.palette.secondary.main
  },
  form: {
    width: "100%", // Fix IE 11 issue.
    marginTop: theme.spacing(3)
  },
  submit: {
    margin: theme.spacing(3, 0, 2)
  },
  textField: {
    marginLeft: theme.spacing(1),
    marginRight: theme.spacing(1),
    width: "100%"
  }
}));

export default function AddTask(){
    const classes = useStyles();
    const[firstLoad, setLoad] = React.useState(true);

    const [selectedDate, setSelectedDate] = React.useState(
        new Date()
    );

    const [taskname, setTaskname] = React.useState("Write Code");
    const [status, setStatus] = React.useState("Active");

    const handleDateChange = event => setSelectedDate(event.target.value);
    const handleTasknameChange = event => setTaskname(event.target.value);
    const handleStatusChange = event => setStatus(event.target.value);

    const [message, setMessage] = React.useState("Nothing saved in the session");

    async function sampleFunc(toInput){
    	const response = await fetch("/api/task",{
			method : "POST",
			cache : "no-cache",
			credentials : "include",
			headers : {
				"Content-Type":"application/json"
			},
			redirect : "follow",
			referrerPolicy: "no-referrer", // no-referrer, *client
	      	body: JSON.stringify(toInput)

    	});
    	let body = await response.json();
    	//console.log(body.id);
    	setMessage(body.id ? "Data successfully updated" : "Data updation failed");
    }

    const handleSubmit = variables => {
    	const toInput = {taskname,status, dueDate:selectedDate};
    	sampleFunc(toInput);
    	setTaskname("");
    	setStatus("");
    };

    if(firstLoad){
    	setLoad(false);
    }

    return(
    	<Container component="main" maxWidth="xs">
    	   <CssBaseline />
    	   <div className={classes.paper}>
    	   	<Avatar className={classes.paper}>
    	   	   <GroupIcon />
    	   	</Avatar>
    	   	<Typography component="h1" variant="h5">
    	   	  Tasks
    	   	</Typography>
    	   	<form className={classes.form} noValidate>
    	   	   <Grid container spacing={2}>
    	   	    <Grid item xs={12}>
    	   	      <TextField
    	   	         variant = "outlined"
    	   	         required
			 	     fullWidth
			         id="taskname"
			         value={taskname.text}
			         label="Taskname"
			         name="taskname"
			         autoComplete="taskname"
			         onChange={handleTasknameChange}
              		/>
		   </Grid>
		   <Grid item xs={12} sm={6}>
		                 <TextField
		                   autoComplete="status"
		                   name="status"
		                   variant="outlined"
		                   required
		                   fullWidth
		                   value={status.text}
		                   id="status"
		                   label="Status"
		                   onChange={handleStatusChange}
		                 />
                   </Grid>
                   <Grid item xs={12}>
		                 <TextField
		                   id="date"
		                   label="Due Date"
		                   type="date"
		                   defaultValue={""}
		                   className={classes.textField}
		                   InputLabelProps={{
		                     shrink: true
		                   }}
		                   onChange={handleDateChange}
		                 />
                  </Grid>
                 </Grid>
                 <Button
		             // type="submit"
		             fullWidth
		             variant="contained"
		             color="primary"
		             preventDefault
		             className={classes.submit}
		             onClick={handleSubmit}
		           >
		             Save
                 </Button>
                 <Grid container justify="center">
		            <Grid item>
		               <Link to="/view">View Tasks</Link>
		            </Grid>
		 </Grid>
              </form>
    	      <Typography style={{ margin: 7 }} variant="body1">
	              Status: {message}
	      </Typography>
	    </div>
       </Container>    );
}

3. Table.js

This file we get the list of tasks from our database table through an API call to Spring Boot REST API. It will also show all the entries in table form.



import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Paper from "@material-ui/core/Paper";
import Avatar from "@material-ui/core/Avatar";
import GroupIcon from "@material-ui/icons/Group";
import { Link } from "react-router-dom";
import Typography from "@material-ui/core/Typography";
import CircularProgress from "@material-ui/core/CircularProgress";

const useStyles = makeStyles(theme => ({
  table: {
    minWidth: 600
  },
  avatar: {
    margin: theme.spacing(1),
    backgroundColor: theme.palette.secondary.main
  },
  paper: {
    display: "flex",
    flexDirection: "column",
    justifyContent: "center",
    alignItems: "center",
    margin: `10px`,
    height: "100%",
    width: "99%",
    marginTop: theme.spacing(7)
  },
  link: {
    color: "rgba(0,0,0,0.65)",
    textDecoration: "none",
    marginLeft: "10%",
    alignSelf: "flex-start",
    "&:hover": {
      color: "rgba(0,0,0,1)"
    }
  }
}));

export default function SimpleTable() {
  const classes = useStyles();

  const [data, upDateData] = React.useState([]);
  const [firstLoad, setLoad] = React.useState(true);
  let isLoading = true;

  async function sampleFunc() {
    let response = await fetch("/api/tasks");
    let body = await response.json();
    upDateData(body);
  }

  if (firstLoad) {
    sampleFunc();
    setLoad(false);
  }

  if (data.length > 0) isLoading = false;

  return (
    <div className={classes.paper}>
      <Avatar className={classes.avatar}>
        <GroupIcon />
      </Avatar>
      <Typography component="h1" variant="h5">
        All Tasks
      </Typography>

      {isLoading ? (
        <CircularProgress />
      ) : (
        <TableContainer
          style={{ width: "80%", margin: "0 10px" }}
          component={Paper}
        >
          <Table className={classes.table} aria-label="simple table">
            <TableHead>
              <TableRow>
              	<TableCell align="center">No.</TableCell>
                <TableCell align="center">Taskname</TableCell>
                <TableCell align="center">Status</TableCell>
                <TableCell align="center">Due Date</TableCell>
              </TableRow>
            </TableHead>
            <TableBody>
              {data?.map(row => (
                <TableRow key={row.taskname}>
                  <TableCell align="center">{row.id}</TableCell>
                  <TableCell align="center">{row.taskname}</TableCell>
                  <TableCell align="center">{row.status}</TableCell>
                  <TableCell align="center">{row.dueDate}</TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        </TableContainer>
      )}
      <Link className={classes.link} to="/">
        {" "}
        <Typography align="left">
          &#x2190; Head back to save data
        </Typography>{" "}
      </Link>
    </div>
  );
}


 

With that change, we complete most of our code changes on the frontend and backend side.

Test the application in the local environment

Now to run the application, start Spring Boot application either on command-line or in your editor

To start the frontend, use the following command from the frontend folder:

npm start

This will start the localhost at 3000 port as below:

Create an application with Spring Boot and ReactJS

Now if I click on View Tasks, it will take me to list of tasks as follows:

Conclusion

In this post, we showed how to create an application with Spring Boot and ReactJS.  You can follow me on twitter if you have any questions. The code for this is available on Github repository. If you are looking for how to create a similar application with AngularJS, you can visit my post here.

References

  1. ReactJS – ReactJS Home
  2. Spring boot and ReactJS – Spring Boot and ReactJS

 

 

Details of Spring Boot AutoConfiguration

What is the Spring Boot? How does Spring Boot Autoconfiguration work? This post will dive into the details of Spring Boot Autoconfiguration.

What is the Spring Boot?

Spring Boot website says “We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.

Spring Boot is a framework to build applications.

Spring Boot provides different features and if you are using it to build your application, you will need different beans. So, autoconfiguration automatically configures the Spring Boot application by creating these beans.

Why you use autoconfiguration?

Efficiency and time. With autoconfiguration, Spring will do a lot of lifting for developers and will save time from creating the beans.

Behind the scenes, it is basically a bunch of @Configuration classes. These classes don’t use the annotation @Configuration .

Some of the annotations these classes use are:

  • @ConditionalOnClass – The application uses this only if the given class is on the classpath.
  • @Conditional – Only if a condition is met
  • @ConditionalOnMissingBean – The application uses this if a bean is missing or not created.

In short, @Conditional annotation is the base of all annotations.

How do you really understand this?

You or your team are working on multiple projects and these projects share some common code.  If you want to extract this common code in its own library or shared beans, so all projects can use them.


@Configuration
public class SharedObjects
{
   @Bean
   public CommonObject commonObject()
   {
      return new CommonObject();
   }
}

Once this CommonObject is shared through a jar file, other projects can import it.

The drawback with this approach is if the other project wants to use CommonObject, but don’t want to use any other beans from that common code. Importing those beans would be unnecessary overhead in the project during startup. Therefore, you need a way to tell Spring that we only need CommonObject Bean and not other beans, don’t even create other beans. That’s when we can use @Conditional annotation.

To use this @Conditional annotation, there are a few ways. Spring Boot provides Condition interface that a class can implement.


public class IsBrowserOnCondition implements Condition
{
   @Override
   public boolean matches(ConditionContext context, AnotatedTypeMetadata metadata)
   {
      return isMozillaFirefoxEnabled(context);
   }
   
   public boolean isMozillaFirefoxEnabled(ConditionContext context)
   {
      return context.getEnvironment().containsProperty("spring.preferredbrowser");
   }
}

In this class IsBrowserOnCondition , we see the implementation of interface Condition.

  • This implementation includes the method matches .
  • This method calls another method to check if the Mozilla Firefox browser has been enabled.
  • In the process, it checks for a property spring.preferredbrowser condition.
  • Now if we want to create new beans on the condition, we will use the annotation @Conditional as @Conditional(IsBrowserOnCondition.class).

In short, Spring Boot is a shared context configuration with a number of beans created using annotation @Conditional.

AutoConfiguration with Spring Boot

To understand more about autoconfiguration, we will use a simple Spring Boot application. We want to know what happens when we start this application.

So the main class of this application will look like below:


package com.betterjavacode.abccompany

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

@SpringBootApplication
@EnableJdbcHttpSession
public class HomeApplication extends SpringBootServletInitializer
{
	public static void main(String[] args)
	{
		SpringApplication.run(HomeApplication.class, args);
	}
}

When I run this main class, Spring Boot starts up the tomcat webserver.



.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

2020-06-20 16:38:20.317  INFO 19632 --- [           main] c.rentersfeedback.home.HomeApplication   : Starting HomeApplication on YMALI2019 with PID 19632 (C:\projects\rentersfeedback\out\production\classes started by Yogesh Mali in C:\projects\rentersfeedback)
2020-06-20 16:38:20.320  INFO 19632 --- [           main] c.rentersfeedback.home.HomeApplication   : No active profile set, falling back to default profiles: default
2020-06-20 16:38:21.483  INFO 19632 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-06-20 16:38:21.589  INFO 19632 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 96ms. Found 5 repository interfaces.
2020-06-20 16:38:22.052  INFO 19632 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$fdb646fa] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-06-20 16:38:22.674  INFO 19632 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8443 (https)
2020-06-20 16:38:22.700  INFO 19632 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-06-20 16:38:22.700  INFO 19632 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.21]
2020-06-20 16:38:22.906  INFO 19632 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext


Behind the scene, Spring Boot is doing some work when starting the application on Tomcat. There are different 17 sources of properties that Spring Boot is using here. The official documentation Spring Boot provides the detail of these 17 sources. A developer can externalize these properties and many times we do that for application.properties. So if you have any of these properties configured, Spring Boot will read those properties instead of default sources.

Now if we expand the jar file spring-boot-autoconfigure-2.1.6.RELEASE.jar , you will see the number of directories under package org.springframework.boot.autoconfigure. All these sub packages are the beans that Spring Boot is pulling up, but only using them based on @Conditional annotation. Therefore, during startup Spring Boot will load some of these packages based on the dependencies you have configured in your Maven or Gradle build file.

From this jar, if we open the source file for ThymeleafAutoConfiguration, we will see the following:


/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.thymeleaf;

import java.util.Collection;
import java.util.LinkedHashMap;

import javax.annotation.PostConstruct;
import javax.servlet.DispatcherType;

import com.github.mxab.thymeleaf.extras.dataattribute.dialect.DataAttributeDialect;
import nz.net.ultraq.thymeleaf.LayoutDialect;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.template.TemplateLocation;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties.Reactive;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ConditionalOnMissingFilterBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.util.MimeType;
import org.springframework.util.unit.DataSize;
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for Thymeleaf.
 *
 * @author Dave Syer
 * @author Andy Wilkinson
 * @author Stephane Nicoll
 * @author Brian Clozel
 * @author Eddú Meléndez
 * @author Daniel Fernández
 * @author Kazuki Shimizu
 * @author Artsiom Yudovin
 */
@Configuration
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {

	@Configuration
	@ConditionalOnMissingBean(name = "defaultTemplateResolver")
	static class DefaultTemplateResolverConfiguration {

		private static final Log logger = LogFactory.getLog(DefaultTemplateResolverConfiguration.class);

		private final ThymeleafProperties properties;

		private final ApplicationContext applicationContext;

		DefaultTemplateResolverConfiguration(ThymeleafProperties properties, ApplicationContext applicationContext) {
			this.properties = properties;
			this.applicationContext = applicationContext;
		}

		@PostConstruct
		public void checkTemplateLocationExists() {
			boolean checkTemplateLocation = this.properties.isCheckTemplateLocation();
			if (checkTemplateLocation) {
				TemplateLocation location = new TemplateLocation(this.properties.getPrefix());
				if (!location.exists(this.applicationContext)) {
					logger.warn("Cannot find template location: " + location + " (please add some templates or check "
							+ "your Thymeleaf configuration)");
				}
			}
		}

		@Bean
		public SpringResourceTemplateResolver defaultTemplateResolver() {
			SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
			resolver.setApplicationContext(this.applicationContext);
			resolver.setPrefix(this.properties.getPrefix());
			resolver.setSuffix(this.properties.getSuffix());
			resolver.setTemplateMode(this.properties.getMode());
			if (this.properties.getEncoding() != null) {
				resolver.setCharacterEncoding(this.properties.getEncoding().name());
			}
			resolver.setCacheable(this.properties.isCache());
			Integer order = this.properties.getTemplateResolverOrder();
			if (order != null) {
				resolver.setOrder(order);
			}
			resolver.setCheckExistence(this.properties.isCheckTemplate());
			return resolver;
		}

	}

	@Configuration
	protected static class ThymeleafDefaultConfiguration {

		private final ThymeleafProperties properties;

		private final Collection templateResolvers;

		private final ObjectProvider dialects;

		public ThymeleafDefaultConfiguration(ThymeleafProperties properties,
				Collection templateResolvers, ObjectProvider dialectsProvider) {
			this.properties = properties;
			this.templateResolvers = templateResolvers;
			this.dialects = dialectsProvider;
		}

		@Bean
		@ConditionalOnMissingBean
		public SpringTemplateEngine templateEngine() {
			SpringTemplateEngine engine = new SpringTemplateEngine();
			engine.setEnableSpringELCompiler(this.properties.isEnableSpringElCompiler());
			engine.setRenderHiddenMarkersBeforeCheckboxes(this.properties.isRenderHiddenMarkersBeforeCheckboxes());
			this.templateResolvers.forEach(engine::addTemplateResolver);
			this.dialects.orderedStream().forEach(engine::addDialect);
			return engine;
		}

	}

	@Configuration
	@ConditionalOnWebApplication(type = Type.SERVLET)
	@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
	static class ThymeleafWebMvcConfiguration {

		@Bean
		@ConditionalOnEnabledResourceChain
		@ConditionalOnMissingFilterBean(ResourceUrlEncodingFilter.class)
		public FilterRegistrationBean resourceUrlEncodingFilter() {
			FilterRegistrationBean registration = new FilterRegistrationBean<>(
					new ResourceUrlEncodingFilter());
			registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
			return registration;
		}

		@Configuration
		static class ThymeleafViewResolverConfiguration {

			private final ThymeleafProperties properties;

			private final SpringTemplateEngine templateEngine;

			ThymeleafViewResolverConfiguration(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
				this.properties = properties;
				this.templateEngine = templateEngine;
			}

			@Bean
			@ConditionalOnMissingBean(name = "thymeleafViewResolver")
			public ThymeleafViewResolver thymeleafViewResolver() {
				ThymeleafViewResolver resolver = new ThymeleafViewResolver();
				resolver.setTemplateEngine(this.templateEngine);
				resolver.setCharacterEncoding(this.properties.getEncoding().name());
				resolver.setContentType(
						appendCharset(this.properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
				resolver.setProducePartialOutputWhileProcessing(
						this.properties.getServlet().isProducePartialOutputWhileProcessing());
				resolver.setExcludedViewNames(this.properties.getExcludedViewNames());
				resolver.setViewNames(this.properties.getViewNames());
				// This resolver acts as a fallback resolver (e.g. like a
				// InternalResourceViewResolver) so it needs to have low precedence
				resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
				resolver.setCache(this.properties.isCache());
				return resolver;
			}

			private String appendCharset(MimeType type, String charset) {
				if (type.getCharset() != null) {
					return type.toString();
				}
				LinkedHashMap parameters = new LinkedHashMap<>();
				parameters.put("charset", charset);
				parameters.putAll(type.getParameters());
				return new MimeType(type, parameters).toString();
			}

		}

	}

	@Configuration
	@ConditionalOnWebApplication(type = Type.REACTIVE)
	@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
	static class ThymeleafReactiveConfiguration {

		private final ThymeleafProperties properties;

		private final Collection templateResolvers;

		private final ObjectProvider dialects;

		ThymeleafReactiveConfiguration(ThymeleafProperties properties, Collection templateResolvers,
				ObjectProvider dialectsProvider) {
			this.properties = properties;
			this.templateResolvers = templateResolvers;
			this.dialects = dialectsProvider;
		}

		@Bean
		@ConditionalOnMissingBean(ISpringWebFluxTemplateEngine.class)
		public SpringWebFluxTemplateEngine templateEngine() {
			SpringWebFluxTemplateEngine engine = new SpringWebFluxTemplateEngine();
			engine.setEnableSpringELCompiler(this.properties.isEnableSpringElCompiler());
			engine.setRenderHiddenMarkersBeforeCheckboxes(this.properties.isRenderHiddenMarkersBeforeCheckboxes());
			this.templateResolvers.forEach(engine::addTemplateResolver);
			this.dialects.orderedStream().forEach(engine::addDialect);
			return engine;
		}

	}

	@Configuration
	@ConditionalOnWebApplication(type = Type.REACTIVE)
	@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
	static class ThymeleafWebFluxConfiguration {

		private final ThymeleafProperties properties;

		ThymeleafWebFluxConfiguration(ThymeleafProperties properties) {
			this.properties = properties;
		}

		@Bean
		@ConditionalOnMissingBean(name = "thymeleafReactiveViewResolver")
		public ThymeleafReactiveViewResolver thymeleafViewResolver(ISpringWebFluxTemplateEngine templateEngine) {
			ThymeleafReactiveViewResolver resolver = new ThymeleafReactiveViewResolver();
			resolver.setTemplateEngine(templateEngine);
			mapProperties(this.properties, resolver);
			mapReactiveProperties(this.properties.getReactive(), resolver);
			// This resolver acts as a fallback resolver (e.g. like a
			// InternalResourceViewResolver) so it needs to have low precedence
			resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
			return resolver;
		}

		private void mapProperties(ThymeleafProperties properties, ThymeleafReactiveViewResolver resolver) {
			PropertyMapper map = PropertyMapper.get();
			map.from(properties::getEncoding).to(resolver::setDefaultCharset);
			resolver.setExcludedViewNames(properties.getExcludedViewNames());
			resolver.setViewNames(properties.getViewNames());
		}

		private void mapReactiveProperties(Reactive properties, ThymeleafReactiveViewResolver resolver) {
			PropertyMapper map = PropertyMapper.get();
			map.from(properties::getMediaTypes).whenNonNull().to(resolver::setSupportedMediaTypes);
			map.from(properties::getMaxChunkSize).asInt(DataSize::toBytes).when((size) -> size > 0)
					.to(resolver::setResponseMaxChunkSizeBytes);
			map.from(properties::getFullModeViewNames).to(resolver::setFullModeViewNames);
			map.from(properties::getChunkedModeViewNames).to(resolver::setChunkedModeViewNames);
		}

	}

	@Configuration
	@ConditionalOnClass(LayoutDialect.class)
	protected static class ThymeleafWebLayoutConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public LayoutDialect layoutDialect() {
			return new LayoutDialect();
		}

	}

	@Configuration
	@ConditionalOnClass(DataAttributeDialect.class)
	protected static class DataAttributeDialectConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public DataAttributeDialect dialect() {
			return new DataAttributeDialect();
		}

	}

	@Configuration
	@ConditionalOnClass({ SpringSecurityDialect.class })
	protected static class ThymeleafSecurityDialectConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public SpringSecurityDialect securityDialect() {
			return new SpringSecurityDialect();
		}

	}

	@Configuration
	@ConditionalOnClass(Java8TimeDialect.class)
	protected static class ThymeleafJava8TimeDialect {

		@Bean
		@ConditionalOnMissingBean
		public Java8TimeDialect java8TimeDialect() {
			return new Java8TimeDialect();
		}

	}

}

If you are building a web application, thymeleaf template will be your default template engine.  Spring boot will load this class if TemplateMode and SpringBootEngine are loaded. We can see the use of @Conditional annotation.

How to exclude Spring Boot AutoConfiguration?

Spring Boot does offer an option to exclude any of the autoconfiguration you don’t want to include in your project.


@SpringBootApplication(exclude = {BatchAutoConfiguration.class)
public class HomeApplication 
{
    public static void main(String[] args) {
        SpringApplication.run(HomeApplication.class, args);
    }
}

One thing to remember here is that you must know why you are excluding a certain bean and if you are ok that it might exclude some dependent configurations.

Conclusion

In this post, I showed

  • How Spring Boot works and how you can build the Spring Boot application with some of the dependencies.
  • What Auto Configuration is and what it includes.

If you enjoyed this post or have any other questions, subscribe to my blog.

References

  1. Spring Boot Guide
  2. Spring Boot Official Documentation
  3. What is Spring Boot?

 

 

Key concepts about using JProfiler

JProfiler is a tool for analyzing what is going on inside a JVM. E-J Technologies has developed this tool. The most common usage is for CPU Profiling or Memory analyzing. In this post, I want to detail a few key concepts about using JProfiler.

Every software developer has to use JProfiler or similar tools VisualVM or JDK bundled tools when improving the performance of an enterprise application. Previously, I had shown how to use JProfiler for performance testing.

JProfiler focuses on three different topics that can help developers to analyze their application.

  • Method Calls – This shows what your application is doing with method calls and where the performance can be improved.
  • Thread and Lock – To find any multi-threading issues, you should check the thread and lock analysis that JProfiler provides.
  • Allocations – You can analyze the objects on the heap, reference chains, and garbage collection. Therefore, it helps to fix memory leak issues.

JProfiler also provides database analysis that can help in understanding if there are any performance issues with database queries. I will not be covering that in this post.

JProfiler Settings

There are two ways you can collect data while using JProfiler.

  • Sampling – This does not necessarily provide the accuracy of the collected data. This feature doesn’t provide method-level statistics. The advantage of using this feature is that system performance is not affected when running JProfiler.
  • Instrumentation – This mode collects the entire data, but also heavy on performance affecting the application. The advantage is the accuracy of the data. If you are doing performance analysis, this should be your default choice even with the drawback, it provides large data that is helpful to analyze performance.

How to run JProfiler on a running application

JProfiler offers two modes to start the profiling of an application. If you run in local mode, you have to choose data collection mode when starting the JProfiler. In this mode, the application and JProfiler are running on the same machine. Usually not recommended.

In remote mode, you will start your JProfiler agent on a remote machine, probably the same machine where the application is running. And from your local JProfiler installation, you will connect to the JProfiler agent. To do this, you will need the IP address of the remote machine as well as the port where the JProfiler agent is running. In short, you are connecting JProfiler GUI to a remote JProfiler agent that is profiling your application.

How to analyze application performance using JProfiler

If you are running a performance test on your application, you will stop the profiling when the performance test stops. Once the profiling is complete, JProfiler GUI should provide a snapshot of the profiled data.

In this snapshot, we will have data for performance analysis number of objects generated, method calls, garbage collection, memory leaks. We will go through these key concepts from JProfiler snapshot data now.

Overview page on Telemetries as shown below will give you an idea about metrics such as garbage collection activity, memory, threads, classes, and CPU load.

CPU Views

CPU views display the number of executions, call relationships of each method in the application. In CPU Views, you can find any specific method you suspect might be causing performance issues. This is a great view to analyze what might be the culprit in your application.

Call Tree

Call Tree shows hierarchical view of all the methods calls during the execution of your application. JProfiler helps with sorting the methods with their total execution times.

As shown above, we can see method to read data from Redis Cache is taking the most time.

Hot Spots

In the same view of CPU Views, there is an option for Hot Spots. This view can sort the methods based on various factors, such as individual execution time, total execution time, average execution time, and number of calls.

Live Memory

Live memory view shows memory allocation. This is where we can find if there is any memory leak.

All Objects

All objects view will show all the objects that were created during execution of the application and size of these objects.

 

Conclusion

In this post, I showed how to some key concepts about using JProfiler for performance analysis. JProfiler is a powerful tool if used effectively. There are a lot of features in JProfiler and it can be overwhelming while using the tool. I hope this post will help you use JProfiler to analyze your application.

References

  1. JProfiler Introduction – JProfiler Introduction

 

 

Performance Monitoring with Spring Boot

In this post, I want to show some interesting features that Spring Boot offers for performance monitoring.

Once the application starts to scale, performance becomes the topmost priority. We over-optimize the application to lose simplicity. That’s how software development works. In a production scenario, we monitor our application for performance. With most applications moving to the cloud, it is essential to monitor the application and improve the performance constantly.

If you have used a spring actuator, it provides a number of statistics to monitor. Previously, I covered this topic Spring-Actuator.

Subsequently, we will be covering some different features of Spring Boot. We will be talking about CustomizableTraceInterceptor, PerformanceMonitorInterceptor, and CommonsRequestLoggingFilter.

Using CustomizableTraceInterceptor

You can add CustomizableTraceInterceptor as a Bean and use that Bean as an advisor for what expressions you want to intercept. Basically, this interceptor allows us to intercept the method calls and add custom log messages.

To show this in the working example, we will be tracking repository timings. Firstly, create a class that will extend CustomizableTraceInterceptor as follows:


package com.abccompany.home.performance;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.interceptor.CustomizableTraceInterceptor;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

public class RepositoryMethodInterceptor extends CustomizableTraceInterceptor
{
    @Override
    protected Class<?> getClassForLogging(Object target)
    {
        Class<?> classForLogging = super.getClassForLogging(target);
        if (SimpleJpaRepository.class.equals(classForLogging))
        {
            Class<?>[] interfaces = AopProxyUtils.proxiedUserInterfaces(target);
            if (interfaces.length > 0)
            {
                return interfaces[0];
            }
        }
        return classForLogging;
    }


    protected void writeToLog(Log logger, String message, Throwable ex)
    {
        if (ex != null)
        {
            logger.info(message, ex);
        }
        else
        {
            logger.info(message);
        }
    }

    protected boolean isInterceptorEnabled(MethodInvocation invocation, Log logger)
    {
        return true;
    }

}

Likewise, I will explain in a bit what this class is doing. We need a @Bean that will use this interceptor to intercept repository methods. The code for this will look like below:


package com.abccompany.home.performance;

import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class TraceLoggerConfig
{
    @Bean
    public RepositoryMethodInterceptor repositoryMethodInterceptor()
    {
        RepositoryMethodInterceptor repositoryMethodInterceptor = new RepositoryMethodInterceptor();
        repositoryMethodInterceptor.setHideProxyClassNames(true);
        repositoryMethodInterceptor.setUseDynamicLogger(false);
        repositoryMethodInterceptor.setExitMessage("Executed $[methodName] in $[invocationTime] " +
                "ms");
        return repositoryMethodInterceptor;
    }

    @Bean
    public Advisor advisor()
    {
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression("execution(public * com.abccompany.home" +
                ".repositories.*Repository+.*(..))");
        return new DefaultPointcutAdvisor(aspectJExpressionPointcut, repositoryMethodInterceptor());
    }
}

Now if we look at this configuration, this is creating a bean for using RepositoryMethodInterceptor which is a subclass of CustomizableTraceInterceptor. You can see we have used an exit message to log the timing that the repository method took in this bean.



repositoryMethodInterceptor.setExitMessage("Executed $[methodName] in $[invocationTime] " + "ms");

AspectJExpression creates an expression for which the packages interception should happen. The class RepositoryMethodInterceptor does a few useful things. Firstly, it helps us to track the class information of Repository classes. Secondly, it logs the message in our log file. Once you run the application, you will see the log messages as below:



2020-05-24 19:08:04.870 INFO 14724 --- [nio-8443-exec-9] c.r.h.p.RepositoryMethodInterceptor : Entering method 'findUserByEmail' of class [com.abccompany.home.repositories.UserdataRepository] Hibernate: select userdata0_.id as id1_4_, userdata0_.email as email2_4_, userdata0_.firstname as firstnam3_4_, userdata0_.guid as guid4_4_, userdata0_.lastname as lastname5_4_, userdata0_.middlename as middlena6_4_, userdata0_.confirmpassword as confirmp7_4_, userdata0_.passwordtxt as password8_4_, userdata0_.phonenumber as phonenum9_4_, userdata0_.role as role10_4_ from userdata userdata0_ where userdata0_.email=? 
2020-05-24 19:08:04.872 INFO 14724 --- [nio-8443-exec-9] c.r.h.p.RepositoryMethodInterceptor : Executed findUserByEmail in 2 ms 
2020-05-24 19:08:04.872 INFO 14724 --- [nio-8443-exec-9] c.r.h.p.RepositoryMethodInterceptor : Entering method 'findAll' of class [com.abccompany.home.repositories.FeedbackRepository] Hibernate: select feedback0_.id as id1_1_, feedback0_.createdon as createdo2_1_, feedback0_.fromdate as fromdate3_1_, feedback0_.guid as guid4_1_, feedback0_.rating as rating5_1_, feedback0_.rentalpropertyid as rentalpr8_1_, feedback0_.review as review6_1_, feedback0_.todate as todate7_1_, feedback0_.userid as userid9_1_ from feedback feedback0_ 
2020-05-24 19:08:04.876 INFO 14724 --- [nio-8443-exec-9] c.r.h.p.RepositoryMethodInterceptor : Executed findAll in 4 ms

Using PerformanceMonitorInterceptor feature

So, in order to use PerformanceMonitorInterceptor , we will create a configuration class and add a bean that will create PerformanceMonitorInterceptor. AspectJExpressionPointcut will point to the expression that will evaluate our controller classes.

Like CustomizableTraceInterceptor, we will have a subclass that will extend PerformanceMonitoringInterceptor so we can log our messages in Spring Boot logging. This will look like below:


package com.abccompany.home.performance;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.springframework.aop.interceptor.PerformanceMonitorInterceptor;


public class ControllerMonitoringInterceptor extends PerformanceMonitorInterceptor
{
    protected void writeToLog(Log logger, String message, Throwable ex)
    {
        if (ex != null)
        {
            logger.info(message, ex);
        }
        else
        {
            logger.info(message);
        }
    }

    protected boolean isInterceptorEnabled(MethodInvocation invocation, Log logger)
    {
        return true;
    }
}

We will create a bean for ControllerMonitoringInterceptor . This bean will be part of our logger configuration which will also evaluate AspectJExpression for Controller classes. Therefore, it will look like below:



package com.abccompany.home.performance;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.interceptor.PerformanceMonitorInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@Aspect
public class ControllerLoggerConfig
{
    @Pointcut("execution(* com.abccompany.home.controllers.*Controller+.*(..))")
    public void monitor()
    {

    }

    @Bean
    public ControllerMonitoringInterceptor controllerMonitoringInterceptor()
    {
        return new ControllerMonitoringInterceptor();
    }

    @Bean
    public Advisor advisorPerformance()
    {
        AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression("com.abccompany.home.performance" +
                ".ControllerLoggerConfig.monitor()");
        return new DefaultPointcutAdvisor(aspectJExpressionPointcut,
                controllerMonitoringInterceptor());
    }
}

Now if we execute the application, the log messages will show latencies for Controller classes.


2020-05-24 20:12:09.237  INFO 9280 --- [nio-8443-exec-6] c.r.h.p.ControllerMonitoringInterceptor  : StopWatch 'com.abccompany.home.controllers.LoginController.signin': running time (millis) = 0
2020-05-24 20:12:18.263  INFO 9280 --- [nio-8443-exec-2] c.r.h.p.ControllerMonitoringInterceptor  : StopWatch 'com.abccompany.home.controllers.MainController.home': running time (millis) = 43
2020-05-24 20:12:20.025  INFO 9280 --- [nio-8443-exec-9] c.r.h.p.ControllerMonitoringInterceptor  : StopWatch 'com.abccompany.home.controllers.MainController.logout': running time (millis) = 12
2020-05-24 20:12:20.042  INFO 9280 --- [nio-8443-exec-5] c.r.h.p.ControllerMonitoringInterceptor  : StopWatch 'com.abccompany.home.controllers.LoginController.login': running time (millis) = 0

How to use CommonsRequestLoggingFilter

Additionally, Spring Boot offers one useful feature of logging incoming requests. This helps in monitoring the application and to see how the requests are coming. To use this feature, we will create a @Configuration class RequestLoggingFilter as below:


package com.abccompany.home.performance;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

import javax.servlet.http.HttpServletRequest;

@Configuration
public class RequestLoggingFilter extends AbstractRequestLoggingFilter
{
    @Bean
    public CommonsRequestLoggingFilter requestLoggingFilterConfig()
    {
        CommonsRequestLoggingFilter commonsRequestLoggingFilter = new CommonsRequestLoggingFilter();
        commonsRequestLoggingFilter.setIncludeClientInfo(true);
        commonsRequestLoggingFilter.setIncludeQueryString(true);
        commonsRequestLoggingFilter.setIncludePayload(true);
        return commonsRequestLoggingFilter;
    }

    @Override
    protected void beforeRequest (HttpServletRequest request, String message)
    {
        logger.info(message);
    }

    @Override
    protected void afterRequest (HttpServletRequest request, String message)
    {
        logger.info(message);
    }
}

Once, we add this, we will see beforeRequest and afterRequest messages in the log as below:



2020-05-24 21:07:15.161  INFO 11984 --- [nio-8443-exec-1] gFilter$$EnhancerBySpringCGLIB$$cb4fdaab : Before request [uri=/css/bootstrap.min.css]
2020-05-24 21:07:15.171  INFO 11984 --- [nio-8443-exec-2] gFilter$$EnhancerBySpringCGLIB$$cb4fdaab : Before request [uri=/js/jquery.min.js]
2020-05-24 21:07:15.203  INFO 11984 --- [nio-8443-exec-7] gFilter$$EnhancerBySpringCGLIB$$cb4fdaab : Before request [uri=/js/bootstrap.min.js]
2020-05-24 21:07:15.290  INFO 11984 --- [nio-8443-exec-7] gFilter$$EnhancerBySpringCGLIB$$cb4fdaab : After request [uri=/js/bootstrap.min.js]
2020-05-24 21:07:15.306  INFO 11984 --- [nio-8443-exec-2] gFilter$$EnhancerBySpringCGLIB$$cb4fdaab : After request [uri=/js/jquery.min.js]
2020-05-24 21:07:15.318  INFO 11984 --- [nio-8443-exec-1] gFilter$$EnhancerBySpringCGLIB$$cb4fdaab : After request [uri=/css/bootstrap.min.css]

Conclusion

In conclusion, I showed three features for performance monitoring – CustomizableTraceInterceptor, PerformanceMonitorInterceptor, and CommonsRequestLoggingFilter to log useful performance metrics.

References

  1. Spring Framework Features
  2. CommonsRequestLoggingFilter