Tag Archives: Java

Spring WebClient vs RestTemplate – Comparison and Features

Introduction

Spring 5 introduced a new reactive web client called WebClient. In this post, I will show when and how we can use Spring WebClient vs RestTemplate. I will also describe what features WebClient offers.

What is RestTemplate?

RestTemplate is a central Spring class that allows HTTP access from the client-side. RestTemplate offers POST, GET, PUT, DELETE, HEAD, and OPTIONS HTTP methods. The simple use case of RestTemplate is to consume Restful web services.

You can create a bean that provides the instance of RestTemplate. You can then @autowire this bean in any class where you plan to call REST services. RestTemplate is the class that implements the interface RestOperations.

The following code shows the declaration of the bean:

    @Bean
    public RestOperations restOperations()
    {
        return new RestTemplate();
    }

The following code shows a REST client `YelpClient` calling Yelp’s REST API to get rental property reviews.

   @Autowired
   private final RestOperations restOperations;

   public List getRentalPropertyReviews(String address)
   {
        String url = buildRestUrl(businessId);
        HttpHeaders httpHeaders = new HttpHeaders();
        String apiKey = getApiKey(YELP);
        httpHeaders.add("Authorization","Bearer " + apiKey);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);

        HttpEntity entity = new HttpEntity("parameters", httpHeaders);
        ResponseEntity response;

        try
        {
            response = restOperations.exchange(url, HttpMethod.GET,entity, String.class);
        }
        catch(RestClientException e)
        {
            throw new RuntimeException("Unable to retrieve reviews", e);
        }

    }

In the above code, we are building HTTP Headers by adding Yelp’s REST API key as part of the authorization. We call the GET method to get review data.

Basically, one has to do

  • Autowire the RestTemplate object
  • Build HTTP Headers with authorization and Content Type
  • Use HttpEntity to wrap the request object
  • Provide URL, Http Method, and the Return type for exchange method.

What is WebClient?

Spring 5 introduced a reactive web client called WebClient. It’s an interface to perform web requests. It is part of the Spring web reactive module. WebClient will be replacing RestTemplate eventually.

Most importantly, WebClient is reactive, nonblocking, asynchronous, and works over HTTP protocol Http/1.1.

To use WebClient, one has to do

  • Create an instance of WebClient
  • Make a request to the REST endpoint
  • handle the response

 

   WebClient webClient = WebClient
       .builder()
       .baseUrl("https://localhost:8443")
       .defaultCookie("cookieKey", "cookieValue")
       .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) 
       .defaultUriVariables(Collections.singletonMap("url", "https://localhost:8443"))
       .build();

The above code shows one way to instantiate WebClient. You can also create an instance by simply using WebClient webClient = WebClient.create();

WebClient provides two methods exchange and retrieve . exchange method usually fetches the response along with status and headers. retrieve method gets the response body directly. It’s easier to use.

Also depending on if you are trying to fetch a single object in response or a list of objects, you can use mono or flux.

this.webClient =
                webClientBuilder.baseUrl("http://localhost:8080/v1/betterjavacode/").build();

this.webClient.get()
                .uri("users")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve().bodyToFlux(UserDto.class).collectList();

The above code basically uses webClient to fetch a list of users from the REST API.

Spring WebClient vs RestTemplate

We already know the one key difference between these two features. WebClient is a non-blocking client and RestTemplate is a blocking client.

RestTemplate uses Java Servlet API under the hood. Servlet API is a synchronous caller. Because it is synchronous, the thread will block until webclient responds to the request.

Consequently, Requests waiting for results will increase. This will result in an increase in memory.

On the other hand, WebClient is an asynchronous non-blocking client. It uses Spring’s reactive framework under the hood. WebClient is a part of the Spring-WebFlux module.

Spring WebFlux uses reactor library. It provides Mono and Flux API to work data sequences. Reactor is a reactive streams library. And, all of its operators support non-blocking back pressure.

Example of how to use WebClient in a Spring Boot Application

