10

現在、Jerseyで作成しようとしていますがInjectableProvider、Jerseyに取得させることができません。

@Providerその使用法の実際の例、または実装のアノテーションを使用する以外にそれを取得する方法さえも見つけることができません。ジャージー内でそれを書いたように見える人は、これがそれを拾うのに十分であるといくつかの投稿で暗示しました。

SPIサービスファイルを指定する必要がありますか、それともどこかの工場に追加する必要がありますか?

注:私はGlassfish 3.1内で実行しており、Spring3.1を使用しています。Springがsの自動ロードを何らかの形で引き継いでいる可能性があることは合理的と思われますProvider。しかし、私にはわかりません。私はとにかくSpringを使用して以下の提案されたInjectableProviderを管理していません。また、他の方法でそれを追加しようとはしていません。これは私の問題かもしれません。

import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;

public abstract class AbstractAttributeInjectableProvider<T>
        extends PerRequestTypeInjectableProvider<AttributeParam, T>
{
    protected final Class<T> type;

    public AbstractAttributeInjectableProvider(Class<T> type)
    {
        super(type);

        this.type = type;
    }

    @Override
    public Injectable<T> getInjectable(ComponentContext componentContext,
                                       AttributeParam attributeParam)
    {
        return new AttributeInjectable<T>(type, attributeParam.value());
    }
}

基本的な実装:

import javax.ws.rs.ext.Provider;

@Component // <- Spring Annotation
@Provider  // <- Jersey Annotation
public class MyTypeAttributeInjectableProvider
        extends AbstractAttributeInjectableProvider<MyType>
{
    public MyTypeAttributeInjectableProvider()
    {
        super(MyType.class);
    }
}

参照Annotation

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AttributeParam
{
    /**
     * The value is the name to request as an attribute from an {@link
     * HttpContext}'s {@link HttpServletRequest}.
     * @return Never {@code null}. Should never be blank.
     */
    String value();
}

ジャージー開発者からの参照リンク。


更新:calvinkrishyは私の考えに2つの欠陥を指摘しました。

最初に、Jerseyは@Provider、従来のJersey-Springサーブレットによってキックオフされた後、sのスキャンを開始する予定であると想定しましたcom.sun.jersey.spi.spring.container.servlet.SpringServlet。これはほとんど正しくありませんでした。スキャンを開始しますが、アノテーションのあるSpringBeanを探します。

次に、制御するアノテーションを処理するPerRequestTypeInjectableProviderためのリクエストが着信するたびに、が要求されると想定しました。Injectableこれも間違っていました。はPerRequestTypeInjectableProvider起動時に期待どおりにインスタンス化されますが、JerseyはすぐにInjectable、指定されたアノテーションを指定されたアノテーションで処理するように要求しtypeます。これは、この時点で管理していると判断したRestfulServicesをスキャンすることで決定されます。つまり、それらすべて)。

PerRequestTypeInjectableProviderとの違いはSingletonTypeInjectableProvider、結果Injectableが機能せずに値を含む(シングルトン)か、値を毎回(要求ごとに)検索するため、要求ごとに値を変更できることです。

これは、余分な知識AttributeInjectableを与えないように、計画したようにいくつかのオブジェクトを渡すのではなく、(以下のコード)で余分な作業を強制することによって、私の計画に小さなレンチを投げ込みました。AttributeInjectable

public class AttributeInjectable<T> implements Injectable<T>
{
    /**
     * The type of data that is being requested.
     */
    private final Class<T> type;
    /**
     * The name to extract from the {@link HttpServletRequest} attributes.
     */
    private final String name;

    /**
     * Converts the attribute with the given {@code name} into the {@code type}.
     * @param type The type of data being retrieved
     * @param name The name being retrieved.
     * @throws IllegalArgumentException if any parameter is {@code null}.
     */
    public AttributeInjectable(Class<T> type, String name)
    {
        // check for null

        // required
        this.type = type;
        this.name = name;
    }

    /**
     * Look up the requested value.
     * @return {@code null} if the attribute does not exist or if it is not the
     *         appropriate {@link Class type}.
     *         <p />
     *         Note: Jersey most likely will fail if the value is {@code null}.
     * @throws NullPointerException if {@link HttpServletRequest} is unset.
     * @see #getRequest()
     */
    @Override
    public T getValue()
    {
        T value = null;
        Object object = getRequest().getAttribute(name);

        if (type.isInstance(object))
        {
            value = type.cast(object);
        }

        return value;
    }

