@Deprecated: How to Inject Spring Beans into Servlets.

This post has been Deprecated, see this post instead

A couple of months ago I was working on an application that used Servlets as entry points to an application, sort of like a web service. This was a new application and I decided to use the Spring framework for dependancy injection and to make using Hibernate easier.

I am fairly new to Spring and never needed to inject any spring beans into servlet before, and I thought there must be a way to do it. However after browsing through a number of websites, blog posts and forum posts, it appeared that there wasn’t a clean spring way to do this.

Solution 1

In the end I read somewhere that you could inject spring beans into the ServletContext, so I took this route.

With this you have to declare this little piece in your applicationContext.xml

<bean class="org.springframework.web.context.support.ServletContextAttributeExporter">
 <property name="attributes">
     <map>
         <!-- inject the following beans into the servlet
context so the servlets can access them. -->
         <entry key="myBeanFromSpring">
             <ref bean="myBeanFromSpring"/>
         </entry>
     </map>
 </property>
</bean>

As you can see this puts the spring bean myBeanFromSpring into the servlet context. Therefore in your servlet code you can do the following…

protected void doGet(HttpServletRequest reqest, HttpServletResponse response)
                                         throws ServletException, IOException {

   MyBeanFromSpring myBean = MyBeanFromSpring)getServletContext().getAttribute("myBeanFromSpring");
   myBean.someOperation();
   ...
}

Although this works it still doesn’t feel very spring like.

Solution 2

There is another way to achieve the same thing. You can use WebApplicationContext and get the beans directly from Spring without having to inject anything into the servlet context.

void doGet(HttpServletRequest reqest, HttpServletResponse response)
                                         throws ServletException, IOException {

   WebApplicationContext springContext =
       WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   MyBeanFromSpring myBean =(MyBeanFromSpring)springContext.getBean("myBeanFromSpring");
   myBean.someOperation();
   ...
}

Although this achieves the same thing and is probably more concise than Solution 1, it still is not achieving what I initially wanted, which was dependancy injection into the servlet.

Solution 3

Although I stayed with Solution 1 for the application it got me thinking. So I set out to write a sub class of HttpServlet that would use the servletContext solution and use reflection to figure out if the servlet had any setters on that spring should call.

My original servlet that had to do all the stuff with getting the servlet context suddenly looks a lot more spring-like…


import name.kayley.springutils.SpringDependencyInjectionServlet;

public class MyServlet extends SpringDependencyInjectionServlet {

   private MyBeanFromSpring myBean;

   public void setMyBeanFromSpring(MyBeanFromSpring myBean) {
       this.myBean = myBean;
   }

   protected void doGet(HttpServletRequest reqest, HttpServletResponse response)
                                           throws ServletException, IOException {

       myBean.someOperation();
   }
}

And here is the source of the SpringDependencyInjectionServlet, go easy on it, Its the first version and it passes the unit tests i have written for it, use it at your own risk or as a starting point, but like I said it appears to work ok so far.

I have attempted to keep in with the spring autowiring of giving 2 options, autowire by type and by name, so you can override autowireByType if you want to autowire by name. I think they need some work as I believe that autowire by name should go off the name of the parameter to the setter method and not the setter method name itself… keep coming back for updates.


package name.kayley.springutils;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;

public class SpringDependencyInjectionServlet extends HttpServlet {

   private static final Logger logger =
Logger.getLogger(SpringDependencyInjectionServlet.class.getName());


   protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
       Enumeration attributeNames = getServletContext().getAttributeNames();

       while (attributeNames.hasMoreElements()) {

           String name = (String) attributeNames.nextElement();
           logger.log(Level.INFO, "attempting to autowire " + name);

           autowire(name);
       }