We can combine the capabilities of Spring Web MVC and Spring WebFlux. In this section, I will create a sample application. This application will call a REST API using WebFlux and we will build a response to show a web page with a list of users.

RestController for this example is an API to get a list of users:

package com.betterjavacode.webclientdemo.controllers;

import com.betterjavacode.webclientdemo.dto.UserDto;
import com.betterjavacode.webclientdemo.managers.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("v1/betterjavacode")
public class UserController
{
    @Autowired
    public UserManager userManager;

    @GetMapping(value = "/users")
    public List getUsers()
    {
        return userManager.getAllUsers();
    }
}

Controller class that uses a WebClient to call REST API looks like below:

package com.betterjavacode.webclientdemo.controllers;

import com.betterjavacode.webclientdemo.clients.UserClient;
import com.betterjavacode.webclientdemo.dto.UserDto;
import com.betterjavacode.webclientdemo.managers.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@Controller
public class MainController
{
    @Autowired
    UserClient userClient;

    @GetMapping(value = "/")
    public String home()
    {
        return "home";
    }

    @GetMapping(value = "/users")
    public String getUsers(Model model)
    {
        List users = userClient.getUsers().block();

        model.addAttribute("userslist", users);
        return "users";
    }
}

Now, the important piece of code of UserClient is where we will be using WebClient to call REST API.

package com.betterjavacode.webclientdemo.clients;

import com.betterjavacode.webclientdemo.dto.UserDto;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

@Service
public class UserClient
{

    private WebClient webClient;

    public UserClient(WebClient.Builder webClientBuilder)
    {
        this.webClient =
                webClientBuilder.baseUrl("http://localhost:8080/v1/betterjavacode/").build();
    }

    public Mono<List> getUsers()
    {
        return this.webClient.get()
                .uri("users")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve().bodyToFlux(UserDto.class).collectList();
    }
}

Above code shows first building the WebClient and then using it to retrieve response from REST API. retrieve method offers two options of mono or flux. Since we have more than one user to get, we are using flux.

This shows we can use reactive, non-blocking WebClient which is part of WebFlux in Spring Web MVC framework.

What Else Is There in Spring WebClient?

Spring WebClient is part of Spring WebFlux framework. The major advantage of this API is that the developer doesn’t have to worry about concurrency or threads. WebClient takes care of that.

WebClient has a built-in HTTP Client library support to perform requests with. That includes Apache HttpComponents, Jetty Reactive HttpClient, or Reactor Netty.

WebClient.builder() offers following options:

  • uriBuilderFactory – customized uriBuilderFactory to use base URL
  • defaultHeader – Headers for every request
  • defaultCookie – Cookies for every request
  • defaultRequest – To customize every request
  • filter – Client filter for every request
  • exchangeStrategies – HTTP Message reader/writer customizations

I already showed retrieve method in the above code demo.

WebClient also offers a method exchange with varients like exchangeToMono and exchangeToFlux.

With attribute(), we can also add attributes to the request.

Alternatively, one can use WebClient for synchronous use also. In my example above MainController, I use block to get the final result. This basically blocks parallel calls till we get the result.

One key feature that WebClient offers is retryWhen(). For more resilient system, it is a great feature that you can add while using WebClient.

        webClient
            .get()
            .uri(String.join("", "/users", id))
            .retrieve()
            .bodyToMono(UserDto.class)
            .retryWhen(Retry.fixedDelay(5, Duration.ofMillis(100)))
            .block();

retryWhen takes Retry class as a parameter.

WebClient also offers a feature for error handling. doOnError() allows you to handle the error. It is triggered when mono ends with an error. onErrorResume() is a fallback based on the error.

Conclusion

In this post, I showed what is Spring WebClient is, how we can use Spring WebClient vs RestTemplate, and what different features it offers.

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

References

  1. Spring WebClient – Spring Documentation
  2. WebClient Cheatsheet – Spring WebClient

Fundamentals of a Distributed System Design

