Tag Archives: Spring-boot

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:

Create an application with Spring Boot and ReactJS

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?

 

 

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.

Features for Performance Monitoring

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

 

Error with Spring Context Lookup Failed

In this post, I will show how to resolve the error Spring context lookup failed. This scenario resembles if you are using Spring Boot 2.x or Spring Boot 1.5. I was recently using Spring Boot 2.x and ran into an error like below:

org.glassfish.jersey.server.spring.SpringComponentProvider: Spring context lookup failed, 
skipping spring component provider initialization.

 

Why did I see this error?

Before I can explain why I saw this error. Let’s sort of some basic concepts about what  ContextLoaderListner is and what DispatcherServlet is.

Spring boot creates multiple contexts. It will start with a root context and the rest of the contexts will be child contexts. Child contexts can access the beans defined in the root context, but not vice versa.

ContextLoaderListener

When you start the Spring Boot application, ContextLoaderListener creates the root application context. Spring boot will share this root context with all other child contexts that DispatcherServlet will create. Usually, you define ContextLoaderListener in web.xml like below:


<listener>
  <listener-class>
    org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>
  
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring/applicationContext.xml</param-value>
</context-param>

ServletContext stores this root application context and one can use WebApplicationContextUtils to retrieve this context.

DispatcherServlet

DispatcherServlet is a HttpServlet whose primary purpose is to handle incoming web requests matching the configured URL pattern. When you define DispatcherServlet in Spring configuration, Spring will use a configured configuration file or creates its own configuration file based on controllers and views in the code.

DispatcherServlet creates one child application context per servlet entry.

Why Spring Context Lookup Failed:

Spring Boot 2.x offers a programmatic way to configure your contexts and servlets. In my particular case, I was using this programmatic way. But I had configured anyway to load application context, especially root application context.

To configure this root application context, I needed ContextLoaderListener . Since I didn’t want to use web.xml , I looked for a programmatic way. Spring boot 2.x offers this through SpringBootServletInitializer . Basically, when we extend the root application class with SpringBootServletInitializer, it binds Servlet, Filter, and ServletInitializer beans from the application context to the server. This also allows us to create a WAR archive of our application and run in any web container.

Once I added SpringBootServletInitializer in my main Spring Boot Application class, I was able to resolve this error. The code for that will look like below:


@SpringBootApplication
public class Application extends SpringBootServletInitializer 
{    
    public static void main(String[] args) 
    {
        SpringApplication sa = new SpringApplication(Application.class);
        sa.run(args);
    }
}

 

Conclusion

In this post, we showed how we can resolve an error Spring Context Lookup Failed. If you have any feedback about this post, please comment and I will be happy to answer.

References

  1. ContextLoaderListener vs DispatcherServlet
  2. Spring Boot Servlet Initializer
  3. Upgrade Spring Boot and adding Spring Boot Actuator – Spring Boot Actuator