Saturday, January 15, 2011

Spring - Using Beans in HTTP Servlet

So you want to use your beautiful and shining Spring Beans in ugly and ordinary HTTP Servlet? Don't be ashamed ;) Sometimes we all have to do it, and we do it in many different ways ;) Below you may find few examples.

Old-fashioned way :) - used by me till some beautiful Thursday morning ...
public class BeanTest extends HttpServlet {

    private EmployerManager employerManager;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Do something amazing with the Spring Bean ...
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext());
        employerManager = applicationContext.getBean("business-logic.EmployerManager", EmployerManager.class);
        super.init(config);
    }

}
As you see, you can access Spring WebApplicationContext and then the desired bean by its identifier. Main disadvantage of this method is the bean identifier hardcoded into Servlet code. Especially if you are performing automatic component scan in Spring (ex. <context:component-scan base-package="[...].logic.impl" />). In this case you have two choices:
  1. Define the identifier of the bean using annotations:
    
    @Service("business-logic.EmployerManager")
    public class DefaultEmployerManager implements EmployerManager {
       ...
    }
    
  2. Use default bean identifier created by Spring in your Servlet - see: Naming autodetected components
Now relax, close your eyes, think about the refactoring the code, and all those hardcoded names which you should change by hand. It's time to forget this method my Friend, as I did. :)

Thankfully there are many young people, with open mind, who don't use the old-fashioned ways of doing things, and invent their own, as did my colleague - Bartek. Below you may find method doing the same as the above one, but based on @Autowired annotation usage.
public class BeanTest extends HttpServlet {

    @Autowired
    private EmployerManager employerManager;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Do something amazing with the Spring Bean ...
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, config.getServletContext());
        super.init(config);
    }

}

SpringBeanAutowiringSupport will inject the appropriate Spring Beans into all your Servlet's properties marked with @Autowired annotation. That's all :) No hardcoding of bean names, no worries about future refactorings. Now relax, close your eyes, ...

10 comments:

  1. Hi,

    I tried your '@Autowired'-approach but it didn't work. Could it be that this only works when using Spring itself, and not SpringMVC.

    I have a webapplication that is built on SpringMVC and if I do the following:

    @Autowired
    private IConfigurationService config;

    it remains null although it is declared in the application-context.xml.

    Greetings,
    Bart

    ReplyDelete
  2. Let me summarize it :) - We've found the reason of Bart's problems together :) - hope that's will be useful for him and maybe for few more people out there :)

    ReplyDelete
  3. In Spring 2.5.6 you can achieve the same "autowiring" effect with the following call:

    SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext (this);

    ReplyDelete
  4. @Guy - it should work in similar way in Spring 3.0 - and just for the reference ;) - method suggested by you uses class loader keyed map: ContextLoader#currentContextPerThread, which is initialized by ContextLoaderListener - so with appropriate setup, using ContextLoaderListener, it should basically do the same :)

    ReplyDelete
  5. Thanks!
    Excellent Information!
    Solved my issue using JSF 2 with Spring 3 and Servlet.
    Thanks so much...

    ReplyDelete
  6. Somehow the beans are not getting injected. I have defined the context loader listener in the web.xml.

    ReplyDelete
  7. that works very well. but i'm interested in, why afterPropertiesSet() (implements InitializingBean) is not working.

    I had that error:

    15:21:40,015 ERROR [org.springframework.web.servlet.DispatcherServlet] (http-localhost-127.0.0.1-8080-6) Context initialization failed: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping#0': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'selectAttribute' defined in "/D:/java/jbosses/Jboss For OpenID/Jboss711/jboss-as-7.1.1.Final/standalone/deployments/eid-openid-idp-1.0-SNAPSHOT.ear/eid-openid-idp-web-1.0-SNAPSHOT.war/WEB-INF/classes/ge/id/idp/openid/server/controller/SelectAttribute.class": Invocation of init method failed; nested exception is 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.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:527) [spring-beans-3.0.5.RELEASE.jar:3.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456) [spring-beans-3.0.5.RELEASE.jar:3.0.5.RELEASE]

    ReplyDelete
  8. Whoooo HOoooo.... You are amazing! Thanks for sharing!

    ReplyDelete
  9. Did you read the Javadoc you link to? For SpringBeanAutoWiringSupport it says in bold

    "If there is an explicit way to access the ServletContext, prefer such a way over using this class. The WebApplicationContextUtils class..."

    An implementation respecting this:

    @Override
    public void init(ServletConfig config) throws ServletException {
    super.init(config);
    WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext())
    .getAutowireCapableBeanFactory().autowireBean(this);
    }

    ReplyDelete