Jersey+Spring FrameworkでRESTfulAPIの最小構成サンプル

こちらの記事の続きです。

Mavenでマルチモジュール構成にする(Jersey RESTful)
https://qiita.com/kasa_le/items/db0d84e3e868ff14bc2b

# 目的
**Jsersey**と**Spring Framework**(not Boot)でRESTfulAPIを実装する。

`Java Spring`で調べると**Spring Boot**の例ばかりで、多分Boot使ったほうがいろいろ楽なんでしょうが、大人の事情で使えない(かもしれない)ので、**Spring Framework**の方を使ったサンプルを目指します。

# ゴール
Jersey+Spring Frameworkの最小構成のプログラムでHello World的なものが動く。

# 環境など

|ツールなど|バージョンなど|
|:--|:--|
| 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|

# 参考サイト

自分が作った[Jerseyのプロジェクト](https://qiita.com/kasa_le/items/db0d84e3e868ff14bc2b)や、[Spring MVCのHelloWorldプロジェクト](https://qiita.com/kasa_le/items/6aaf17823db67c951fb0)に追加しようとしたのですが、情報を探すとどうしてもSpringBootを使っているものが多く、なかなか難儀していました。

やっと見つけたのがこちらのサイトです。

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

十年近く前の情報で、かなり古いですが、上がっているプロジェクトは一応そのまま動きました。（※ただしJDK9以降の環境では、JAXB関連の依存関係の追加が必要)
なので、いったんそこから最新版に上げることを目指し、成功したので、その最終形態をメモしておきます。

# プロジェクト設定手順

## 1.Mavenプロジェクトを新規作成

IntelliJ IDEAで新規にMavenプロジェクトを作成します。
手順は[こちら](https://qiita.com/kasa_le/items/6aaf17823db67c951fb0)などを参考にしてください。


# 2.依存関係を設定

`pom.xml`は次のようになりました。
参考サイトと変わっているのは、各バージョンが最新版になっていることと、それに伴いパッケージ移動したものを変更しています。また、JAXB関連がJDK9以降削除されているのでその依存関係も追加しています。

```xml: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は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.トランザクションクラスを作成

Beanでインジェクトするクラスとなります。
インターフェースを作ってImplementするのが作法みたいですが、そうなっていなくても問題はないです。Springの売りの1つが**DI**(依存成注入)なので、後でテストするときにモックすることなどを考えたら、最初からやっておいたほうがいいでしょうね。

インターフェースクラス

```transaction/TransactionBo.java
public interface TransactionBo {
    String save();
}
```

実装クラス

```transaction/impl/TransactionBoImpl.java
public class TransactionBoImpl implements TransactionBo {
    public String save() {
        return "Jersey + Spring example";
    }
}
```

## 4.サービスクラスを作成

`TransactionBoImpl`をSpringにDIしてもらいますが、`@Autowired`アノテーションは使いません。
コンストラクタインジェクションを使います。
(コンストラクタインジェクションが推奨のようです。詳しくは末尾の参考サイトから)

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

Beanのクラスをインジェクトをしてもらうために設定するファイルです。
`src/main/resources`フォルダ下に置きます。
パッケージ名とBeanのクラスは自分が作成したものに適宜変更して下さい。

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

## 5. web.xml

`src/main/webapp/WEB-INF/`下に`web.xml`を作成して以下のようにします。

```xml: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>
```

参考サイトと違うのは、`<servlet-class>`がJerseyのクラスになっている点くらいです。
あとパッケージ名も自分が作成したものに変えてくださいね。

## 6.index.jsp

`webapp`フォルダ下に置きます。
なくてもいいんですが、ないとTomcatにデプロイして起動したとき404ページが表示されて気持ち悪いので(^^;

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

# 実行

Tomcatで実行設定をして起動すれば、次のような画面が表示されるはずです。

|<img width="625" alt="jersey_spring_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/b011b668-301b-38e3-d877-0b7e7271dc59.png">|
|:--|

`Jersey Resource`のリンクをクリックすると、`rest/payment/mkyong`の`GET`メソッドが叩かれ、戻り値が表示されます。

|<img width="611" alt="jersery_spring_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103886/af2bb030-b5f5-38fc-33ef-9a6f0c9cbac6.png">|
|:--|

curlやPostmanでも成功するはずです。

# テスト

せっかくなのでテストも書いてみます。

## 1.単純なテスト

### (1)依存関係の追加
テスト用の依存関係を追加します。
[この記事](https://qiita.com/kasa_le/items/b0bbf6b4c23fd105959a)の時と同じにしておきました。

まず`<build>/<plugins>`下にプラグインを追加します。

```xml: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>
```

続いて、`<dependencies>`に追加します。

```xml:pom.xml
       <!-- テスト -->
        <!-- 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)テストクラスの実装
基本的には[Jerseyの基本サンプル](https://qiita.com/kasa_le/items/f5d5997e90691104d422)でやったをそのまま踏襲するだけです。

```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.TransactionBoの実装を変えてみる

せっかくなのでSpringのDIを使ってモックに入れ替えられないか試してみました。

### (1)依存関係の追加

SpringのDIを動かすために、SpringのTestフレームワークを入れます。

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

### (2)モックBeanクラスを作成

`src/test/java/my/example/transaction/impl`下に、下記のクラスを作ります。

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

これをBean定義しますが、テスト用なのでテスト用の`applicationContext.xml`を作ります。
`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>
```

実装クラスを`TransactionBoMock`に変更しています。

### (3)テストクラスの修正

`Junit5`に対応した書き方にしています。

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

２つのアノテーションを追加するだけ！
実行すると、まだ比較文字列を変えていないので、エラーになると思います。

```console
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.
```

```java
assertThat(content).isEqualTo("Jersey + Spring example");
```

上記の文字列を`"This is mock."`に変えてやれば完了ですね。

# 感想

ここにたどり着くまでにかなり時間かかっていますが・・・

出来上がってみれば、割と単純です。
ここまでシンプルになると、DIしてもらうのに`applicationContext.xml`で<bean>の定義が必要なんだなとか、そういうことが分かりやすくなりますね。

Spring MVCを入れるとなると、Controllerとかdispatcherとかの設定がまた大変そうですが、RESTfulAPIだけの予定なので、いったんはこれでよいでしょう。（あまりSpring MVCは勉強する気がなかったりw)

次は、もうちょっとちゃんとしたAPIを作って、できれば[Jerseyのみでマルチモジュール化したプロジェクト](https://qiita.com/kasa_le/items/db0d84e3e868ff14bc2b)とマージしていきたいですね。

ここまでのプロジェクトは、以下にアップしてあります。

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

# その他の参考サイト

情報が古かったりBoot向けだったりで、直接の参考にはならなかったけど、細かいところでは色々とヒントを貰ったサイトです。

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/

SpringでField InjectionよりConstructor Injectionが推奨される理由
http://pppurple.hatenablog.com/entry/2016/12/29/233141

JUnitでテストするときにも、DIが動作するようにするには
https://wikiwiki.jp/webapp/Spring/JUnit

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