       super.service(request, response);
   }

   protected boolean autowireByType() {
       return true;
   }

   private void autowire(String name) {
       if (name != null) {
           Object attribute = getServletContext().getAttribute(name);
           Class c = getClass();
           while (c != null && c != c.getSuperclass()) {
               try {
                   if (autowireByType()) {
                       if (byType(c, attribute)) {
                           break;
                       }
                       else {
                           c = c.getSuperclass();
                       }
                   }
                   else {
                       if (byName(c, name, attribute)) {
                           break;
                       }
                       else {
                           c = c.getSuperclass();
                       }
                   }
               }
               catch (NoSuchMethodException e) {
                   c = c.getSuperclass();
               }
           }
       }
   }

   private boolean byName(Class c, String name, Object attribute)
       throws NoSuchMethodException {
       boolean success = false;

       if (attribute != null) {

           Method[] methods = c.getDeclaredMethods();
           for (Method method : methods) {
               if (method.getName().equals(getMethodName(name))) {
                   Class[] paramTypes = method.getParameterTypes();

                   if (paramTypes.length == 1) {
                       success = invokeSpringBeanSetter(method, attribute);
                   }
               }
           }
       }
       return success;
   }

   private boolean byType(Class c, Object attribute) {
       boolean success = false;

       if (attribute != null) {
           Method[] methods = c.getDeclaredMethods();

           for (Method method : methods) {
               Class[] paramTypes = method.getParameterTypes();

               Class attributeClass = attribute.getClass();
               if (paramTypes.length == 1 &&
          paramTypes[0].equals(attributeClass)) {
                   boolean succeeded = invokeSpringBeanSetter(method,attribute);
                   if (!success && succeeded) {
                       success = succeeded;
                   }
               }
           }
       }
       return success;
   }

   private boolean invokeSpringBeanSetter(Method method, Object attribute) {
       boolean success = false;
       try {
           method.invoke(this, attribute);
           success = true;
       }
       catch (Exception e) {
           // TODO do we care?
       }
       return success;
   }

   private String getMethodName(String contextName) {
       return "set" + StringUtils.capitalize(contextName);
   }
}

Technorati Tags: , , , ,

Advertisements

11 Replies to “@Deprecated: How to Inject Spring Beans into Servlets.”

  1. Thank you, this is exactly what I was looking for. I’m amazed that SpringSource has not addressed the lack of compatibility with injecting beans into servlets.I do have one question. What jar file is WebApplciationContext in? I’m getting a compile error.Thanks again.Dave

  2. Thanks a tone Andy! After one full day’s search finally i could accomplish what I was trying to do.Just one suggestion. if the bean that you are trying to inject is a concrete class and bean defined the servlet is interface, the code does not work. I changed one line as below to make it work. May be you can update your codebase accordingly.From: if (paramTypes.length == 1 && paramTypes[0].equals(attributeClass) ){ to:if (paramTypes.length == 1 && (paramTypes[0].equals(attributeClass) || paramTypes[0].isAssignableFrom(attributeClass) ) ){

  3. @davidyou’re getting null probably because you havent set it web application context up in your web.xml….<servlet> <servlet-name>context</servlet-name> <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class> <load-on-startup>2</load-on-startup> </servlet> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/classes/applicationContext.xml </param-value> </context-param>

  4. Have you ever considered using Spring’s ServletContextAttributeExporter? This may be a more generic and better solution since it allows you to export any number of Spring-defined beans into your servlet context as context attributes – while still allowing your servlet remain agnostic of Spring. I agree that some Spring features are not well documented, unfortunately.Another comment: I would recommend putting a try/catch (Thowable t) around the body of the doGet() method in a servlet, and making all exceptions you need to throw (if any) inside the nested methods runtime exceptions. This way, you don’t need to worry about catching anything prior to that – especially b/c there is no sense in that anyway, and your servlet will never blow up into the user’s face. You shouldn’t really care about the nature of the underlying exception. All you need to know is that your servlet failed to satisfy the request. Just do whatever you need to do in the catch block to process the failure, navigate to the error page, or display an user-friendly error message, log the full stack trace, etc. Otherwise, your servlet will puke out a nasty stack trace all over the page in a case of any un-handled RT exception. Just a suggestion – as a general good practice. Don’t listen to those who say you shouldn’t use generic catches. In most cases – that’s exactly what you need, especially at the component boundaries – such as the servlet’s doGet(). ;)Good luck.

  5. Spring does pretty much address this with the HttpRequestHandler interface. Basically you declare a HttpRequestHandlerServlet in the web.xml and this delegates to a full fledged spring managed bean that matches the servlet-name.

  6. AFAIK, servlet containers create only one instance of each servlets and therefore if there are non-singleton beans that are being injected to the servlet, thread-safety is going to be an issue. A quick fix may be for you to put those autowiring logic in init() rather than in service().

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s