Джерси: InjectableProvider не поднял - Spring
В настоящее время я пытаюсь создать InjectableProvider
с Джерси, но я не могу заставить Джерси забрать его.
Я не могу найти реальных примеров его использования или даже как его получить, кроме использования аннотации @Provider
для реализации. Человек, который, казалось бы, написал его в Джерси, подразумевал в некоторых сообщениях, что этого достаточно, чтобы его поднять.
Нужно ли мне указывать какой-либо служебный файл SPI или добавлять его к некоторому factory где-нибудь?
Примечание. Я работаю в Glassfish 3.1 и используя Spring 3.1. Кажется разумным, что Spring может каким-то образом взять на себя автоматическую загрузку Provider
s. Однако я просто не знаю. Я вообще не использую 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} {@link HttpServletRequest}.
* @return Never {@code null}. Should never be blank.
*/
String value();
}
Ссылка ссылка от разработчика в Джерси.
ОБНОВЛЕНИЕ: calvinkrishy указал на два недостатка в моем мышлении.
Во-первых, я предположил, что Джерси начнет сканирование на @Provider
после того, как его отправит традиционный твитер Spring: com.sun.jersey.spi.spring.container.servlet.SpringServlet
. Это было в основном неправильным; он начинает сканирование, но он ищет Spring beans, у которого есть аннотация.
Во-вторых, я предположил, что PerRequestTypeInjectableProvider
будет запрашивать каждый входящий запрос для Injectable
для обработки аннотации, которую он контролирует. Это тоже было неправильно. PerRequestTypeInjectableProvider
создается при запуске, как и ожидалось, но Джерси затем немедленно просит Injectable
обрабатывать данную аннотацию с данным type
, которую он определяет, сканируя Restful Services, которые у нее есть, - на этом этапе - - решил, что он управляет (то есть, все они).
Разница между PerRequestTypeInjectableProvider
и SingletonTypeInjectableProvider
, по-видимому, заключается в том, что в полученном Injectable
есть значение, не работающее для него (singleton), или каждый раз он просматривает значение (для каждого запроса) что позволяет изменять значение для каждого запроса.
Это бросило в мои планы меньший ключ, заставив меня сделать некоторую дополнительную работу в моем 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
создается только для уникальной аннотации/типа. Поскольку я не могу этого сделать, я делаю это для поиска по значениям, который использует Spring RequestContextFilter
singleton, который предоставляет механизм ThreadLocal
для безопасного извлечения HttpServletRequest
(помимо прочего, связанных с текущим запросом).
<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
}
}
Это становится очень удобным, поскольку я использую Servlet Filter, чтобы гарантировать, что у пользователя есть соответствующие права доступа к службе перед передачей данных, а затем я могу анализировать входящие данные (или загружать их или что-то еще) и выгружать их в атрибут для загрузки.
Если вам не нужен описанный выше подход 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
, который сам по себе является общим классом, который существует только для предоставления AttributeInjectable
для данного типа, Class<T>
и поставляемого AttributeParam
, Гораздо проще обеспечить реализацию не abstract
, которая сообщает свой тип (Class<T>
) с каждым запрошенным AttributeParam
, тем самым избегая кучки реализаций только для конструкторов, предоставляющих вам тип. Это также позволяет избежать необходимости писать код для каждого отдельного типа, который вы хотите использовать с аннотацией AttributeParam
.
@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 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
Как вы инициализируете Джерси?
Я предполагаю, что вы используете Джерси, используя свиток-джерси - spring. В этом случае Джерси по умолчанию будет инициализировать с помощью Spring beans, и поэтому ваш Provider
должен быть Spring bean. Попробуйте добавить @Named
(или если вы не используете atinject @Component
или одну из аннотаций Spring) для своего Provider
.
Пример использования инъецируемых поставщиков.
Обновлено. Более ясность в отношении объема инъекции:
Provider
должен быть Singleton, как и для всех практических целей, a factory с привязкой к нему, и нет необходимости создавать factory для каждого запроса. Само по себе инъекция будет выполняться по запросу. Другими словами, метод 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);
}
};
}
}
Обновлено. Чтобы предоставить дополнительную информацию о типах инъекций и контекстах, доступных в Джерси.
Как вы, вероятно, уже догадались, если все, что вам нужно, это доступ к HttpServletRequest
, то просто прямое вложение его в ваш Resource
или Provider
с помощью аннотации @Context
вы получите это.
Однако для передачи этих значений в Injectable необходимо использовать AssistedProvider
или использовать подход, аналогичный вашему. Но опять-таки вы можете уменьшить это, если встраиваете определение Injectable
в Поставщик и вводите HttpServletRequest
в класс Provider
. В этом случае Injectable
сможет получить доступ к экземпляру HttpServletRequest
(с момента его появления в области видимости). Я просто обновил свой пример, чтобы показать этот подход.
Инъекция с использованием PerRequestTypeInjectableProvider
и SingletonTypeInjectableProvider
- это не единственные два параметра, которые вы должны вводить значения в свои ресурсы. Вы также можете вводить значения *Param
с помощью StringReaderProvider
. Очевидно, что такая инъекция является областью применения.
@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
чувствует себя чище.