読者です 読者をやめる 読者になる 読者になる

JAX-RS (Jersey) でパラメータとして独自のクラスをインジェクトする (2)

このエントリの続きです。パラメータが多い場合には、対応するメソッドの引数が増えてしまうので、引数をDTOにまとめたいという話です。
例えば、id, foo, bar, bazという引数があるメッソッドで、これらの引数を以下のようなDTOにしてクエリパラメータやパスパラメータから値を自動で設定したいとします。

public class MyParams {
	@PathParam("id")
	public Integer id
	@QueryParam("foo")
	public String foo;
	@QueryParam("bar")
	public String bar;
	@QueryParam("baz")
	public Integer baz;
}

前回は、以下のようにResourceContextをインジェクトして、メソッド内でそれを介してMyParamsを取得していました。これだと、メッソッドの引数にResourceContextがあるのが少し気持ちがよくない気がします。今回はその続きで直接MyParamsをインジェクトします。

@Path("/myResource")
public class MyResource {
	@GET
	@Path("{id}")
	@Produces(//...)
	public String get(@Context ResourceContext rc){
		MyParams params = rc.getResource(MyParams.class);
		// ...
	} 
}

この方法は、JerseyのMLにある方法そのままです。まず、@ResourceParamという専用のアノテーションを作成します。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResourceParam {
}

次に、このアノテーションが付加された引数をインジェクトするために、JerseyのInjectableProviderを実装したクラスを作成します。内部では、ResourceContextを使用した場合と同じことをやってます。

import java.lang.reflect.Type;
import javax.ws.rs.core.Context;
import com.sun.jersey.api.core.ResourceContext;
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;

public class ResourceParamInjector implements InjectableProvider<ResourceParam, Type> {
	private final ResourceContext rc;

    public ResourceParamInjector(@Context ResourceContext rc) {
        this.rc = rc;
    }

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

	@Override
	public Injectable getInjectable(ComponentContext ic, ResourceParam a, Type type) {
        if (type instanceof Class) {
            final Class c = (Class)type;

            return new Injectable() {
                public Object getValue() {
                    return rc.getResource(c);
                }
            };
        }
        return null;
	}
}

作成したResourceParamInjectorをApplicationクラスから登録します。

public class MyApplication extends Application {
	@Override
	public Set<Class<?>> getClasses() {
		Set<Class<?>> classes = new HashSet<Class<?>>();
		// ...
		//
		classes.add(ResourceParamInjector.class);
		return classes;
	}
}

以下のように使うことができます。

@Path("/myResource")
public class MyResource {
	@GET
	@Path("{id}")
	@Produces(//...)
	public String get(@ResourceParam MyParams param){
		// ...
	} 
}

何らかのオブジェクト自体ををPOST/PUTするようなケースは@ConsumesでJSONやXMLを処理すると思うので、あまり使わないかもしませんが。