When you are a beginner software developer, your focus is on the micro-level. What happens in your code? What happens in your application? But if you start thinking in a System Design way, it can help you immensely in your career. System design is a big topic, but I will cover the important fundamentals of distributed system design. Understanding System Design is the key to building a good system. Therefore, a developer should definitely try to learn about system design.

Fundamentals of a Distributed System

In this post, we will learn the following fundamentals.

  1. Key characteristics of a distributed system
  2. Load balancing
  3. Caching
  4. Database
  5. Database indexes
  6. Proxies
  7. CAP Theorem
  8. Consistent Hashing

Key Characteristics of a distributed system

Scalability

  • Scalability is the system’s ability to grow and manage increased demand
  • Horizontal scaling – you scale by adding more servers into your pool of resources.
  • Vertical scaling – you scale by adding more power to an existing server.

Reliability

  • It is the probability a system will fail in a given period. Specifically, the goal is to minimize this probability as much as possible.
  • To achieve reliability, redundancy is required. Therefore, it has a cost.

Availability

  • Availability is the time a system remains operational to perform its required function in a specific period.
  • If a system is reliable, it is available. By comparison, if it is available, it is not necessarily reliable.

Efficiency

  • Latency – response time
  • Throughput – the number of items delivered in a given time unit

Load Balancing

The load balancer routes the traffic from clients to different servers. It keeps track of the status of all the resources while distributing requests. Equally, a load balancer reduces individual server load and prevents any one application server from becoming a single point of failure. So, the load balancer can be added between clients and web servers, between webservers and an internal platform layer (application server), and between internal platform and database servers.

To organize a load balancer for distributing requests to servers, one can use different algorithms like Round Robin, Weighted Round Robin, Least Connection Method, Least Response Time, Least Bandwidth, IP Hash.

As a result, the load balancer can be a single point of failure. To overcome this, a second load balancer can be connected to the first to form a cluster.

Caching

Caches take advantage of the locality of references principle. A cache is like a short term memory. That is to say, it is faster with limited space. Furthermore, caches can exist at all levels in the architecture but often found at the level nearest to the front end.

Application Server Cache

Placing a cache directly on a request layer node enables the local storage of response data.

Content Distribution Network

CDNs are a kind of cache that comes into play for sites serving large amounts of static data.

Cache Invalidation

  1. Write through cache – Write the data into the cache and the corresponding database at the same time.
  2. Write around cache – Write the data to permanent storage, bypassing the cache. Therefore, recently written data will create a cache miss.
  3. Write-back cache – Write the data to cache alone and sync with backend storage after a specified interval.

Cache Eviction Policies

  1. First In First Out
  2. Last In First Out
  3. Least Recently Used
  4. Least Frequently Used
  5. Most Recently Used
  6. Random Replacement

Database

You will need a storage system for your data. Obviously, Databases are the most common solution. Accordingly, there are two types of databases. Basically, Relational databases and Non-Relational databases.

If your data is structured, you can use a relational database. Also, relational databases offer structured query language (SQL) to query the databases.

Non-relational databases are unstructured, and distributed.

SQL

  1. Store data in rows and columns
  2. Each row contains information about one entity
  3. MySQL, MS SQL, Oracle, PostgreSQL, SQLite are some examples of relational databases.
  4. SQL databases use SQL for querying.
  5. Vertically scalable, but expensive.
  6. Horizontally scalable, but time-consuming process.
  7. SQL databases are ACID (Atomicity, Consistency, Isolation, and Durability) compliant.
  8. If you need ACID compliance and structured data, use SQL databases.

NoSQL

  1. Key-Value Stores  – Redis, Dynamo DB
  2. Document databases – Couch DB and MongoDB
  3. Wide-Column databases – Columnar databases are best suited for analyzing large datasets – Cassandra and HBase
  4. Graph databases – data stored and related to each other in graph format.  Subsequently, data is stored with nodes (entities), properties (info about entities), and lines (the connection between entities) – Neo4J and InfiniteGraph
  5. Schemas are dynamic. Columns can be added on the fly and each row doesn’t have to contain data for each column.
  6. Use UnQL (Unstructured Query Language).
  7. Horizontally scalable easily.
  8. Not ACID Compliant
  9. Allows rapid development, stores a large volume of data with no structure.

