[JAVA] Minimum configuration sample of RESTful API in Jersey + Spring Framework

This is a continuation of this article.

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

Purpose

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

If you look at Java Spring, there are only examples of ** Spring Boot **, and it's probably easier to use Boot, but it may not be usable due to adult circumstances, so ** Spring Framework ** is recommended. Aim for the sample used.

goal

Hello World-like program works with the minimum configuration of Jersey + Spring Framework.

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

Reference site

Added to my own Jersey project and Spring MVC Hello World project I tried, but when I searched for information, many of them used Spring Boot, so it was quite difficult.

I finally found this site.

Jersey + Spring integration example https://mkyong.com/webservices/jax-rs/jersey-spring-integration-example/

It's information about 10 years ago, and it's quite old, but the project that is up has just worked. (* However, in the environment of JDK9 or later, it is necessary to add JAXB related dependencies) So, once I aimed to upgrade to the latest version from there, I succeeded, so I will make a note of the final form.

Project setting procedure

1. Create a new Maven project

Create a new Maven project in IntelliJ IDEA. Please refer to here for the procedure.

2. Set dependencies

The pom.xml looks like this: What is different from the reference site is that each version is the latest version, and the package moved accordingly is changed. Also, since JAXB related has been deleted since JDK9, its dependency is also added.

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>RESTfulExample</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>RESTfulExample Maven Webapp</name>
    <url>http://maven.apache.org</url>

    <dependencies>

        <!-- Jersey -->
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-server -->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-server</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
            <version>${jersey.version}</version>
        </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>

    </dependencies>

    <build>
        <finalName>RESTfulExample</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <inherited>true</inherited>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <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>

3. Create a transaction class

It is a class to be injected by Bean. It seems to be a method to create an interface and implement it, but there is no problem even if it is not so. One of the selling points of Spring is ** DI ** (dependency injection), so if you think about mocking it later when testing, it's better to do it from the beginning.

Interface class

transaction/TransactionBo.java


public interface TransactionBo {
    String save();
}

Implementation class

transaction/impl/TransactionBoImpl.java


public class TransactionBoImpl implements TransactionBo {
    public String save() {
        return "Jersey + Spring example";
    }
}

4. Create a service class

Have Spring DI the TransactionBoImpl, but don't use the @ Autowired annotation. Use constructor injection. (Constructor injection seems to be recommended. For details, see the reference site at the end)

rest/PaymentService.java


import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

import org.springframework.stereotype.Component;
import my.example.jerseyspring.transaction.TransactionBo;

@Component
@Path("/payment")
public class PaymentService {

    final TransactionBo transactionBo;

    public PaymentService(TransactionBo transactionBo) {
        this.transactionBo = transactionBo;
    }

    @GET
    @Path("/mkyong")
    public Response savePayment() {
        String result = transactionBo.save();
        return Response.status(200).entity(result).build();
    }
}

5.applicationContext.xml

This is a file to set the bean class to be injected. Place it under the src / main / resources folder. Change the package name and bean class to the ones you created.

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.transaction.impl.TransactionBoImpl" />
 
</beans>
  1. web.xml

Create web.xml undersrc / main / webapp / WEB-INF /and do as follows.

web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
		 version="2.4">
	<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-serlvet</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-serlvet</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>

</web-app>

The only difference from the reference site is that <servlet-class> is a Jersey class. Also, change the package name to the one you created.

6.index.jsp

