[JAVA] RESTful API multi-module sample in IntelliJ + Jersey + Spring Framework

This is a continuation of the following article.

Minimum configuration sample of RESTful API in Jersey + Spring Framework https://qiita.com/kasa_le/items/59ebd6b5490945dd5580

Multi-module configuration with Maven (Jersey RESTful) https://qiita.com/kasa_le/items/db0d84e3e868ff14bc2b

This time, it's like merging the projects created in the above two articles. However, I will explain by creating a project from scratch.

Purpose

Implement RESTful API with ** Jersey ** and ** Spring Framework ** (not Boot).

goal

--Jersey + Spring Framework works RESTful API (CRUD). --DI can be done with a multi-module configuration.

Environment etc.

Tools etc. Version etc.
MacbookPro macOS Mojave 10.14.5
IntelliJ IDEA Ultimate 2019.3.3
Java AdoptOpenJDK 11
apache maven 3.6.3
Jersey 2.30.1
JUnit 5.6.0
Tomcat apache-tomcat-8.5.51
Postman 7.19.1
Spring Framework 5.2.4-RELEASE

Project settings

In IntelliJ, create a new Maven project.

1. Root pom

Basically, you can copy the dependency and plugin settings from the Jersey + Spring Framework minimum configuration project.

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>my.example.jerseyspring</groupId>
    <artifactId>jersey-spring-restful</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>Jersey and Spring Framework RESTfulAPI Sample</name>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <inherited>true</inherited>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <additionalClasspathElements>
                        <additionalClasspathElement>src/test/java/</additionalClasspathElement>
                    </additionalClasspathElements>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.7.1</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>3.0.0-M4</version>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.glassfish.jersey</groupId>
                <artifactId>jersey-bom</artifactId>
                <version>${jersey.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Jersey -->
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-server -->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
        </dependency>

        <!-- Spring dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- Jersey + Spring -->
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.ext/jersey-spring5 -->
        <dependency>
            <groupId>org.glassfish.jersey.ext</groupId>
            <artifactId>jersey-spring5</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <!--JAXB removed from JDK9-->
        <!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.activation/activation -->
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.2</version>
        </dependency>

        <!--test-->
        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.test-framework.providers/jersey-test-framework-provider-grizzly2 -->
        <dependency>
            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
            <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
            <version>2.30.1</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.15.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <properties>
        <spring.version>5.2.4.RELEASE</spring.version>
        <jersey.version>2.30.1</jersey.version>
        <junit.jupiter.version>5.6.0</junit.jupiter.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

2. Creating a submodule

(1) Delete the root src folder

Delete it manually.

(2) Addition of submodule

From the IntelliJ menu [File]-[New]-[Module ...], add the following two in the Maven project.

The name of the submodule is arbitrary. Don't forget to set ** Parent **. (Please select the root project.)

Then the <packaging> of the parent pom.xml should change to pom. If it has not changed, fix it manually.

root/pom.xml


    <groupId>my.example.jerseyspring</groupId>
    <artifactId>jersey-spring-restful</artifactId>
    <packaging>pom</packaging>

Each pom.xml in the submodule should look like this:

repository/pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jersey-spring-restful</artifactId>
        <groupId>my.example.jerseyspring</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>repository</artifactId>
    <packaging>jar</packaging>
</project>

serverapi/pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jersey-spring-restful</artifactId>
        <groupId>my.example.jerseyspring</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>serverapi</artifactId>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>my.example.jerseyspring</groupId>
            <artifactId>repository</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- for spring test-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

The serverapi module is included in the <test> scope because Spring Test is required for Junit test. However, if you need it in other submodules, you can put it in the root pom.xml.

Neither of them has specified their own version. I want to use the parent version as it is. It is a policy not to manage the version for each submodule. If you need to, specify the version for each with the <version> tag.

(3) Folder structure

The current folder structure should look like this.

$ tree
.
├── SpringJerseyRest.iml
├── pom.xml
├── repository
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   └── resources
│       └── test
│           └── java
└── serverapi
    ├── pom.xml
    └── src
        ├── main
        │   ├── java
        │   └── resources
        └── test
            └── java

2. Repository module settings

(1) Model class

Create the following classes in the package my.example.jerseyspring.repository.models.

Employee.java


package my.example.jerseyspring.repository.models;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Employee {
    private int id;
    private String firstName;

    public Employee() {
    }

    public Employee(int id, String firstName) {
        this.id = id;
        this.firstName = firstName;
    }

    public int getId() {
        return id;
    }

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

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

(2) Repository class

Create the ʻEmployeeRepository class in the package my.example.jerseyspring.repository`.

EmployeeRepository.java


package my.example.jerseyspring.repository;


import my.example.jerseyspring.repository.exceptions.DuplicateIdException;
import my.example.jerseyspring.repository.exceptions.EmployeeNameNotFoundException;
import my.example.jerseyspring.repository.exceptions.EmployeeNotFoundException;
import my.example.jerseyspring.repository.models.Employee;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;

@Repository
public class EmployeeRepository {

    private List<Employee> employeeList;

    public EmployeeRepository() {
        employeeList = new ArrayList<>();
        employeeList.add(new Employee(3, "Cupcake"));
        employeeList.add(new Employee(4, "Donuts"));
        employeeList.add(new Employee(5, "Eclair"));
        employeeList.add(new Employee(8, "Froyo"));
        employeeList.add(new Employee(9, "Gingerbread"));
    }

    public List<Employee> selectAll() {
        return employeeList;
    }

    public Employee select(int id) {
        for (Employee employee : employeeList) {
            if (employee.getId() == id) {
                return employee;
            }
        }
        throw new EmployeeNotFoundException();
    }

    public synchronized void insert(int id, String firstName) {
        try {
            select(id);
        } catch (EmployeeNotFoundException e) {
            //If not, you can add
            employeeList.add(new Employee(id, firstName));
            return;
        }
        //Cannot be added if the same ID exists
        throw new DuplicateIdException();
    }

    public synchronized void update(int id, String firstName) {
        Employee employee = select(id);
        employee.setFirstName(firstName);
    }

    public synchronized void delete(int id) {
        Employee employee = select(id);
        employeeList.remove(employee);
    }

    public List<Employee> search(String name) {
        List<Employee> list = new ArrayList<>();
        for (Employee employee : employeeList) {
            if (employee.getFirstName().contains(name)) {
                list.add(employee);
            }
        }
        if (list.size() > 0) return list;
        throw new EmployeeNameNotFoundException(name);
    }
}

In the previous Multi-module sample, I put the singleton implementation by myself, but the DI of Spring Framework is basically singleton. , Removing its implementation.

(3) Exception class

ʻCreate each exception class used in the EmployeeRepositoryclass. The package isrepository.exceptions`.

Example) ʻEmployeeNotFoundException` class

EmployeeNotFoundException.java


package my.example.jerseyspring.repository.exceptions;

public class EmployeeNotFoundException extends RuntimeException {
    public EmployeeNotFoundException() {
        super("The Employee with that ID cannot be found.");
    }
}

Create other classes in the same way.

Also, I will bring the transacation package used in the Jersey + Spring minimum configuration project. The repository.transaction package is fine.

(4) Junit test class

Create a test class for the exception class.

As an example, let's take the case of ʻEmployeeNotFoundException`.

EmployeeNotFoundExceptionTest.java


package my.example.jerseyspring.repository.exceptions;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class EmployeeNotFoundExceptionTest {

    @Test
    void getMessage() {
        EmployeeNotFoundException e = new EmployeeNotFoundException();
        assertThat(e.getMessage()).isEqualTo("The Employee with that ID cannot be found.");
    }
}

Let's run the Junit test from IntelliJ and check that it passes.

(5) Final configuration

In the end, you should have a folder structure like this:

$ tree
.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── my
    │   │       └── example
    │   │           └── jerseyspring
    │   │               └── repository
    │   │                   ├── EmployeeRepository.java
    │   │                   ├── exceptions
    │   │                   │   ├── DuplicateIdException.java
    │   │                   │   ├── EmployeeNameNotFoundException.java
    │   │                   │   └── EmployeeNotFoundException.java
    │   │                   ├── models
    │   │                   │   └── Employee.java
    │   │                   └── transaction
    │   │                       ├── TransactionBo.java
    │   │                       └── impl
    │   │                           └── TransactionBoImpl.java
    │   └── resources
    └── test
        └── java
            └── my
                └── example
                    └── jerseyspring
                        └── repository
                            └── exceptions
                                ├── DuplicateIdExceptionTest.java
                                ├── EmployeeNameNotFoundExceptionTest.java
                                └── EmployeeNotFoundExceptionTest.java

19 directories, 11 files

3. Serverapi module settings

(1) Service class

Create a ʻEmployeeServiceclass in the packagemy.example.jerseyspring.rest`.

EmployeeService.java


package my.example.jerseyspring.rest;

import my.example.jerseyspring.repository.EmployeeRepository;
import my.example.jerseyspring.repository.models.Employee;
import org.springframework.stereotype.Service;

import javax.ws.rs.*;
import javax.ws.rs.core.*;
import java.util.List;

@Service
@Path("/employees")
public class EmployeeService {

    final EmployeeRepository employeeRepository;

    public EmployeeService(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    @Context
    UriInfo uriInfo;

    @GET
    @Path("/all")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public List<Employee> getAll() {
        return employeeRepository.selectAll();
    }

    @GET
    @Path("/{id}")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public Employee getEmployee(@PathParam("id") int id) {
        return employeeRepository.select(id);
    }

    @GET
    @Path("/search")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public List<Employee> searchEmployee(@QueryParam("name") String name) {
        return employeeRepository.search(name);
    }

    @POST
    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public Response addEmployee(Employee employee) {
        employeeRepository.insert(employee.getId(), employee.getFirstName());

        UriBuilder builder = uriInfo.getAbsolutePathBuilder();
        builder.path(String.valueOf(employee.getId()));
        return Response.created(builder.build()).build();
    }

    @PUT
    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public Response updateEmployee(Employee employee) {
        employeeRepository.update(employee.getId(), employee.getFirstName());
        //If you create a new one, you need to return created, but in this sample it is an error, so it always returns ok.
        return Response.ok().build();
    }

    @DELETE
    @Path("/{id}")
    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public Response deleteEmployee(@PathParam("id") int id) {
        employeeRepository.delete(id);
        //To return the status of Entity, return ok.
        //If it is accepted but the process is not finished(Just got on the queue, etc.)Returns accepted
        //In this sample, only the deletion is completed and the corresponding content is exhausted.
        return Response.noContent().build();
    }
}

Also, I will bring the PaymentService class used in the Jersey + Spring minimum configuration project.

(2) Exception handler class

Create a class for exception mapping in the rest.handlers package. Required for each exception class created in the repository module. For exception mapping, click here (https://qiita.com/kasa_le/items/3c7e7a426acf846ee64e#%E4%BE%8B%E5%A4%96%E3%83%8F%E3%83%B3%E3 Please refer to% 83% 89% E3% 83% AA% E3% 83% B3% E3% 82% B0).

Example) ʻEmployeeNotFoundException` class mapper

NotFoundExceptionHandler.java


package my.example.jerseyspring.rest.handler;


import my.example.jerseyspring.repository.exceptions.EmployeeNotFoundException;

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class NotFoundExceptionHandler implements ExceptionMapper<EmployeeNotFoundException> {

    public Response toResponse(EmployeeNotFoundException ex) {
        return Response.status(Response.Status.NOT_FOUND).build();
    }
}

(3) Bean settings

Create ʻapplicationContext.xml under src / main / resources` and make it as follows.

applicationContext.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="my.example.jerseyspring"/>

    <bean id="transactionBo" class="my.example.jerseyspring.repository.transaction.impl.TransactionBoImpl"/>

    <bean id="employeeRepository" class="my.example.jerseyspring.repository.EmployeeRepository"/>

</beans>

(4) webapp folder settings

Create a src / main / webapp folder.

--Create web.xml under webapp / WEB-INF and make it the following content

web.xml


<?xml version="1.0" encoding="UTF-8"?>
<!-- This web.xml file is not required when using Servlet 3.0 container,
     see implementation details http://jersey.java.net/nonav/documentation/latest/jax-rs.html -->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <display-name>Restful Web Application</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>jersey-servlet</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>my.example.jerseyspring</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>jersey-servlet</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>
</web-app>

--Create ʻindex.jsp under webapp / `and make it as follows

index.jsp


<html>

<body>
<h2>Jersey + Spring RESTful Web Application!</h2>
<p><a href="rest/payment/mkyong">Jersey resource</a>
    <br>
<p><a href="rest/employees/all">All Employee List</a>
<p><a href="rest/employees/3">get id=3 employee</a>

</body>

</html>

(5) Junit test class

ʻCreate a EmployeeServiceTestclass and aPaymentServiceTest` class to write a test.

EmployeeServiceTest.java


package my.example.jerseyspring.rest;

import my.example.jerseyspring.repository.EmployeeRepository;
import my.example.jerseyspring.repository.models.Employee;
import my.example.jerseyspring.rest.handler.DuplicateExceptionHandler;
import my.example.jerseyspring.rest.handler.NameNotFoundExceptionHandler;
import my.example.jerseyspring.rest.handler.NotFoundExceptionHandler;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:/applicationContext.xml")
public class EmployeeServiceTest extends JerseyTest {

    @Autowired
    EmployeeRepository employeeRepository;

    @Override
    protected Application configure() {
        return new ResourceConfig(EmployeeService.class)
                .register(DuplicateExceptionHandler.class)
                .register(NameNotFoundExceptionHandler.class)
                .register(NotFoundExceptionHandler.class)
                .register(this);
    }

    @BeforeEach
    @Override
    public void setUp() throws Exception {
        super.setUp();
    }

    @AfterEach
    @Override
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @ParameterizedTest
    @ValueSource(strings = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public void getAll(String mediaType) {
        final Response response = target("/employees/all").request().accept(mediaType).get();
        assertThat(response.getHeaderString("Content-Type"))
                .isEqualTo(mediaType);

        List<Employee> content = response.readEntity(new GenericType<>() {
        });
        assertThat(content.size()).isEqualTo(5);
        assertThat(content.get(0)).isEqualToComparingFieldByField(new Employee(3, "Cupcake"));
        assertThat(content.get(1)).isEqualToComparingFieldByField(new Employee(4, "Donuts"));
        assertThat(content.get(2)).isEqualToComparingFieldByField(new Employee(5, "Eclair"));
        assertThat(content.get(3)).isEqualToComparingFieldByField(new Employee(8, "Froyo"));
        assertThat(content.get(4)).isEqualToComparingFieldByField(new Employee(9, "Gingerbread"));
    }

    @ParameterizedTest
    @MethodSource("getParamProvider")
    public void getEmployee(int id, String mediaType) {
        String urlPath = String.format("/employees/%d", id);
        final Response response = target(urlPath).request().accept(mediaType).get();
        assertThat(response.getHeaderString("Content-Type"))
                .isEqualTo(mediaType);

        Employee employee = response.readEntity(Employee.class);
        Employee expect = employeeRepository.select(id);
        assertThat(employee).isEqualToComparingFieldByField(expect);
    }

    static Stream<Arguments> getParamProvider() {
        return Stream.of(
                Arguments.of(3, MediaType.APPLICATION_JSON),
                Arguments.of(4, MediaType.APPLICATION_JSON),
                Arguments.of(5, MediaType.APPLICATION_JSON),
                Arguments.of(8, MediaType.APPLICATION_JSON),
                Arguments.of(9, MediaType.APPLICATION_JSON),
                Arguments.of(3, MediaType.APPLICATION_XML),
                Arguments.of(4, MediaType.APPLICATION_XML),
                Arguments.of(5, MediaType.APPLICATION_XML),
                Arguments.of(8, MediaType.APPLICATION_XML),
                Arguments.of(9, MediaType.APPLICATION_XML)
        );
    }

    @ParameterizedTest
    @ValueSource(strings = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public void searchEmployee(String mediaType) {
        final Response response = target("/employees/search")
                .queryParam("name", "a")
                .request()
                .accept(mediaType)
                .get();
        assertThat(response.getHeaderString("Content-Type"))
                .isEqualTo(mediaType);

        List<Employee> content = response.readEntity(new GenericType<>() {
        });
        assertThat(content.size()).isEqualTo(3);
        assertThat(content.get(0)).isEqualToComparingFieldByField(new Employee(3, "Cupcake"));
        assertThat(content.get(1)).isEqualToComparingFieldByField(new Employee(5, "Eclair"));
        assertThat(content.get(2)).isEqualToComparingFieldByField(new Employee(9, "Gingerbread"));
    }

    @ParameterizedTest
    @MethodSource("postRawProvider")
    public void addEmployee(int id, String bodyRaw, String mediaType) {

        final Response response = target("/employees").request()
                .post(Entity.entity(bodyRaw, mediaType));
        assertThat(response.getStatus()).isEqualTo(201);
        assertThat(response.getHeaderString("Location"))
                .isEqualTo("http://localhost:9998/employees/" + id);
    }

    static Stream<Arguments> postRawProvider() {
        final String json = "{\"firstName\":\"Honeycomb\",\"id\":11}";
        final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<employee><firstName>KitKat</firstName><id>19</id></employee>";
        return Stream.of(
                Arguments.of(11, json, MediaType.APPLICATION_JSON),
                Arguments.of(19, xml, MediaType.APPLICATION_XML)
        );
    }

    @ParameterizedTest
    @MethodSource("putRawProvider")
    public void updateEmployee(int id, String bodyRaw, String mediaType) {
        final Response response = target("/employees").request()
                .put(Entity.entity(bodyRaw, mediaType));
        assertThat(response.getStatus()).isEqualTo(200);

        Employee employee = target("/employees/" + id).request().get(Employee.class);
        Employee expected = employeeRepository.select(id);
        assertThat(employee).isEqualToComparingFieldByField(expected);
    }

    static Stream<Arguments> putRawProvider() {
        final String json = "{\"firstName\":\"Frozen yogurt\",\"id\":8}";
        final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<employee><firstName>Cup Cake</firstName><id>3</id></employee>";
        return Stream.of(
                Arguments.of(8, json, MediaType.APPLICATION_JSON),
                Arguments.of(3, xml, MediaType.APPLICATION_XML)
        );
    }

    @Test
    public void deleteEmployee() {
        final Response response = target("/employees/9")
                .request().delete();
        assertThat(response.getStatus()).isEqualTo(204);
    }

    @Test
    public void exception_selectEmployee() {
        final Response response = target("/employees/1").request().get();
        assertThat(response.getStatus()).isEqualTo(404);
    }

    @Test
    public void exception_searchEmployee() {
        final Response response = target("/employees/search?name=android").request().get();
        assertThat(response.getStatus()).isEqualTo(404);
    }

    @ParameterizedTest
    @MethodSource("putRawProvider")
    public void exception_addEmployee(int id, String bodyRaw, String mediaType) {
        final Response response = target("/employees").request()
                .post(Entity.entity(bodyRaw, mediaType));
        assertThat(response.getStatus()).isEqualTo(409);
    }

    @ParameterizedTest
    @MethodSource("putExceptionProvider")
    public void exception_updateEmployee(int id, String bodyRaw, String mediaType) {
        final Response response = target("/employees").request()
                .put(Entity.entity(bodyRaw, mediaType));
        assertThat(response.getStatus()).isEqualTo(404);
    }

    static Stream<Arguments> putExceptionProvider() {
        final String json = "{\"firstName\":\"Lollipop\",\"id\":21}";
        final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<employee><firstName>Jelly Bean</firstName><id>17</id></employee>";
        return Stream.of(
                Arguments.of(21, json, MediaType.APPLICATION_JSON),
                Arguments.of(3, xml, MediaType.APPLICATION_XML)
        );
    }

    @Test
    public void exception_deleteEmployee() {
        final Response response = target("/employees/1").request().get();
        assertThat(response.getStatus()).isEqualTo(404);
    }
}

In Multi-module sample, the part where the Sington object was acquired as ʻEmployeeRepository.getInstanceis to the member variable declared by@ Autowired. Access and change. With the @Autowiredannotation, Spring will DI for you, but in fact it seems that different instances will be created for the person who actually works with the service and the person who tests (it should be a singleton, but probably the test). I've seen an article saying that it's okay to doResourceConfig # register (this)` (because the context is different from the application context). However, isn't it a bug that works with this? Because there is a warning message? I don't know if it's correct or not. https://stackoverflow.com/questions/34453448/how-to-access-spring-bean-from-jerseytest-subclass

This time, the repository class isn't the main subject in the first place, it's just providing temporary data access, so I'm happy with this. However, please note that it may stop working when the version of Spring or Jersey goes up.

The PaymentServiceTest class is also brought from Minimum configuration sample. Also, bring in the TransactionBoMock class created for the DI mock replacement test.

Then put ʻapplicationContext.xml in the resourcesfolder of thetest` folder so that you can replace the bean, and do as follows.

test/resources/applicationContext.xml


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="my.example.jerseyspring"/>

    <bean id="transactionBo" class="my.example.jerseyspring.repository.transaction.impl.TransactionBoMock"/>
    <bean id="employeeRepository" class="my.example.jerseyspring.repository.EmployeeRepository"/>

</beans>

Since EmployeeRepository does not have an interface, I will not replace it this time, but originally, I should use a Mocked class for testing for the original implementation that acquires data from DB etc.

(6) Final configuration

The structure of the serverapi folder should look like this:

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── my
    │   │       └── example
    │   │           └── jerseyspring
    │   │               └── rest
    │   │                   ├── EmployeeService.java
    │   │                   ├── PaymentService.java
    │   │                   └── handler
    │   │                       ├── DuplicateExceptionHandler.java
    │   │                       ├── NameNotFoundExceptionHandler.java
    │   │                       └── NotFoundExceptionHandler.java
    │   ├── resources
    │   │   └── applicationContext.xml
    │   └── webapp
    │       ├── WEB-INF
    │       │   └── web.xml
    │       └── index.jsp
    └── test
        ├── java
        │   └── my
        │       └── example
        │           └── jerseyspring
        │               ├── repository
        │               │   └── transaction
        │               │       └── impl
        │               │           └── TransactionBoMock.java
        │               └── rest
        │                   ├── EmployeeServiceTest.java
        │                   └── PaymentServiceTest.java
        └── resources
            └── applicationContext.xml

21 directories, 13 files

4. Run, test

(1)JUnitTest Run JUnitTest to see if all the tests pass.

$ mvn clean test

(2) Operation check

Set up and deploy Tomcat.

You should see a page like the one below, click the link and you should see the API return value.

jersey-spring-restful.png

If you want to check the input / output of POST system or Json / Xml, please check using Curl or Postman.

The projects so far are uploaded below. https://github.com/le-kamba/spring-jersey-sample/tree/spring_jersey

Impressions

I was able to do it smoothly without getting hooked except for the singleton part.

By the way, the folder structure was put out by putting the ** tree ** command with brew on mac. I had to do it from the installation of Homebrew in the first place. (Did you not put it in?)

#Homebrew installation
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

#installation of tree
$ brew install tree

reference

What's the difference between Spring DI and new? https://qiita.com/uqichi/items/5f59817beb3dff9c0c1e

Recommended Posts

RESTful API multi-module sample in IntelliJ + Jersey + Spring Framework
Minimum configuration sample of RESTful API in Jersey + Spring Framework
Spring Boot: Restful API sample project
Java --Jersey Framework vs Spring Boot
spring boot access authorization RESTful API
Implement REST API in Spring Boot
Launch (old) Spring Boot project in IntelliJ
Create Java Spring Boot project in IntelliJ
Major changes in Spring Framework 5.0 core functionality
Sample code to call Yahoo! Shopping Product Search (v3) API in Spring RestTemplate
I made a function to register images with API in Spring Framework. Part 1 (API edition)