IK.AM

@making's tech note


MessagePack + Spring MVC or JAX-RS(Jersery) on Spring Boot

🗃 {Programming/Java/org/msgpack/jackson/dataformat/MessagePackFactory}
🏷 JAX-RS 🏷 Jackson 🏷 Java 🏷 Jersey 🏷 MessagePack 🏷 Spring 🏷 Spring Boot 🏷 Spring MVC 
🗓 Updated at 2015-01-17T06:08:33Z  🗓 Created at 2015-01-17T06:08:33Z   🌎 English Page

以前の記事「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さんがつぶやいていたことを思い出しました。

↑のサンプルだと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

騙されたと思って使ってみてください。


✒️️ Edit  ⏰ History  🗑 Delete