Using Spring request scope beans instead of threadlocal

I was adding out of container integration test support to an existing project and noticed that application was using threadlocals in controller to get logged in user info. In fact, an application filter authenticates/identifies the user and put it in threadlocal for the rest of the application to use. It was good first step but definitely has limitations from unit testing perspective.

Questions popped in mind were:

  • How to write unit tests and mock up threadlocals
  • can we use spring request scope beans to store user info as we can use spring dependency injections nicely
  • Since application filter we had was operating outside the scope of spring dispatcher servlet, how to get spring request scoped bean and store user info from application filter. [ For the curious, We are not using spring security ]

Finally, I took following steps:

Step1 - Convert ThreadLocal code to Service

UserInfoService.java
public interface UserInfoService {

  public void setUserInfo(User user);
  
  public User getUserInfo();
  
}
UserInfoServiceImpl.java
public class UserInfoServiceImpl implements UserInfoService {

  private User user;
  
  public void setUserInfo(User pUser) {
    this.user = pUser;    
  }

  public User getUserInfo() {
    return this.user;
  }  
  
}

Step2 - Declare the bean as request scope

ServiceConfig.java
@Configuration
public class ServiceConfig {
 
  // Spring hands over proxied object as when spring is initialized, 
  // request scope object is not initialized.
  // why? because request scope bean will be created when httprequest
  // will be active.
  @Bean
  @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
  public UserInfoService userService() {
      return new UserInfoServiceImpl();
  }

Step3 - Register the RequestContextListener

In case of out of container request scope bean access, register the RequestContextListener.

WebApplicationInitializer.java
@Order(2)
public class WebApplicationInitializer extends
		AbstractAnnotationConfigDispatcherServletInitializer {

	
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		
		// Add authFilter Filter
		FilterRegistration authFilter = servletContext
			.addFilter("authFilter", new authFilter());
		authFilter.addMappingForUrlPatterns(null, true, "*.do");
		authFilter.setInitParameter("appName", "employee");
		
		super.onStartup(servletContext);
	}
	
	// Required for Request scoped beans otherwise you would get exception
	servletContext.addListener(new RequestContextListener());

}

We would run into below exception, if we don’t add requestContextListener:

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

Step4 - Initialize your Service

Initialize the threadLocal as per your business logic. Get the access to bean via WebApplicationContextUtils.

AuthFilter.java
public class AuthFilter implements Filter {

	private UserInfoService userInfoService;

	private final static Logger LOGGER = LoggerFactory.getLogger(UserFilter.class.getName());

	public void init(FilterConfig filterConfig) {
		
		// Initialize the User info service so that we can add current logged in user info
		ApplicationContext ctx = WebApplicationContextUtils
		      .getRequiredWebApplicationContext(filterConfig.getServletContext());
		this.userInfoService = ctx.getBean(UserInfoService.class);

	}
	
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
			throws IOException, ServletException {
		
		HttpServletRequest req = (HttpServletRequest) request;
		
		// Application auth logic here
		...
		// SET USER INFO IN SPRING REQUEST SCOPED BEAN
		userInfoService.setUserInfo(user);

		chain.doFilter(request, response);
	}
}
		

Step5 - Inject request scope bean via constructor injection

EmployeeController.java
@Controller
public class EmployeeController {

	private UserInfoService userInfoService;
	
	@Autowired
	public EmployeeController(UserInfoService pUserInfoService) {
	      this.userInfoService = pUserInfoService;
	}
	
	@RequestMapping(value = "/user", method = RequestMethod.GET)
	public String getUserInfo(HttpServletRequest request) {

		// Get the user info from current request thread via Thread Local
		// User user = CurrentRequestThread.getUserInfo();
		
		// Get the user info from spring request scope bean
	    User user = userInfoService.getUserInfo();
	    ...
	}
}
			

After this we can easily mock UserInfoService in our unit tests and can inject it as a service in other service classes.

Version History


Date Description
2016-06-26    Initial Version