Enterprise Java

Spring Boot Tutorial

1. Introduction

If you always wanted to work with a Web Framework which allows you to jump-start into API development without the hassle of setting up Web Servers, collecting all the wired dependencies, installing various tools, you’ve been blessed with an excellent framework, Spring Boot. The prime motto of Spring Boot is convention over configuration.

In this lesson, we will study how easy it is to set up a Spring Boot project and make some RESTful services which interact with a database in under 20 minutes! Exactly, that’s how easy it is with Spring Boot. Spring Boot allows us to make production-grade Spring-based applications that you can “just run”. The best part of a Spring boot application is that it needs very little configuration.

2. Making the Spring Boot Project with Maven

We will be using one of the many Maven archetypes to create a sample project for our example. To create the project execute the following command in a directory that you will use as workspace:

mvn archetype:generate -DgroupId=com.javacodegeeks.example -DartifactId=JCG-SpringBoot-example -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

If you are running maven for the first time, it will take a few seconds to accomplish the generate command because maven has to download all the required plugins and artifacts in order to make the generation task.

Once you have created the project, feel free to open it in your favourite IDE. Next step is to add appropriate Maven Dependencies to the project. We will work with four dependencies in our project:

  • spring-boot-starter-web: This dependency marks this project as a Web project and it adds dependencies in order to create controllers and make Web-related classes.
  • spring-boot-starter-data-jpa: This dependency provides the project with the JPA support where we can perform many Database related operations
  • h2: H2 is an in-memory database which we will use to demonstrate DB operations in this lesson
  • spring-boot-starter-test: This dependency collects all test related JARs into the project like JUnit and Mockito.

Here is the pom.xml file with the appropriate dependencies:

pom.xml

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.10.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
</properties>

<dependencies>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
  </dependency>
  
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  
</dependencies>

To package the code into a JAR, we will make use of spring-boot-maven-plugin which is an excellent tool to manage packaging of the application itself into a JAR or a WAR. Here is how we can add into our dependency file:

pom.xml

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

Find the latest Maven dependencies on Maven Central.

Finally, to understand all the JARs which are added to the project when we added this dependency, we can run a simple Maven command which allows us to see a complete Dependency Tree for a project when we add some dependencies to it. Here is a command which we can use:

Check Dependency Tree

mvn dependency:tree

When we run this command, it will show us the following Dependency Tree:

Dependency Tree

[INFO] --------------------
[INFO] Building JCG-SpringBoot-example 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ JCG-SpringBoot-example ---
[INFO] com.javacodegeeks.example:JCG-SpringBoot-example:jar:1.0-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:1.5.10.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.5.10.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:1.5.10.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:1.5.10.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.5.10.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.1.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.1.11:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.25:compile
[INFO] |  |  |  \- org.slf4j:log4j-over-slf4j:jar:1.7.25:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.17:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:1.5.10.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-aop:jar:4.3.14.RELEASE:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.8.13:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:1.5.10.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat:tomcat-jdbc:jar:8.5.27:compile
[INFO] |  |  |  \- org.apache.tomcat:tomcat-juli:jar:8.5.27:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:4.3.14.RELEASE:compile
[INFO] |  +- org.hibernate:hibernate-core:jar:5.0.12.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.3.1.Final:compile
[INFO] |  |  +- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:jar:1.0.0.Final:compile
[INFO] |  |  +- org.javassist:javassist:jar:3.21.0-GA:compile
[INFO] |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  +- org.jboss:jandex:jar:2.0.0.Final:compile
[INFO] |  |  +- dom4j:dom4j:jar:1.6.1:compile
[INFO] |  |  \- org.hibernate.common:hibernate-commons-annotations:jar:5.0.1.Final:compile
[INFO] |  +- org.hibernate:hibernate-entitymanager:jar:5.0.12.Final:compile
[INFO] |  +- javax.transaction:javax.transaction-api:jar:1.2:compile
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:1.11.10.RELEASE:compile
[INFO] |  |  +- org.springframework.data:spring-data-commons:jar:1.13.10.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-orm:jar:4.3.14.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-context:jar:4.3.14.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-tx:jar:4.3.14.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-beans:jar:4.3.14.RELEASE:compile
[INFO] |  |  +- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] |  |  \- org.slf4j:jcl-over-slf4j:jar:1.7.25:compile
[INFO] |  \- org.springframework:spring-aspects:jar:4.3.14.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:1.5.10.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.5.10.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.5.27:compile
[INFO] |  |  |  \- org.apache.tomcat:tomcat-annotations-api:jar:8.5.27:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.5.27:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.5.27:compile
[INFO] |  +- org.hibernate:hibernate-validator:jar:5.3.6.Final:compile
[INFO] |  |  +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] |  |  \- com.fasterxml:classmate:jar:1.3.4:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.8.10:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.8.0:compile
[INFO] |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.8.10:compile
[INFO] |  +- org.springframework:spring-web:jar:4.3.14.RELEASE:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:4.3.14.RELEASE:compile
[INFO] |     \- org.springframework:spring-expression:jar:4.3.14.RELEASE:compile
[INFO] +- com.h2database:h2:jar:1.4.196:runtime
[INFO] \- org.springframework.boot:spring-boot-starter-test:jar:1.5.10.RELEASE:test
[INFO]    +- org.springframework.boot:spring-boot-test:jar:1.5.10.RELEASE:test
[INFO]    +- org.springframework.boot:spring-boot-test-autoconfigure:jar:1.5.10.RELEASE:test
[INFO]    +- com.jayway.jsonpath:json-path:jar:2.2.0:test
[INFO]    |  \- net.minidev:json-smart:jar:2.2.1:test
[INFO]    |     \- net.minidev:accessors-smart:jar:1.1:test
[INFO]    |        \- org.ow2.asm:asm:jar:5.0.3:test
[INFO]    +- junit:junit:jar:4.12:test
[INFO]    +- org.assertj:assertj-core:jar:2.6.0:test
[INFO]    +- org.mockito:mockito-core:jar:1.10.19:test
[INFO]    |  \- org.objenesis:objenesis:jar:2.1:test
[INFO]    +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO]    +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO]    +- org.skyscreamer:jsonassert:jar:1.4.0:test
[INFO]    |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO]    +- org.springframework:spring-core:jar:4.3.14.RELEASE:compile
[INFO]    \- org.springframework:spring-test:jar:4.3.14.RELEASE:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Noticed something? So many dependencies were added by just adding four dependencies to the project. Spring Boot collects all related dependencies itself and leave nothing for us in that matter. The biggest advantage is that all these dependencies are guranteed to be copatible with each other.

