Заполнение запроса spring областью bean в файле ContainerRequestFilter

Я написал сервис отдыха с использованием джерси 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, а не одно и то же для запрос.

Так что я делаю неправильно?

Ответ 1

После игры с этим я обнаружил, что параметр proxyMode в аннотации @Scope все равно сделал трюк. Итак, это решение. Прокси-сервер будет заботиться о создании нового экземпляра каждый раз и будет следить за тем, чтобы он был охвачен запросом.