LoginSignup
3
5

More than 3 years have passed since last update.

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

Posted at

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

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

目的

JserseySpring 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のプロジェクトや、Spring MVCのHelloWorldプロジェクトに追加しようとしたのですが、情報を探すとどうしてもSpringBootを使っているものが多く、なかなか難儀していました。

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

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

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

プロジェクト設定手順

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

IntelliJ IDEAで新規にMavenプロジェクトを作成します。
手順はこちらなどを参考にしてください。

2.依存関係を設定

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

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を作成して以下のようにします。

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ページが表示されて気持ち悪いので(^^;

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

実行

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

jersey_spring_1.png

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

jersery_spring_2.png

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

テスト

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

1.単純なテスト

(1)依存関係の追加

テスト用の依存関係を追加します。
この記事の時と同じにしておきました。

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

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

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の基本サンプルでやったをそのまま踏襲するだけです。

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フレームワークを入れます。

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 {

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

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");

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

感想

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

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

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

次は、もうちょっとちゃんとしたAPIを作って、できればJerseyのみでマルチモジュール化したプロジェクトとマージしていきたいですね。

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

その他の参考サイト

情報が古かったり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

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5