3. Gradle equivalent for the build file

Although Maven is an excellent build-system, if you prefer Gradle, here is the Gradle equivalent for the pom.xml build file:

build.gradle

buildscript {
	ext {
		springBootVersion = '1.5.10.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.javacodegeeks.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
	mavenCentral()
}


dependencies {
	compile('org.springframework.boot:spring-boot-starter-data-jpa')
	compile('org.springframework.boot:spring-boot-starter-web')
	runtime('com.h2database:h2')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

We have exhibited exactly the same dependencies and versions to provide an exact equivalent.

4. Project Structure

Before we move on and start working on the code for the project, let’s present here the projet structure we will have once we’re finished adding all the code to the project:

Spring Boot project Structure

We have divided the project into multiple packages so that the principle of separation of concern is followed and code remains modular.

5. Defining an Entity

Let’s look at how this can be done:

Person.java

package com.javacodegeeks.example.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private int age;

    //standard getters and setters
    
    @Override
    public String toString() {
        return String.format("Person{id=%d, name='%s', age=%d}", id, name, age);
    }
}

We omitted standard getters and setters for brevity but they are necessary to be made as Jackson uses them during Serialization and Deserialization of an Object.

The @Entity annotation marks this POJO as an object which will be managed by the Spring Data APIs and its fields will be treated as table columns (unless marked transient).

Finally, we added a custom implementation for the toString() method so that we can print-related data when we test our application.

6. Defining JPA Repository to access H2 Database

JPA provides us with a very simple way of defining a JPA Repository interface.

Before getting to know how to define a JPA Repository, we need to remember that each JPA interface is only made to interact with a single Entity of Database Table when JPA-related functionality is leveraged. We will understand this deeply if we have a look at the interface definition:

PersonRepository.java

package com.javacodegeeks.example.repository;

import com.javacodegeeks.example.model.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {  }

Although above interface definition is empty, we still have some points which we need to understand:

  • @Repository annotation marks this interface as a Spring Bean which is initialised on application startup. With this annotation, Spring takes care of managing exception database interaction throws gracefully
  • We used Person as a parameter to signify that this JPA interface will manage the Person Entity
  • Finally, we also passed the data type Long as a parameter. This signifies that the Person Entity contains a unique identifier which is for the type Long.

7. Making a RESTful Controller

A RESTful Controller where we expose the application’s data to a client. We will make use of several HTTP verbs like GET, POST, PUT and DELETE to support features associated with them.

To start, let’s define a PersonController class which is marked as @RestController. The @RestController annotation signals the Spring Container that any exceptions which are raised in this class are Ok to be passed on to the client itself. This is a different behaviour in comparison to a Repository bean.

PersonController.java

package com.javacodegeeks.example.controller;

import com.javacodegeeks.example.model.Person;
import com.javacodegeeks.example.repository.PersonRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class PersonController {

    private final Logger LOG = LoggerFactory.getLogger(getClass().getName());

    private final PersonRepository personRepository;

    @Autowired
    public PersonController(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    ...
}

Let’s provide some power to this Controller by adding specific APIs which perform actual operations.

7.1 Inserting data with POST

We will start by adding an API through which we can add data to the H2 Database. As this method accepts a Person Object in its @RequestBody, the JSON for Person object must be passed to the API in the request.

PersonController.java

@RequestMapping(value = "", method = RequestMethod.POST)
public Person createPerson(@RequestBody Person person) {
    LOG.info("Saving Person: {}.", person);
    personRepository.save(person);
    LOG.info("Person saved: {}.", person);
    return person;
}

We made use of the Personrepository bean to access a method which is pre-defined in the JpaRepository interface we defined earlier to deal with Person Entity.

In the coming section, we will try this API in a REST client, Postman.

7.2 Constructing a GET API

Now that we have an API to insert data into the Database, we can ow construct an API to Get the Person Object with its ID. Here, the personId is passed as a PathVariable:

PersonController.java

@RequestMapping(value = "/{personId}", method = RequestMethod.GET)
public Person getPerson(@PathVariable Long personId) {
    Person person = personRepository.findOne(personId);
    LOG.info("Got person from DB: {}.", person);
    return person;
}

To get all data which is present in the database, we will make yet another GET API to get all data from the database:

PersonController.java

@RequestMapping(value = "/all", method = RequestMethod.GET)
public List<Person> getAllPerson() {
    List<Person> persons = personRepository.findAll();
    LOG.info("Getting all Data: {}.", persons);
    return persons;
}

It just makes use of the JPA Repository’s findAll method to get all data of Person in DB and collect it into a List.

7.3 Updating data with PUT

We will now allow a client to update the existing data from the database. For this, we make use of save method again. When save method sees that the JSON object is populated with the id field, it first finds the Object with that ID and then it updates the fields provided.

PersonController.java

@RequestMapping(value = "", method = RequestMethod.PUT)
public Person editPerson(@RequestBody Person person) {
    LOG.info("Updating Person: {}.", person);
    personRepository.save(person);
    LOG.info("Person updated: {}.", person);
    return person;
}

7.4 Deleting data with DELETE

We will now have a final operation of deleting the data when a person’s ID is passed as a PathVariable:

PersonController.java

@RequestMapping(value = "/{personId}", method = RequestMethod.DELETE)
public void deletePerson(@PathVariable Long personId) {
    LOG.info("Deleting Person with ID {}.", personId);
    personRepository.delete(personId);
    LOG.info("Person deleted.");
}

8. Including a Request Interceptor

Although this is not strictly needed, we will include a Request Interceptor in this example as well. A Request Interceptor allows us to do something with the request object before it reaches the Controller. Once the Controller is done with the request and returns the response, that response object again passes through the Request Interceptor. Let’s define the Request Interceptor here:

JCGRequestInterceptor.java

package com.javacodegeeks.example.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class JCGRequestInterceptor extends HandlerInterceptorAdapter {

    private final Logger LOG = LoggerFactory.getLogger(getClass().getName());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        LOG.info("Incoming request.");
        return super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        LOG.info("Outgoing request.");
        super.postHandle(request, response, handler, modelAndView);
    }
}

9. Running the Project with Maven

Before we run the project, we need to define a main class for the project as well. Here is the main class definition with the Request Interceptor bean:

Running the Project

package com.javacodegeeks.example;

import com.javacodegeeks.example.interceptor.JCGRequestInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class BootApp {

    public static void main(String[] args) {
        SpringApplication.run(BootApp.class, args);
    }

    @Bean
    public JCGRequestInterceptor requestInterceptor() {
        return new JCGRequestInterceptor();
    }
}

Now that it is done, we can run our project. Running the application is easy with maven, just use the following command:

Running the Project

mvn spring-boot:run

Now that the project is running, we can use Postman tool to access APIs and see if they are working as expected.

10. Accessing APIs in Postman

We will start by inserting some data into the H2 Database wth the POST API we made:

Inserting Data into H2

Now, we can get this Person object with ID JPA assigned it, i.e. 1:

Get Object by ID

We can also try the Get All data API to see if this Object is returned or not:

Get All Data

Let’s update one of the field of the object we created:

Update Data

Finally, we try to delete the data by passing the ID in the URL as a PathVariable:

Delete Data


 

11. Including and Running Unit Tests

Any Spring application is incomplete without at least one unit-test case. In this example, we will include a single unit-test case which is production ready. Here it goes:

ControllerTests.java

package com.javacodegeeks.example;

import com.javacodegeeks.example.controller.PersonController;
import com.javacodegeeks.example.repository.PersonRepository;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
public class ControllerTest {

    private MockMvc mockMvc;

    @InjectMocks
    private PersonController controller;

    @Mock
    private PersonRepository repository;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        controller = new PersonController(repository);
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @Test
    public void getAllPerson_Pass() throws Exception {
        mockMvc.perform(
                get("/all"))
                .andExpect(status().is2xxSuccessful());
    }
}

We did several things in above sample test-case. Let’s understand these one at a time:

  • I marked this class a @RunWith(SpringJUnit4ClassRunner.class) annotation which provides the test case with a runner.
  • When we use Mockito, we need to enable its annotations. This is done by the initMocks(...) method call.
  • We used the mockMvc object’s get nethod to check the status code returned by the /all GET API and compared it to any 2XX status code.

When we ran this test-case in IntelliJ, we see the following output:

Running Spring Boot test case

Seeing a test-case with green on it is an excellent feeling, isn’t it?

12. Using Spring Boot CLI

Spring Boot Command Line Interface is a software which we can use to run and test Spring Boot applications from the command line. Spring Boot CLI internally makes use of Spring Boot starters and AutoConfiguration components to collect the required dependencies and run the application.

To start using CLI, quickest way is to download the ZIP and change into the bin directory and check the command as shown:

Downloading and using Spring CLI

To use Spring CLI from anywhere, add this JAR to your PATH.

Now, let us quickly show how powerful Spring Boot CLI is. CLI can be used to execute nad run single Groovy-based scripts without providing any dependencies. We will make a single file and name it “HelloWorld.groovy”. Do take note of the file extension here as it is necessary that the file is of type groovy only. In the file, we will put simple code fragment:

HelloWorld.groovy

@RestController
class HelloWorld {
  @RequestMapping("/")
  String hello() {
    "Hello World!"
  }
}

Now, move to the folder where you made this script and run the following command:

HelloWorld.groovy

spring run HelloWorld.groovy

This command will run the mentioned Grovvy script on the default port 8080. Let’s try visiting this port now:

Hello World from CLI

Wasn’t it easy? Note that there were no dependencies, no configuration and no import statements involved when we wrote the Groovy script. This is because this responsibility is taken by Spring Boot Core Components, Groovy Compiler (groovyc) and Groovy Grape (Groovy’s JAR Dependency Manager).

13. Conclusion

In this lesson, we looked at how easy and quick it is to construct a production-grade API with Spring Boot. We managed to make some fully-functional APIs which talked to the database and running a unit-test case as well.

We tried the APIs in a production-grade RESTful client, Postman and saw our APIs responding to calls as expected. The H2 Database we used in this lesson is very easy to be replaced with a real database like MySQL, MongoDB or any other database.

14. Download the Source Code

In this example, we looked at how we can get started with a basic Spring Boot project.

Download
You can download the full source code of this example here: JCG-SpringBoot-Example

Shubham Aggarwal

Shubham is a Java EE Engineer with about 3 years of experience in building quality products with Spring Boot, Spring Data, AWS, Kafka, PrestoDB.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Alexander Mitenko
Alexander Mitenko
6 years ago

Hello!

But why not UI for crud?

Thanx,
Alex.

Alexander Mitenko
Alexander Mitenko
6 years ago

It’s will be amazing if You link these articles both.
There are many tutorials about REST CRUD without UI (most of them are worst with no abstraction – there are couple of boilerplate for each entity), but UI for REST CRUD I can’t find that are good.

Thanx, I’ll wait eagerly :)

Martin O'Shea
Martin O'Shea
6 years ago

This is a userful tutorial for Spring Boot. But can you please explain the use of the JCGRequestInterceptor class? For example, at run-time I would expect to see the message “Incoming request.” in IntelliJ before the messages hardcoded into method createPerson in the PersonController class, but there is no trace of them.

Back to top button