Я написал сервис отдыха с использованием джерси 1.13 и spring 3.1.1, который работает на tomcat 6. В tomcat я использую область, которая будет выполнять аутентификацию. В моем приложении мне нужен текущий пользователь, но я не хочу получать доступ к SecurityContext из трикотажа на каждом ресурсе. Я хочу добавить объект ApplicationConfig с ограниченным запросом в мои ресурсы для отдыха, который будет содержать текущего пользователя. Позже я могу расширить этот класс, чтобы содержать больше параметров настройки уровня запроса. Мне кажется, это хорошая абстракция.
@Component
@Scope(value = "request")
public class ApplicationConfig
{
private String userCode;
public String getUserCode()
{
return this.userCode;
}
public void setUserCode(String userCode)
{
this.userCode = userCode;
}
}
Я создал ApplicationConfigManager для обеспечения доступа к конфигурации.
@Component
public class ApplicationConfigManager
{
@Autowired
public ApplicationConfig applicationConfig;
public ApplicationConfig getApplicationConfig()
{
return this.applicationConfig;
}
}
Диспетчер конфигураций приложений определяется как singleton (по умолчанию), но ApplicationConfig должен быть областью запроса, поэтому аннотация @Scope.
Я использую (трикотаж) ContainterRequestFilter, чтобы установить пользователя в объект конфигурации приложения.
@Component
@Provider
public class ApplicationConfigFilter implements ResourceFilter, ContainerRequestFilter
{
@Autowired
private ApplicationConfigManager applicationConfigManager;
@Override
public ContainerRequest filter(ContainerRequest request)
{
this.applicationConfigManager.getApplicationConfig().setUserCode(
request.getSecurityContext().getUserPrincipal().getName()
);
return request;
}
@Override
public ContainerRequestFilter getRequestFilter()
{
return this;
}
@Override
public ContainerResponseFilter getResponseFilter()
{
return null;
}
}
Чтобы зарегистрировать этот фильтр, я создал ResourceFilterFactory
@Component
@Provider
public class ResourceFilterFactory extends RolesAllowedResourceFilterFactory
{
@Autowired
private ApplicationConfigFilter applicationConfigFilter;
@Override
public List<ResourceFilter> create(AbstractMethod am)
{
// get filters from RolesAllowedResourceFilterFactory Factory!
List<ResourceFilter> rolesFilters = super.create(am);
if (null == rolesFilters) {
rolesFilters = new ArrayList<ResourceFilter>();
}
// Convert into mutable List, so as to add more filters that we need
// (RolesAllowedResourceFilterFactory generates immutable list of filters)
List<ResourceFilter> filters = new ArrayList<ResourceFilter>(rolesFilters);
filters.add(this.applicationConfigFilter);
return filters;
}
}
Я активировал этот factory, установив его в web.xml
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.mypackage</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>com.mypackage.ResourceFilterFactory</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Я также добавил этих слушателей, чтобы загрузиться в контекст spring и получить рабочую область запроса
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Это то, что я вложил в свой контекст приложения, чтобы заставить функцию сканирования работать и определить объект конфигурации приложения (я думаю, это даже не нужно, поскольку spring найдет его автоматически)
<context:annotation-config/>
<context:component-scan base-package="com.mypackage" />
<bean id="applicationConfig" class="com.mypackage.ApplicationConfig" scope="request"/>
А теперь моя проблема. Когда я запустил приложение, spring будет загружен, и он будет внедрять объект ApplicationConfig в ApplicationConfigManager, который вводится в ApplicationConfigFilter.
В этот момент он выдает исключение:
.
.
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually o
perating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:40) ~[spring-web-3.1.1.RELEASE.jar:3.1.1.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:328) ~[spring-beans-3.1.1.RELEASE.jar:3.1.1.RELEASE]
... 57 common frames omitted
Это исключение довольно ясное, и я думаю, что это означает, что приложение ApplicationConfig с запросом не может быть введено, потому что запрос еще не отправлен.
Итак, я spring должен создавать экземпляр объекта ApplicationConfig ТОЛЬКО при отправке запроса, а не во время запуска приложения. Я искал решения и обнаружил, что инъекция области с запросом bean в singleton bean не очень логична. В любом случае, я всегда получаю один и тот же объект. Решение заключается в использовании прокси-сервера, поэтому я изменил аннотацию @Scope на класс ApplicationConfig на этот
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
Это должно дать мне новый объект ApplicationConfig для каждого запроса.
Приложение теперь запускается просто отлично (похоже, он не создает экземпляр ApplicationConfig), но когда я отправляю запрос на мой сервис отдыха, я обнаружил, используя отладку, что у меня есть разные объекты ApplicationConfig, а не одно и то же для запрос.
Так что я делаю неправильно?