User Guide: https://jersey.github.io/documentation/latest/user-guide.html

登場人物

pom.xml

<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>2.27</version>
    <scope>provided</scope>
</dependency>

アプリケーションサーバとして GlassFish を使う場合は、サーバ側の jar に含まれているため<scope>provided</scope>として良い

javax.ws.rs.core.Application のサブクラス

アプリケーションのルート?

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/")
public class MyApp extends Application {}

中身は空でも良い。その場合、定義したサーブレットクラスが全て読み込まれる。

javax.ws.rs.core.Applicationを拡張したorg.glassfish.jersey.server.ResourceConfigを使うと Dependency Injection とかできて便利?

サーブレットクラス

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/")
public class MyAppResource {
    @GET
    @Produces(MediaType.TEXT_HTML)
    public String rootPage() {
        return "<html><body><h1>Root Page</h1></body></html>";
    }

    @GET
    @Path("/xml")
    @Produces(MediaType.APPLICATION_XML)
    public String xml() {
        return "<tag>sample xml</tag>";
    }

    @GET
    @Path("/json")
    @Produces(MediaType.APPLICATION_JSON)
    public String json() {
        return "{\"num\": 100}";
    }
}

@Path

  • URI の相対パスを表す
  • クラスにもメソッドにも付与できる
  • パスの一部を{}で囲むと、@PathParamアノテーションで変数として取り出すことができる
@Path("/users/{username}")
public class UserResource {
    @GET
    @Produces("text/xml")
    public String getUser(@PathParam("username") String userName) {
        ...
    }
}

@GET, @PUT, @POST, @DELETE, …

  • Java のメソッドを HTTP メソッドに対応させる

@Produces

  • リソースが返却する MIME タイプ(text/plain など)を表す
  • クラスにもメソッドにも付与できる
@Path("/")
public class SomeResource {
    @GET
    @Path("/acceptTypeTest")
    @Produces(MediaType.APPLICATION_JSON)
    public String acceptTypeTestOnlyJSON() {
        return "{\"result\": \"JSON が要求されたため JSON 形式で返却します。\"}";
    }
    @GET
    @Path("/acceptTypeTest")
    @Produces({MediaType.APPLICATION_XML, MediaType.TEXT_XML})
    public String acceptTypeTestOnlyXML() {
        return "<result>XML が要求されたため XML 形式で返却します</result>";
    }
    @GET
    @Path("/acceptTypeTest")
    public String acceptTypeTestAny() {
        return "JSON/XML ともに Accept ヘッダに含まれなかったため、plain/text を返却します";
    }
}
$ curl --dump-header - -H 'Accept:text/xml' http://localhost:8080/acceptTypeTest
...
Content-Type: text/xml
...
<result>XML が要求されたため XML 形式で返却します</result>

$ curl --dump-header - -H 'Accept:application/xml' http://localhost:8080/acceptTypeTest
...
Content-Type: application/xml
...
<result>XML が要求されたため XML 形式で返却します</result>

$ curl --dump-header - -H 'Accept:application/json' http://localhost:8080/acceptTypeTest
...
Content-Type: application/json
...
{"result": "JSON が要求されたため JSON 形式で返却します。"}

$ curl --dump-header - -H 'Accept:plain/text' http://localhost:8080/acceptTypeTest
...
Content-Type: plain/text
...
JSON/XML ともに Accept ヘッダに含まれなかったため、plain/text を返却します

@Consumes

  • リソースが受け付ける MIME タイプを表す

@*Param

  • リソースメソッドの引数としてリソースのパスやリクエストパラメータなどから値を取ってくる
アノテーション 説明
@MatrixParam  
@HeaderParam  
@CookieParam  
@FormParam  
@QueryParam  
@PathParam  
@Path("/")
public class SomeResource {
	@GET
	@path("/search/user/{username}")
	public String func(
		@DefaultValue("10") @QueryParam("max-response") int maxResponse,
		@PathParam("username") String userName
	) {
		...
	}
}

String 1つを引数とするコンストラクタを持てば、ユーザ定義のクラスへマッピングすることもできる。

public class MyObject {
	public String str;
	public MyObject(String s) {
		this.str = s;
	}
}

@Path("/")
public class SomeResource {
	@GET
	public String func(
		@DefaultValue("default") @QueryParam("s") MyObject myObj
	) {
		...
	}
}

web.xml

Dependency Injection

javax.ws.rs.core.Applicationを拡張したorg.glassfish.jersey.server.ResourceConfigを使う。

サンプルコード

public interface IMyString {
	String s();
}
public class MyString implements IMyString {
	public long instanceCreatedTimeNano;

    public MyString() {
        this.instanceCreatedTimeNano = System.nanoTime();
    }
    
	String s() {
		return "created: " + instanceCreatedTimeNano;
	}
}
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;

import javax.inject.Singleton;
import javax.ws.rs.ApplicationPath;

@ApplicationPath("/")
public class MyApplication extends ResourceConfig {
    public MyApplication() {
        packages(getClass().getPackage().getName());
        register(new AbstractBinder() {
            @Override
            protected void configure() {
            bind(MyString.class).to(IMyString.class);
            //bind(MyString.class).to(IMyString.class).in(Singleton.class);
            }
        });
    }
}
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/")
public class MyAppResource {
	@Inject
	IMyString myStr;
	
	@GET
	public String get() {
		return myStr.s();
	}
}

Singleton

ResourceConfigにおいて

  1. .in(Singleton.class)をつけないで bind した場合、アクセスの都度インスタンスが作られるため毎回結果が変わる:
    // curl http://${app_server}:${port}
    created: 4651889700541
    created: 4654055891730
    created: 4655392680232
    created: 4656127701837
    created: 4656978541467
    created: 4657311692444
    ...
    
  2. .in(Singleton.class)をつけて bind した場合、最初のアクセス時(アプリケーションサーバの起動時ではない※)にしかインスタンスが作られないため何度叩いても結果は変わらない:
    // curl http://${app_server}:${port}
    created: 4782770088054
    created: 4782770088054
    created: 4782770088054
    created: 4782770088054
    created: 4782770088054
    created: 4782770088054
    ...
    

※ Singleton インスタンスの生成が起動時ではないことを確認するため、上の例のMyStringのコンストラクタを以下のように書き換え、アプリケーションサーバ起動から数十秒待ってから curl で複数回叩く実験を実施。

public MyString() throws InterruptedException {
	this.instanceCreatedTimeNano = System.nanoTime();
	Thread.sleep(2000L);
}
$ time curl http://${app_server}:${port}
created: 6900775283199
real	0m2.191s
user	0m0.007s
sys	0m0.010s
# 初回はレスポンスに2秒以上かかっている

$ time curl http://${app_server}:${port}
real	0m0.024s
user	0m0.007s
sys	0m0.008s
# 2回目以降はすぐにレスポンスが返る

TIPS

javax.ws.rs.core.Application と web.xml の関係

Application クラスのサブクラス web.xml
サブクラスが定義されていない web.xml に servlet-mapping が必要
サブクラスが定義され、@ApplicationPathアノテーションがついている web.xml 不要
サブクラスが定義され、@ApplicationPathアノテーションが定義されていない web.xml に servlet-mapping が必要