Database Indexes

If the database search performance has been bad, we create indexes to improve that performance. Henceforth, the goal of creating an index on a particular table in a database is to make it faster to search through the table.

Indexes improve read performance, but decrease write performance. Consequently, indexes also increase memory usage. If your database is read-intensive, indexes are a good strategy. Don’t add indexes if the database is write-intensive.

Proxies

Proxy server is a piece of software or hardware that acts as an intermediary for requests from clients seekings resources from other servers. Accordingly, Proxies are used to filter requests, log requests, and sometimes transform the requests. Even more, proxy server cache can serve a lot of requests.

Open Proxy

An open proxy server is accessible by any internet user. As a result, any internet user is able to use the proxy for forwarding the requests.

Reverse Proxy

A reverse proxy retrieves resources on behalf of the client from one or more servers. Consequently, these resources are then returned to the client.

CAP Theorem

In any distributed system, you can not achieve all three consistency, availability, and partition tolerance.

CAP Theorem states that you can only get two out of these three options.

Consistency – All nodes see the same data at the same time.

Availability – Every request gets a response on success/failure.

Partition Tolerance – A partition tolerant system can tolerate any amount of network failure that doesn’t result in a failure of the entire network. Particularly, data replication across nodes helps to keep the system up.

Consistent Hashing

Consistent hashing is a mechanism that allows distributing the data across a cluster in such a way that will minimize reorganization when nodes are added or removed. As a result, when you employ consistent hashing, resizing of the hash table results in the remapping of k/n keys.

Conclusion

In conclusion, knowing these fundamentals about a distributed system can immensely help a developer while writing code or designing a system. By all means, study these fundamentals, but you should also learn about domain-driven design. Nonetheless, if you enjoyed this post, you can subscribe to my blog here.

References

  1. System Design Primer – System Design Primer
  2. System Design – System Design

How to Use Basic Authentication for Rest Template

In this post, I will show how to use Rest Template to consume RESTful API secured with Basic Authentication. As part of this post, I will show how to build a REST API that is secured with Basic Authentication.

Overview

Basic Authentication is one of the mechanisms that you can use to secure your REST API. In my previous post, I showed how to secure REST API with Json Web Token.

Secure a REST API with Basic Authentication

Configure a REST API

Firstly, we will show a simple REST API to create users or retrieve users from the database. Then, we will secure this REST API with a Basic Authentication mechanism. Lastly, we will show how to use Basic Authentication with Rest Template to call this REST API.

Our REST controller class for this API to create or retrieve users will look like below:


package com.betterjavacode.restdemo.controllers;