Place it under the webapp folder. You don't have to, but if you don't, when you deploy to Tomcat and start it, 404 page will be displayed and it will be unpleasant (^^;

index.jsp


<html>
<body>
    <h2>Jersey + Spring RESTful Web Application!</h2>
    <p><a href="rest/payment/mkyong">Jersey resource</a>
</body>
</html>

Run

If you set the execution settings in Tomcat and start it, you should see the following screen.

jersey_spring_1.png

Clicking on the Jersey Resource link will hit the GET method of rest / payment / mkyong and display the return value.

jersery_spring_2.png

You should also succeed with curl and Postman.

test

I will write a test because it is a big deal.

1. Simple test

(1) Add dependency

Add a dependency for testing. I made it the same as when this article.

First, add plugins under <build> / <plugins>.

pom.xml


            <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>

Then add it to <dependencies>.

pom.xml


       <!--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>

(2) Implementation of test class

Basically, just follow what you did in Jersey Basic Sample.

src/test/java/my/example/jerseysample/PaymentServiceTest.java


package my.example.jerseyspring.rest;

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 javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;

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

class PaymentServiceTest extends JerseyTest {

    @Override
    protected Application configure() {
        return new ResourceConfig(PaymentService.class);
    }

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

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

    @Test
    public void get(){
        final Response response = target("/payment/mkyong").request().get();

        String content = response.readEntity(String.class);
        assertThat(content).isEqualTo("Jersey + Spring example");
    }
}

2. Try changing the implementation of TransactionBo

Since it's a big deal, I tried using Spring DI to see if it could be replaced with a mock.

(1) Add dependency

In order to run Spring DI, put Spring Test framework.

pom.xml


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

(2) Create a mock bean class

Create the following class under src / test / java / my / example / transaction / immpl.

TransactionBoMock.java


public class TransactionBoMock implements TransactionBo {
    public String save() {
        return "This is mock.";
    }
}

We will define this as a bean, but since it is for testing, we will create ʻapplicationContext.xmlfor testing. Place it undersrc / test / resources`.

src/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.transaction.impl.TransactionBoMock"/>

</beans>

I am changing the implementation class to TransactionBoMock.

(3) Modification of test class

The writing style corresponds to Junit5.

PaymentServiceTest.java


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

Just add two annotations! If you do, you'll get an error because you haven't changed the comparison string yet.

org.opentest4j.AssertionFailedError: 
Expecting:
 <"This is mock.">
to be equal to:
 <"Jersey + Spring example">
but was not.
Expected :Jersey + Spring example
Actual   :This is mock.
assertThat(content).isEqualTo("Jersey + Spring example");

Change the above string to " This is mock. " And you're done.

Impressions

It takes a long time to get here, but ...

When it's done, it's relatively simple. If it becomes so simple, it will be easier to understand that it is necessary to define in ʻapplicationContext.xml` to get DI.

When it comes to installing Spring MVC, it seems that setting Controller and dispatcher is difficult again, but since it is planned only for RESTful API, this is fine once. (I don't feel like studying Spring MVC so much)

Next, I would like to create a more decent API and merge it with Multi-modular project only with Jersey if possible.

The projects so far are uploaded below.

https://github.com/le-kamba/spring-jersey-sample/tree/simple_base

Other reference sites

The information was old and it was for Boot, so it was not a direct reference, but it is a site that got various hints in detail.

REST API with Jersey and Spring https://www.baeldung.com/jersey-rest-api-with-spring

Programmers: Jersey with a Side of Spring http://pilotprogrammer.com/archive/2019/01/programmers-jersey-with-a-side-of-spring/

Why Constructor Injection is recommended over Field Injection in Spring http://pppurple.hatenablog.com/entry/2016/12/29/233141

How to get DI to work when testing with JUnit https://wikiwiki.jp/webapp/Spring/JUnit

JUnit5 @RunWith https://www.baeldung.com/junit-5-runwith

Recommended Posts

Minimum configuration sample of RESTful API in Jersey + Spring Framework
RESTful API multi-module sample in IntelliJ + Jersey + Spring Framework
Spring Boot: Restful API sample project
Minimum configuration of Ruby REST-like API framework "grape" (with RSpec / Heroku compatible)
Put the file in the properties of string in spring xml configuration
How to set and use profile in annotation-based Configuration in Spring framework
Spring Framework 5.0 Summary of major changes
Java --Jersey Framework vs Spring Boot
spring boot access authorization RESTful API
Implement REST API in Spring Boot
Settings when calling API using CSRF measures of Spring Security in JMeter
About the initial display of Spring Framework
Major changes in Spring Framework 5.0 core functionality
Features of spring framework for java developers
Maven configuration problem in Spring pom.xml in Eclipse
Sample code to call Yahoo! Shopping Product Search (v3) API in Spring RestTemplate