    /**
     * Get the current {@link HttpServletRequest} [hopefully] being made
     * containing the {@link HttpServletRequest#getAttribute(String) attribute}.
     * @throws NullPointerException if the Servlet Filter for the {@link
     *                              RequestContextHolder} is not setup
     *                              appropriately.
     * @see org.springframework.web.filter.RequestContextFilter
     */
    protected HttpServletRequest getRequest()
    {
        // get the request from the Spring Context Holder (this is done for
        //  every request by a filter)
        ServletRequestAttributes attributes =
            (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();

        return attributes.getRequest();
    }
}

HttpServletRequestからを渡すことができることを望んでProviderいましたが、AttributeInjectableは一意のアノテーション/タイプごとにのみインスタンス化されます。それができないので、値ごとのルックアップを実行します。これは、 (現在のリクエストに関連するものの中でも)安全に取得するためのメカニズムRequestContextFilterを提供するSpringのシングルトンを使用します。ThreadLocalHttpServletRequest

<filter>
    <filter-name>requestContextFilter</filter-name>
    <filter-class>
        org.springframework.web.filter.RequestContextFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>requestContextFilter</filter-name>
    <url-pattern>/path/that/i/wanted/*</url-pattern>
</filter-mapping>

結果は機能し、さまざまなサービスに基本クラスを拡張しての使用法を非表示にすることなく、コードをはるかに読みやすくします@Context HttpServletRequest request。これは、ヘルパーメソッドを介して上記のように属性にアクセスするために使用されます。

次に、これに沿って何かを行うことができます。

@Path("my/path/to")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
public interface MyService
{
    @Path("service1")
    @POST
    Response postData(@AttributeParam("some.name") MyType data);

    @Path("service2")
    @POST
    Response postOtherData(@AttributeParam("other.name") MyOtherType data);
}

@Component // Spring
public class MyServiceBean implements MyService
{
    @Override
    public Response postData(MyType data)
    {
        // interact with data
    }

    @Override
    public Response postOtherData(MyOtherType data)
    {
        // interact with data
    }
}

これは、サーブレットフィルタを使用して、データを渡す前にユーザーがサービスにアクセスするための適切な権限を持っていることを確認し、受信データを解析(またはロードなど)して属性にダンプできるため、非常に便利です。ロードされます。

上記のProviderアプローチが不要で、属性にアクセスするための基本クラスが必要な場合は、次のようにします。

public class RequestContextBean
{
    /**
     * The current request from the user.
     */
    @Context
    protected HttpServletRequest request;

    /**
     * Get the attribute associated with the current {@link HttpServletRequest}.
     * @param name The attribute name.
     * @param type The expected type of the attribute.
     * @return {@code null} if the attribute does not exist, or if it does not
     *         match the {@code type}. Otherwise the appropriately casted
     *         attribute.
     * @throws NullPointerException if {@code type} is {@code null}.
     */
    public <T> T getAttribute(String name, Class<T> type)
    {
        T value = null;
        Object attribute = request.getAttribute(name);

        if (type.isInstance(attribute))
        {
            value = type.cast(attribute);
        }

        return value;
    }
}

@Path("my/path/to")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
public interface MyService
{
    @Path("service1")
    @POST
    Response postData();

    @Path("service2")
    @POST
    Response postOtherData();
}

@Component
public class MyServiceBean extends RequestContextBean implements MyService
{
    @Override
    public Response postData()
    {
        MyType data = getAttribute("some.name", MyType.class);
        // interact with data
    }

    @Override
    Response postOtherData()
    {
        MyOtherType data = getAttribute("other.name", MyOtherType.class);
        // interact with data
    }
}

UPDATE2:自分の実装について考えました。これは、それ自体が、特定の型に'AbstractAttributeInjectableProviderを提供するためだけに存在するジェネリッククラスであり、提供された。要求されるたびにタイプ()が通知される非実装を提供する方がはるかに簡単です。したがって、タイプを提供するコンストラクターのみの実装の束を回避できます。これにより、アノテーションで使用するすべてのタイプのコードを記述する必要もなくなります。AttributeInjectableClass<T>AttributeParamabstractClass<T>AttributeParamAttributeParam

@Component
@Provider
public class AttributeParamInjectableProvider
        implements InjectableProvider<AttributeParam, Type>
{
    /**
     * {@inheritDoc}
     * @return Always {@link ComponentScope#PerRequest}.
     */
    @Override
    public ComponentScope getScope()
    {
        return ComponentScope.PerRequest;
    }

    /**
     * Get an {@link AttributeInjectable} to inject the {@code parameter} for
     * the given {@code type}.
     * @param context Unused.
     * @param parameter The requested parameter
     * @param type The type of data to be returned.
     * @return {@code null} if {@code type} is not a {@link Class}. Otherwise
     *         an {@link AttributeInjectable}.
     */
    @Override
    public AttributeInjectable<?> getInjectable(ComponentContext context,
                                                AttributeParam parameter,
                                                Type type)
    {
        AttributeInjectable<?> injectable = null;

        // as long as it's something that we can work with...
        if (type instanceof Class)
        {
            injectable = getInjectable((Class<?>)type, parameter);
        }

        return injectable;
    }

    /**
     * Create a new {@link AttributeInjectable} for the given {@code type} and
     * {@code parameter}.
     * <p />
     * This is provided to avoid the support for generics without the need for
     * {@code SuppressWarnings} (avoided via indirection).
     * @param type The type of data to be returned.
     * @param parameter The requested parameter
     * @param <T> The type of data being accessed by the {@code param}.
     * @return Never {@code null}.
     */
    protected <T> AttributeInjectable<T> getInjectable(Class<T> type,
                                                       AttributeParam parameter)
    {
        return new AttributeInjectable<T>(type, parameter.value());
    }
}