import com.betterjavacode.restdemo.dto.UserDto;
import com.betterjavacode.restdemo.managers.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class UserController
{
    @Autowired
    private UserManager userManager;

    @RequestMapping(value = "/user/", method = RequestMethod.GET)
    public ResponseEntity<List> listAllUsers()
    {
        List users = userManager.getAllUsers();
        if(users.isEmpty())
        {
            return new ResponseEntity<List>(HttpStatus.NO_CONTENT);
        }

        return new ResponseEntity<>(users, HttpStatus.OK);
    }

    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces =
            MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity getUser(@PathVariable("id") long id)
    {
        UserDto userDto = userManager.getUser(id);
        if(userDto == null)
        {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return new ResponseEntity<>(userDto, HttpStatus.OK);
    }


    @RequestMapping(value = "/user/", method= RequestMethod.POST)
    public ResponseEntity createUser(@RequestBody UserDto userDto)
    {
        UserDto user = userManager.createUser(userDto);

        return new ResponseEntity<>(user, HttpStatus.OK);
    }

    @RequestMapping(value = "/user/{id}", method=RequestMethod.DELETE)
    public ResponseEntity deleteUser(@PathVariable("id") long id)
    {
        UserDto user = userManager.getUser(id);

        if(user == null)
        {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }

        userManager.deleteUser(id);

        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

Our database model class for User will look like below:


package com.betterjavacode.restdemo.models;

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

@Entity(name = "User")
@Table(name = "users")
public class User implements Serializable
{
    private static final long serialVersionUID = 20200816121023L;

    public User()
    {

    }

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

    @Column(name="firstname", length=100)
    private String firstname;

    @Column(name="lastname", length=100)
    private String lastname;

    @Column(name="email", length=100)
    private String email;

    @Column(name="role", length=45)
    private String role;

    @Column(name="enabled")
    private boolean enabled;

    public long getId ()
    {
        return id;
    }

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

    public String getFirstname ()
    {
        return firstname;
    }

    public void setFirstname (String firstname)
    {
        this.firstname = firstname;
    }

    public String getLastname ()
    {
        return lastname;
    }

    public void setLastname (String lastname)
    {
        this.lastname = lastname;
    }

    public String getEmail ()
    {
        return email;
    }

    public void setEmail (String email)
    {
        this.email = email;
    }

    public String getRole ()
    {
        return role;
    }

    public void setRole (String role)
    {
        this.role = role;
    }

    public boolean isEnabled ()
    {
        return enabled;
    }

    public void setEnabled (boolean enabled)
    {
        this.enabled = enabled;
    }
}

Just to make sure we understand here that, we are using a DTO object UserDto to create and retrieve the data from the database. User is our database model object.

The UserDto object will be as follows:


package com.betterjavacode.restdemo.dto;

import com.betterjavacode.restdemo.models.User;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class UserDto
{
    private String firstname;
    private String lastname;
    private String email;

    public UserDto(){}

    public UserDto(User user)
    {
        this.setEmail(user.getEmail());
        this.setFirstname(user.getFirstname());
        this.setLastname(user.getLastname());
    }

    public String getFirstname ()
    {
        return firstname;
    }

    public void setFirstname (String firstname)
    {
        this.firstname = firstname;
    }

    public String getLastname ()
    {
        return lastname;
    }

    public void setLastname (String lastname)
    {
        this.lastname = lastname;
    }

    public String getEmail ()
    {
        return email;
    }

    public void setEmail (String email)
    {
        this.email = email;
    }

}

Once we configure our application properties and create the required database table, we will start the application.

Now if we execute the API through a client like Postman, we will be able to retrieve or create the User object.

The goal is to secure this API.

So add Spring-Security in our project build.

implementation "org.springframework.boot:spring-boot-starter-security"

Now, if we add the annotation @EnableWebSecurity in our main application class like below:


package com.betterjavacode.restdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@SpringBootApplication
@EnableWebSecurity
public class RestdemoApplication
{
	public static void main(String[] args)
	{
		SpringApplication.run(RestdemoApplication.class, args);
	}
}

and if we access the API to create user, we will get 401 unauthorized error like below:

Basic Authentication with Rest Template

Basic Authentication

Traditionally, access to REST API will happen on the server-side once the user has logged in with authentication.

Basic authentication provides one of the ways to secure REST API. It’s not the most secure way compared to OAuth or JWT based security. In Basic Authentication, a client sends Base64 encoded credentials with each request using HTTP Authorization Header.

The client will send the Authorization header with each request. There is always a possibility of compromising these credentials even when they are Base64 encoded. To avoid that, we can use HTTPS.

Now from our implementation perspective, we will add a SecurityConfig class to configure security for our REST API.


package com.betterjavacode.restdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        httpSecurity
                .csrf().disable()
                .authorizeRequests().anyRequest().authenticated()
                .and()
                .httpBasic();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth)
            throws Exception
    {
        auth.inMemoryAuthentication()
                .withUser("adminuser")
                .password("{noop}adminpassword")
                .roles("USER");
    }
}

configure method in this class will configure basic authentication and every request coming to our controller will need to be authorized.

configureGlobal method will add authentication of the incoming request. The requests coming through the controller will be validated for these credentials that we have configured for in-memory authentication.

WARNING – This is not the most secure way to secure your API. Definitely not with in-memory authentication. Do not use it in production.

Now if we execute REST API through POSTMAN, we will see the successful response as below:

Rest Template with Basic Authentication Example

Initially, we used POSTMAN as a client to call our REST APIs. But in a real scenario, we won’t be using POSTMAN, you will have to call these APIs programmatically.

We will create a class RestClient and that will call our APIs while building Basic Authentication.

While using RestTemplate that Spring Boot provides, you need to pass HttpHeaders with a RequestEntity.


    private static HttpHeaders getHeaders ()
    {
        String adminuserCredentials = "adminuser:adminpassword";
        String encodedCredentials =
                new String(Base64.encodeBase64(adminuserCredentials.getBytes()));

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Authorization", "Basic " + encodedCredentials);
        httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        return httpHeaders;
    }

We use exchange method from RestTemplate to call our API and HttpHeaders that contain Basic Authentication.

The whole class  RestClient will look like below:


package com.betterjavacode.restdemo;


import com.betterjavacode.restdemo.dto.UserDto;
import org.apache.tomcat.util.codec.binary.Base64;
import org.json.JSONObject;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;

public class RestClient
{
    public static final String REST_SERVICE_URL = "http://localhost:8080/user/";

    private static HttpHeaders getHeaders ()
    {
        String adminuserCredentials = "adminuser:adminpassword";
        String encodedCredentials =
                new String(Base64.encodeBase64(adminuserCredentials.getBytes()));

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Authorization", "Basic " + encodedCredentials);
        httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        return httpHeaders;
    }

    private static void listAllUsers()
    {
        System.out.println("Getting all users");
        RestTemplate restTemplate = new RestTemplate();

        HttpHeaders httpHeaders = getHeaders();

        HttpEntity httpEntity = new HttpEntity<>(httpHeaders);

        ResponseEntity responseEntity = restTemplate.exchange(REST_SERVICE_URL,
                HttpMethod.GET, httpEntity, List.class);

        if(responseEntity.hasBody())
        {
            List<LinkedHashMap<String, Object>> users = responseEntity.getBody();

            if(users != null)
            {
                for(LinkedHashMap<String, Object> userMap: users)
                {
                    System.out.println("User is " + userMap.get("firstname") + " " + userMap.get(
                            "lastname"));
                }
            }
        }
        else
        {
            System.out.println("User not found");
        }

    }

    public static void main (String[] args)
    {
        listAllUsers();

        getUser(1);
    }



    private static void getUser(long id)
    {
        System.out.println("Getting a user ");

        String restUrl = REST_SERVICE_URL  + id;

        RestTemplate restTemplate = new RestTemplate();

        HttpHeaders httpHeaders = getHeaders();

        HttpEntity httpEntity = new HttpEntity<>(httpHeaders);

        ResponseEntity responseEntity = restTemplate.exchange(restUrl,
                HttpMethod.GET, httpEntity, String.class);

        if(responseEntity.hasBody())
        {
            JSONObject jsonObject = new JSONObject(responseEntity.getBody());

            System.out.println(jsonObject.get("firstname"));
            System.out.println(jsonObject.get("lastname"));
        }
        else
        {
            System.out.println("User not found");
        }

    }
}

Now if we execute the program, we will see the output as below:

In this post, we showed how to secure REST API with Basic Authentication. If you enjoyed this post, subscribe to my blog here.

Do you want to know the fundamentals of Spring Security? I’m launching my new book “Simplifying Spring Security” soon. Get on my launch list to get updates and discount codes.

References

  1. Spring Rest Template –  documentation
  2. Spring Boot Rest Template – Usage

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

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?