Populating a spring request scoped bean in a ContainerRequestFilter

China☆狼群 提交于 2021-01-26 19:39:16

问题


I wrote a rest service using jersey 1.13 and spring 3.1.1 which runs on tomcat 6. In tomcat I'm using a realm which will do the authentication. In my application I need the current user but I don't want to access the SecurityContext from jersey in every resource. I want to inject a request scoped ApplicationConfig object in my rest resources which will contain the current user. Later I can extend this class to contain more request level configuration parameteters. This seems a nice abstraction to me.

@Component
@Scope(value = "request")
public class ApplicationConfig
{
    private String userCode;

    public String getUserCode()
    {
        return this.userCode;
    }

    public void setUserCode(String userCode)
    {
        this.userCode = userCode;
    }
}

I created a ApplicationConfigManager to provide access to the configuration.

@Component
public class ApplicationConfigManager
{
    @Autowired
    public ApplicationConfig applicationConfig;

    public ApplicationConfig getApplicationConfig()
    {
        return this.applicationConfig;
    }
}

The application config manager is defined as a singleton (default) but the ApplicationConfig should be request scope, hence the @Scope annotation.

I'm using a (jersey) ContainterRequestFilter to set the user on the application config object.

@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;
    }
}

To register this filter I created a 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;
    }
}

I activated this factory by setting this into the 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>

I also added these listeners to bootstrap the spring context and to get the request scope working

<listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

This is what I put in my application context to get the scanning feature working and to define the aplication config object (I guess this is not even necessary as spring will find it automagically)

<context:annotation-config/>
<context:component-scan base-package="com.mypackage" />

<bean id="applicationConfig" class="com.mypackage.ApplicationConfig" scope="request"/>

And now my problem. When I start the application spring will be bootstrapped and it will inject the ApplicationConfig object onto the ApplicationConfigManager which is injected onto the ApplicationConfigFilter.

At this point it throws the exception:

.
.
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

This exception is pretty clear and I think it means the request scoped ApplicationConfig cannot be injected because no request was sent yet.

So I spring should instantiate the ApplicationConfig object ONLY when a request is sent and not during application startup. I searched for solutions and found that injecting a request scoped bean in a singleton bean is not very logical. I would always get the same object anyway. The solution is to use a proxy so I changed the @Scope annotation on the ApplicationConfig class to this

@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)

This should give me a new ApplicationConfig object for every request.

The app now starts up just fine (it doesn't seem to instantiate the ApplicationConfig) but when I send a request to my rest service I found, using debugging, that I get different ApplicationConfig objects instead of the same one for the request.

So what am I doing wrong?


回答1:


After playing around with this I found that the proxyMode setting on the @Scope annotation did the trick anyway. So that is the solution. The proxy will take care in creating a new instance every time and will make sure it is request scoped.




回答2:


You can use @RequestScope annotation which does the same. It is just an abbreviation for:

@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)


来源:https://stackoverflow.com/questions/13136817/populating-a-spring-request-scoped-bean-in-a-containerrequestfilter

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!