Dropwizard に学ぶ / Jersey Injection Provider

  • 5
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

InjectableProvider

Dropwizard で利用されている Jersey-1.18 では InjectableProvider<A extends Annotation, C> を実装した Provider を登録しておくことで、これらが提供する依存オブジェクトを取得することができます。

  • A はインジェクト対象を示すアノテーションを指定: @Context, etc.
  • Cjava.lang.reflect.Type または com.sun.jersey.api.model.Parameter

以下は Typesafe Config を提供する ConfigProvider の例です。

ConfigProvider.java
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;

import java.lang.reflect.Type;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

@Provider
public class ConfigProvider implements InjectableProvider<Context, Type> {
    private final Config config;

    public ConfigProvider() {
        config = ConfigFactory.load();
    }

    @Override
    public Injectable<Config> getInjectable(
            ComponentContext ic, Context a, Type t) {
        if (!t.equals(Config.class))
            return null;

        return new Injectable<Config>() {
            @Override
            public Config getValue() {
                return config;
            }
        };
    }

    @Override
    public ComponentScope getScope() {
        return ComponentScope.Singleton;
    }
}
application.conf
app { version: "0.1.0" }

インジェクト対象に @Context アノテーションを指定することで、ConfigProvider が提供する Config オブジェクトを取得できます。

AppResource.java
@Path("/")
public class AppResource {
    @GET
    @Path("version")
    @Produces(MediaType.TEXT_PLAIN)
    public Response version(@Context Config config) {
        return Response.ok(config.getString("app.version")).build();
    }
}

AbstractHttpContextInjectable

依存オブジェクトとして、リクエストヘッダ Accept-Languages から、ユーザの利用言語に応じたメッセージカタログを作成したいとします。以下の I18n クラスを例にします。

import java.util.Locale;
import java.util.ResourceBundle;

public class I18n {
    private static final String NAME = "messages";
    private final ResourceBundle bundle;

    private I18n(Locale locale) {
        if (locale == null)
            this.bundle = ResourceBundle.getBundle(NAME);
        else
            this.bundle = ResourceBundle.getBundle(NAME, locale);
    }

    public String get(String key) {
        return bundle.getString(key);
    }

    public static I18n newInstance() {
        return new I18n(null);
    }

    public static I18n newInstance(Locale locale) {
        return new I18n(locale);
    }
}
message.properties
hello=Hello
message_ja.properties
hello=\u3053\u3093\u306B\u3061\u306F
I18n enMessages = I18n.newInstance(Locale.US);
enMessages.get("hello"); // Hello
I18n jaMessages = I18n.newInstance(Locale.JAPAN);
jaMessages.get("hello"); // こんにちは

このようにリクエストに応じた依存オブジェクトを作成したい場合、Injectable ではリクエストヘッダを得る事ができません。代わりに jersey-server パッケージの AbstractHttpContextInjectable を使う事で、getValue の引数 HttpContext から、リクエストヘッダを参照することができます。

I18nProvider.java
...
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;

import java.util.List;
import java.util.Locale;

@Provider
public class I18nProvider implements InjectableProvider<Context, Type> {
    @Override
    public Injectable<I18n> getInjectable(
            ComponentContext ic, Context a, Type t) {
        if (!t.equals(I18n.class))
            return null;

        return new AbstractHttpContextInjectable<I18n>() {
            @Override
            public I18n getValue(HttpContext ctx) {
                List<Locale> locales = ctx.getRequest().getAcceptableLanguages();
                if (locales.isEmpty())
                    return I18n.newInstance();
                else
                    return I18n.newInstance(locales.get(0));
            }
        };
    }

    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }
}

リクエストごとにインスタンスを生成する必要があるので、スコープは CompoentScope.PerRequest とし、メゾッドインジェクションでのみ取得します。

@GET
@Path("hello")
@Produces(MediaType.TEXT_PLAIN)
public Response hello(@Context I18n messages) {
    return Response.ok(messages.get("hello")).build();
}

この方法により、リクエスト情報からメッセージカタログを組み立てる同ロジックを各リソースクラスに散りばめる必要がなくなります。

Dropwizard 公式マニュアルの Authentication の章で、 @Auth アノテーションを用いた認証ユーザの取得方法が紹介されていますが、BasicAuthProvider / OAuthProvider ともに AbstractHttpContextInjectable が使われています。