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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With