This is a continuation of this article.
Multi-module configuration with Maven (Jersey RESTful) https://qiita.com/kasa_le/items/db0d84e3e868ff14bc2b
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.
Hello World-like program works with the minimum configuration of Jersey + Spring Framework.
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 |
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.
Create a new Maven project in IntelliJ IDEA. Please refer to here for the procedure.
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>
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";
}
}
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>
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>
If you set the execution settings in Tomcat and start it, you should see the following screen.
Clicking on the Jersey Resource
link will hit the GET
method of rest / payment / mkyong
and display the return value.
You should also succeed with curl and Postman.
I will write a test because it is a big deal.
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>
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");
}
}
Since it's a big deal, I tried using Spring DI to see if it could be replaced with a mock.
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>
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 under
src / 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
.
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.
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
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
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