以前の記事「Spring MVC + Spring BootでJackson2のdataformatを変更する方法」の続きです。
本当はMessagePackのjackson-dataformat-msgpackを使いたかったのですが、なんか結果がおかしい(二回書き込まれている)&何回かやるとレスポンスが空になるなど、怪しい挙動をしていました・・・問題の切り分けのためにここに至ったわけです・・・
と言っていた訳ですが、@komamitsu_twさんから、0.7.0-p3で治ったとコメントを頂いたので、試してみました。
Spring MVC + Spring Bootの場合
基本方針は前回と同じです。依存関係に
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>jackson-dataformat-msgpack</artifactId>
<version>0.7.0-p3</version>
</dependency>
を追加して、
@Bean
HttpMessageConverter messagePackMessageConverter() {
return new AbstractJackson2HttpMessageConverter(
new ObjectMapper(new MessagePackFactory()),
new MediaType("application", "x-msgpack")) {
};
}
をBean定義するだけです。
サンプルはこちら。
App
クラスを実行して、
$ curl -v "localhost:8080/calc?left=100&right=300"
> GET /calc?left=100&right=300 HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/x-msgpack;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 16 Jan 2015 15:00:26 GMT
<
��leftd�right�,�answer��
MessagePackフォーマットになっていますね!
Content negotiationもサポートされているので、拡張子に.json
をつけると
$ curl -v "localhost:8080/calc.json?left=100&right=300"
> GET /calc.json?left=100&right=300 HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 16 Jan 2015 15:01:44 GMT
<
{"left":100,"right":300,"answer":400}
JSONが返ります!開発中は見やすいこっちがいいですね。
Accept
でも切り替えられます。
$ curl -v -H "Accept: application/x-msgpack" "localhost:8080/calc?left=100&right=300"
> GET /calc?left=100&right=300 HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: application/x-msgpack
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/x-msgpack;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 16 Jan 2015 15:03:39 GMT
<
��leftd�right�,�answer��
と
$ curl -v -H "Accept: application/json" "localhost:8080/calc?left=100&right=300"
> GET /calc?left=100&right=300 HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: application/json
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 16 Jan 2015 15:04:13 GMT
<
{"left":100,"right":300,"answer":400}
サンプルコードにはE2Eテストもついているので興味があれば見てください。
JAX-RS(Jersey) + Spring Bootの場合
Jerseyでも同じようにできないかなといろいろ試してみたところ、以下のように設定すればいけました。
package com.example;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import org.glassfish.jersey.server.ResourceConfig;
import org.msgpack.jackson.dataformat.MessagePackFactory;
import org.springframework.context.annotation.Configuration;
import javax.inject.Named;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
@Configuration
public class AppConfig {
@Provider
@Produces("application/x-msgpack")
@Consumes("application/x-msgpack")
public static class JacksonMessagePackProvider extends JacksonJsonProvider {
public JacksonMessagePackProvider() {
super(new ObjectMapper(new MessagePackFactory()));
}
@Override
protected boolean hasMatchingMediaType(MediaType mediaType) {
if (mediaType != null) {
String subtype = mediaType.getSubtype();
return "x-msgpack".equals(subtype);
}
return false;
}
}
@Named
static class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
this.packages("com.example")
.register(JacksonMessagePackProvider.class);
}
}
}
JacksonJsonProvider
を継承して、@Produces
と@Consumes
をつけ、hasMatchingMediaType
をオーバーライドするのがポイントでした。
エンドポイントはこんな感じ。
package com.example;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
@Named
@Path("/")
public class CalcEndpoint {
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Result {
private int left;
private int right;
private long answer;
}
@GET
@Produces({"application/json", "application/x-msgpack"})
@Path("calc")
public Result calc(@QueryParam("left") int left, @QueryParam("right") int right) {
return new Result(left, right, left + right);
}
}
サンプルはこちら。
App
クラスを実行して、
$ curl -v -H "Accept: application/x-msgpack" "localhost:8080/calc?left=100&right=300"
> GET /calc?left=100&right=300 HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: application/x-msgpack
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/x-msgpack;charset=UTF-8
< Content-Length: 26
< Date: Fri, 16 Jan 2015 16:32:05 GMT
<
��leftd�right�,�answer��
Accept
でも切り替えて、
$ curl -v -H "Accept: application/json" "localhost:8080/calc?left=100&right=300"
> GET /calc?left=100&right=300 HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: application/json
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json;charset=UTF-8
< Content-Length: 37
< Date: Fri, 16 Jan 2015 16:32:52 GMT
<
{"left":100,"right":300,"answer":400}
JSONも出ました。
以前、@frsyukiさんがつぶやいていたことを思い出しました。
JavaでイマドキのRPCといえば、Jetty+Jersey+Jackson+MessagePackでキマリ!
— FURUHASHI Sadayuki (@frsyuki) 2014, 11月 21
↑のサンプルだとTomcat8ですが、Jetty9にしたい場合は、
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
こうすればOK。Spring MVCの場合も同様。Tomcatでも問題ないと思うけど。
Spring Bootだと即実行可能でかつ、実行可能jarもポータブルで尚良いのではないでしょうか。
Spring Bootを始めるなら、Spring MVC版、Jersey版それぞれmaven-archetypeを作ったのでサクッと試したい場合はどうぞ
Spring Bootはアプリケーション実行基盤として優秀だなーと改めて思いました。
も挙げましたが、RPC実行環境としてもいけてるのでは。
追記
Jetty+Jersey+Jackson+MessagePackを使ったJavaでモダンなRPCを作る雛形プロジェクト(maven archetype)を作ってみました
https://github.com/making/msgpack-rpc-jersey-blank
騙されたと思って使ってみてください。