Ich versuche gerade, eine InjectableProvider
mit Jersey zu schaffen, aber ich kann Jersey nicht bekommen, um es aufzuheben.Jersey: InjectibleProvider nicht abgeholt - Frühling
Ich kann keine echten Beispiele für seine Verwendung finden, oder sogar, wie es neben der Verwendung der @Provider
Annotation auf der Implementierung abgerufen wird. Die Person, die es scheinbar in Jersey geschrieben hat, hat in einigen Beiträgen angedeutet, dass dies genug ist, um es aufzuheben.
Muss ich eine SPI-Servicedatei angeben oder irgendwo einer Fabrik hinzufügen?
Hinweis: Ich laufe in Glassfish 3.1 und mit Spring 3.1. Es scheint vernünftig, dass Spring irgendwie für das automatische Laden der Provider
s übernehmen könnte. Ich weiß es einfach nicht. Ich verwende Spring nicht, um den vorgeschlagenen InjectableProvider zu verwalten, noch versuche ich, es auf andere Weise hinzuzufügen, was mein Problem sein könnte.
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());
}
}
Grund Umsetzung:
import javax.ws.rs.ext.Provider;
@Component // <- Spring Annotation
@Provider // <- Jersey Annotation
public class MyTypeAttributeInjectableProvider
extends AbstractAttributeInjectableProvider<MyType>
{
public MyTypeAttributeInjectableProvider()
{
super(MyType.class);
}
}
Referenz 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();
}
Referenz link from Jersey developer.
UPDATE: calvinkrishy wies darauf hin, zwei Fehler zu meinem Denken.
Zuerst nahm ich an, dass Jersey begann, nach @Provider
s zu starten, nachdem es vom traditionellen Jersey-Spring-Servlet gestartet wurde: com.sun.jersey.spi.spring.container.servlet.SpringServlet
. Das war meistens falsch; Es beginnt mit dem Scannen, sucht aber nach Spring-Beans mit der Anmerkung.
Zweitens nahm ich an, dass die PerRequestTypeInjectableProvider
bei jeder eingehenden Anfrage für eine Injectable
aufgefordert werden würde, die Anmerkung, die es steuert, zu behandeln. Auch das war falsch. Die PerRequestTypeInjectableProvider
wird wie erwartet beim Start instanziiert, aber Jersey fragt dann sofort nach Injectable
, um die gegebene Anmerkung mit der gegebenen type
zu behandeln, die es durch das Scannen der Restful Services ermittelt, die es zu diesem Zeitpunkt - zu diesem Zeitpunkt - entschieden hat verwaltet (das heißt, alle von ihnen).
Der Unterschied zwischen dem PerRequestTypeInjectableProvider
und SingletonTypeInjectableProvider
zu sein scheint, dass die resultierenden Injectable
enthält entweder den Wert, ohne dafür zu arbeiten (Singleton) oder es sieht es jedes Mal für den Wert auf (pro Anfrage), damit der Wert zu ermöglichen Änderung pro Anfrage
Dies warf einen kleineren Schraubenschlüssel in meine Pläne, indem er mich zwang, einige zusätzliche Arbeit in meinem AttributeInjectable
(Code unten) statt einige Objekte übergeben, wie ich geplant hatte, um die AttributeInjectable
zusätzliche Wissen zu geben.
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();
}
}
Ich hatte gehofft, in der HttpServletRequest
vom Provider
passieren zu können, aber die AttributeInjectable
nur pro einzigartige Anmerkung/Typ instanziiert. Da ich das nicht tun kann, mache ich die Suche nach Wert, die Spring RequestContextFilter
Singleton verwendet, die einen ThreadLocal
Mechanismus zum sicheren Abrufen der HttpServletRequest
(unter anderem im Zusammenhang mit der aktuellen Anfrage) bietet.
<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>
Das Ergebnis funktioniert, und es macht den Code viel besser lesbar, ohne verschiedene Dienste zwingt eine Basisklasse zu verlängern nur die Verwendung von @Context HttpServletRequest request
zu verstecken, die dann die Attribute verwendet wird, um Zugriff wie oben durch einige getan Hilfsmethode.
Dann können Sie etwas entlang der Linien von diesem tun:
@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
}
}
Diese sehr bequem wird, wie ich einen Servlet-Filter verwenden, um sicherzustellen, dass der Benutzer über die entsprechenden Berechtigungen hat den Dienst zuzugreifen, bevor die Daten vorbei, und dann kann ich die eingehenden Daten analysieren (oder laden, oder was auch immer) und sie in das zu ladende Attribut ablegen.
Wenn Sie die oben Provider
Ansatz wollen, und Sie wollen die Basisklasse für die Attribute zugreifen, dann hier gehen Sie:
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: Ich dachte an meine Umsetzung von AbstractAttributeInjectableProvider
, die selbst eine generische Klasse ist, die nur existiert, um AttributeInjectable
's für einen gegebenen Typ, Class<T>
und die gelieferte AttributeParam
bereitzustellen. Es ist viel einfacher, eine nicht-abstract
Implementierung bereitzustellen, die ihren Typ (Class<T>
) mit jedem angeforderten AttributeParam
angibt, wodurch eine Reihe von Nur-Konstruktor-Implementierungen vermieden wird, die den Typ für Sie bereitstellen. Dies vermeidet auch, dass Sie Code für jeden einzelnen Typ schreiben müssen, den Sie mit der Annotation verwenden möchten.
@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());
}
}
Hinweis: Jeder Injectable
einmal beim Start instanziiert wird und nicht pro Anfrage, aber sie sind auf jeden eingehenden Request aufgerufen.
Instantiierung der Provider als Bohnen (oder sie automatisch abgeholt) scheint sie abgeholt zu bekommen, aber ich bin aus irgendeinem Grund gezwungen, sie Singletons zu machen. Haben Sie eine Idee, warum ich den "PerRequestTypeInjectiveProvider" -Singleton auf einen Bereich setzen muss? Wenn ich es in einen Anforderungsbereich dränge, schlägt es mit einer Ausnahme fehl. Das Beispiel war das ursprüngliche Beispiel eines injizierbaren Providers, das mich darauf gebracht hat. Die von ihm bereitgestellten Codebeispiele sind nicht die besten (er kombiniert den "Provider" mit dem "Injectable"). Es ist dumm, dass Jersey versuchte, das 'ServletRequest' wegzufiltern. – pickypg
Aktualisiert mit mehr Informationen. Kurz gesagt, der "Provider", der ein Singleton ist, ist ein Merkmal, aber für jede Anfrage sollte ein neuer Wert bereitgestellt werden. – calvinkrishy
Ah ha. Ich habe den Zweck des 'PerRequestTypeInjectiveProvider' falsch verstanden. Jersey konstruiert sofort die 'Injectible's, die es nach dem Scannen der verschiedenen Restful Services, die es steuert, benötigt. Ich nahm "PerRequest", um zu bedeuten, dass der "Provider" mit jeder gegebenen Anfrage überprüft würde. Ich wollte den 'Provider' verwenden, um' HttpServletRequest' in ein neues 'AttributeInjectable' (meinen eigenen Typ) zu injizieren, um 'HttpServletRequest # getAttribute (String)' mit dem Namen zu extrahieren, der dem 'AttributeParam' gegeben wurde. Auf diese Weise können meine Restful Services Attribute sauber injiziert bekommen. – pickypg