注:それぞれInjectableは、要求ごとではなく起動時に1回インスタンス化されますが、着信要求ごとに呼び出されます。

4

1 に答える 1

6

どのようにジャージーを初期化していますか?

jersey-spring サーブレットを使用して Jersey を使用していると仮定します。その場合、Jersey はデフォルトで Spring Bean を使用して初期化するためProvider、Spring Bean である必要があります。に (または atinjectまたは Spring アノテーションのいずれかを@Named使用しない場合) を追加してみてください。@ComponentProvider

Injectable Providers の使用例


更新: インジェクションの範囲をより明確に:

Providerシングルトンである必要があります。すべての実用的な目的のために、スコープがそれに関連付けられたファクトリであり、リクエストごとにファクトリを構築する必要がないためです。インジェクション自体はリクエストごとに行われます。つまり、getInjectableメソッドはリクエストごとに呼び出されます。それを試す機会はありましたか?

OTOH、SingletonTypeInjectableProvider同じオブジェクトを拡張すると、毎回リソースに注入されます。

あなたのProvider実装を完全に理解しているかどうかはわかりません。次のようなものが機能するはずです。

public class UserProvider extends PerRequestTypeInjectableProvider<AttributeParam, Users>{

    public UserProvider(){
        super(Users.class);
    }

    @Context
    HttpServletRequest request;

    @Override
    public Injectable<Users> getInjectable(ComponentContext cc, AttributeParam a) {

        String attributeValue = AnnotationUtils.getValue(a);

        return new Injectable<Users>(){

            public Users getValue() {
                System.out.println("Called"); //This should be called for each request
                return request.getAttribute(attributeValue);
            }

        };

    }

}

更新: Jersey で利用可能なインジェクションの種類とコンテキストに関する詳細情報を提供します。

おそらく今までに理解したように、必要なのが へのアクセスHttpServletRequestだけである場合は、それを直接注入するResourceか、アノテーションをProvider使用するだけでそれが得られます。@Context

ただし、これらの値を Injectable に渡すには、AssistedProviderあなたと同様のアプローチを使用するか、使用する必要があります。Injectableただし、プロバイダーで定義をインラインHttpServletRequest化し、Providerクラスに挿入すると、これを軽減できます。その場合Injectable、インスタンスにアクセスできHttpServletRequestます (スコープ内にあるため)。そのアプローチを示すために例を更新しました。

リソースに値を注入するために必要なオプションは、 と を使用した注入だけPerRequestTypeInjectableProviderではありません。を使用して値をSingletonTypeInjectableProvider挿入することもできます。明らかに、そのようなインジェクションはリクエストスコープです。*ParamStringReaderProvider

@Provider
@Named("userProviderParamInjector")
public class UserProviderParam implements StringReaderProvider<Users> {

    @Context
    HttpServletRequest request;

    public StringReader<Users> getStringReader(Class<?> type, Type type1, Annotation[] antns) {
        if(type.equals(Users.class) {
           return null;
        }

        String attributeValue = null;
        for(Annotation a : antns) {
            if((a.getClass().getSimpleName()).equals("AttributeParam")){
               attributeValue = (String)AnnotationUtils.getValue(a);
            }
        }

        return new StringReader<Users>(){
            public Users fromString(String string) {
                // Use the value of the *Param or ignore it and use the attributeValue of our custom annotation.
                return request.getAttribute(attributeValue);
            }

        };

    }

}

これProviderは、リソースにあるものに対して呼び出され*Paramます。したがって、Provider上記のようなリソースが登録されていて、以下のようなリソースが登録されている場合、Users値はリソース メソッドに注入されます。

@Path("/user/")
@Named
public class UserResource {

    @Path("{id}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Result<Users> get(@AttributeParam("foo") @PathParam("id") Users user) {
    ...
    }

}

しかし、正直に言うと、これは StringReaderProvider コントラクトの乱用であると考えていますが、以前の使用方法Injectableはよりクリーンに感じます。

于 2012-08-28T02:31:34.513 に答える