25

So I have seen this question:

Spring dependency injection to other instance

and was wondering if my method will work out.

1) Declare beans in my Spring application context

    <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="initialSize" value="${jdbc.initialSize}" />
        <property name="validationQuery" value="${jdbc.validationQuery}" /> 
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
    </bean>

    <bean id="apiData" class="com.mydomain.api.data.ApiData">
        <property name="dataSource" ref="dataSource" />
        <property name="apiLogger" ref="apiLogger" />
    </bean>

    <bean id="apiLogging" class="com.mydomain.api.data.ApiLogger">
        <property name="dataSource" ref="dataSource" />
    </bean>

2) Override my servlet's init method as shown:

    @Override
    public void init(ServletConfig config) throws ServletException {
       super.init(config);

       ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

       this.apiData = (ApiData)ac.getBean("apiData");
       this.apiLogger = (ApiLogger)ac.getBean("apiLogger");
    }

Will this work or is Spring not yet ready to deliver beans to my servlet at this point in the web applications deployment? Do I have to do something more traditional like putting the beans in web.xml?

Community
  • 1
  • 1
thatidiotguy
  • 8,701
  • 13
  • 60
  • 105
  • Is there a reason that you are not using a Context-param and an initialization listener? From there you can look up the ApplicationContext from the ServletContext. – bh5k Sep 11 '13 at 15:53
  • @bh5k I am working on some legacy code that actually has a custom servlet. I have not worked with them before, so anything related to them is a bit foreign to me. Usually I rely heavily on the Spring library to do all this back room work. – thatidiotguy Sep 11 '13 at 15:54
  • 1
    You should still be able to do this: http://stackoverflow.com/questions/6451377/loading-context-in-spring-using-web-xml – bh5k Sep 11 '13 at 15:56
  • Load your context using a Context-Listener and then look it up in the servlet. – bh5k Sep 11 '13 at 15:57
  • This should be exactly what you are trying to do: http://stackoverflow.com/questions/6414373/load-spring-bean-into-a-servlet – bh5k Sep 11 '13 at 15:58
  • This may help: http://www.tugay.biz/2016/03/web-app-with-spring-core-only.html – Koray Tugay Nov 06 '16 at 19:38

4 Answers4

43

I wanted to leverage on the solution provided by Sotirios Delimanolis but adding transparent autowiring to the mix. The idea is to turn plain servlets into autowire-aware objects.

So I created a parent abstract servlet class that retrieves the Spring context, gets and autowiring-capable factory and uses that factory to autowire the servlet instances (the subclasess, actually). I also store the factory as an instance variable in case the subclasses need it.

So the parent abstract servlet looks like this:

public abstract class AbstractServlet extends HttpServlet {

    protected AutowireCapableBeanFactory ctx;

    @Override
    public void init() throws ServletException {
        super.init();
        ctx = ((ApplicationContext) getServletContext().getAttribute(
                "applicationContext")).getAutowireCapableBeanFactory();
        //The following line does the magic
        ctx.autowireBean(this);
    }
}

And a sevlet subclass looks like this:

public class EchoServlet extends AbstractServlet {

    @Autowired
    private MyService service;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {
        response.getWriter().println("Hello! "+ service.getMyParam());
    }
}

Notice the only thing EchoServlet needs to do is to declare a bean just in common Spring practice. The magic is done in the init() method of the superclass.

I haven't tested it thoroughly. But it worked with a simple bean MyService that also gets a property autowired from a Spring-managed properties file.

Enjoy!


Note:

It's best to load the application context with Spring's own context listener like this:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Then retrieve it like this:

WebApplicationContext context = WebApplicationContextUtils
    .getWebApplicationContext(getServletContext());
ctx = context.getAutowireCapableBeanFactory();
ctx.autowireBean(this);

Only spring-web library needs to be imported, not spring-mvc.

stop-cran
  • 4,229
  • 2
  • 30
  • 47
Agustí Sánchez
  • 10,455
  • 2
  • 34
  • 25
29

What you are trying to do will make every Servlet have its own ApplicationContext instance. Maybe this is what you want, but I doubt it. An ApplicationContext should be unique to an application.

The appropriate way to do this is to setup your ApplicationContext in a ServletContextListener.

public class SpringApplicationContextListener implements ServletContextListener {
        @Override
    public void contextInitialized(ServletContextEvent sce) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        sce.getServletContext().setAttribute("applicationContext", ac);            
    }
    ... // contextDestroyed
}

Now all your servlets have access to the same ApplicationContext through the ServletContext attributes.

@Override
public void init(ServletConfig config) throws ServletException {
   super.init(config);

   ApplicationContext ac = (ApplicationContext) config.getServletContext().getAttribute("applicationContext");

   this.apiData = (ApiData)ac.getBean("apiData");
   this.apiLogger = (ApiLogger)ac.getBean("apiLogger");
}
JuanMoreno
  • 2,498
  • 1
  • 25
  • 34
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • 1
    This looks like exactly what I am trying to do. I will try out the code and report back. Thank you for taking the time to explain everything. Oh, do I need to add anything to my `web.xml` or application context to get that listener to do its work? – thatidiotguy Sep 11 '13 at 16:20
  • @thatidiotguy You're welcome. Consider looking into `ContextLoaderListener`, a class provided by Spring-mvc that does precisely what I just described, and more. – Sotirios Delimanolis Sep 11 '13 at 16:21
  • Sorry, I edited my comment, I feel like I need to add something to one of my xml files to get that listener to work. Is this not the case? – thatidiotguy Sep 11 '13 at 16:23
  • @thatidiotguy You need to add a `` element. Or with servlet 3.0, you can annotate the class with `@WebListener`. – Sotirios Delimanolis Sep 11 '13 at 16:24
  • 7
    It's bets to let Spring's own web infrastructure to load the application context with the following web.xml snippet: ` contextConfigLocation classpath:applicationContext.xml org.springframework.web.context.ContextLoaderListener ` then retrieve with `WebApplicationContextUtils .getWebApplicationContext(getServletContext())`. That only requires spring-web, not spring-mvc. – Agustí Sánchez Feb 20 '14 at 23:52
  • Spring already stores its context factory inside the servlet context, so no need to do it ourselves. Just call: ```getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)``` to access the AC from inside the servlet. – Pavel Lechev Aug 02 '16 at 12:33
  • Wow Agusti i did not get that I could just call WebApplicationContextUtils .getWebApplicationContext(getServletContext()).getBean("WHATEEVER IOC BEAN I WANT"). That is AWESOME. So I needed to add the Apache File Upload Servlet, and Spring 2.5 was robbing its needed formItems (List formItems = upload.parseRequest(request);) and this way I just register the Apache Servlet in web.xml, and no IOC, just get the beans I need with your system. Thanks!!! – tom Apr 27 '18 at 20:34
8

The answers here so far only worked partly for me. Especially classes with @Configuration annotation were ignored and I did not want to use an xml configuration file. Here is what I have done to get injection working working soley with an Spring (4.3.1) annotation based setup:

Bootstrap an AnnotationConfigWebApplicationContext in web.xml under web-app. As parameter you need the contextClass and the contextConfigLocation (your annotated config class):

<context-param>
    <param-name>contextClass</param-name>
    <param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
  </param-value>
</context-param>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.example.config.AppConfig</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Then overwrite the init method in the servlet. I use an abstract class which extends HttpServlet, so I don't have to repeat it in every servlet:

@Configurable
public abstract class MySpringEnabledServlet extends HttpServlet
{
  @Override
  public void init(
      ServletConfig config) throws ServletException
  {
    super.init(config);
    SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
  }
[...]
}

and finally I have my main configuration in the AppConfig class mentioned in web.xml:

@Configuration
@ComponentScan(basePackages = "com.example")
@Import(
{ SomeOtherConfig.class })
public class AppConfig
{
}

The dependend classes are annotated:

@Component
public class AnnotatedClassToInject

and injected via autowiring in my servlet:

@Autowired
private AnnotatedClassToInject myClass;
Arigion
  • 3,267
  • 31
  • 41
1

Spring is independent of Servlet startup. Right after spring reads the bean xml it will be ready to deliver the beans. So right after below statement, beans are already available

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

Also as pointed by @LuiggiMendoza each ApplicationContext will create/maintain their own beans so its always good to create ApplicationContext once and reuse it from different servlets (as opposed to creating them inside the init() method of a Servlet)

sanbhat
  • 17,522
  • 6
  • 48
  • 64
  • Note that the beans handled by this `ApplicationContext` will be available only to that servlet. If you execute this code in other servlets, `ApiData` bean will be a totally different class instance in each servlet. – Luiggi Mendoza Sep 11 '13 at 15:50
  • it depends on whether the bean is defined as singleton or not.. I think OP is concerned whether beans will be available or not – sanbhat Sep 11 '13 at 15:52
  • I was under the impression that the servlet class will start before Spring, but you are saying that code will start the whole process huh. Good to have to dig into the inner workings of this stuff sometimes. – thatidiotguy Sep 11 '13 at 15:53
  • 1
    Even if they're defined as singleton, they will belong to different `ApplicationContext`s thus being different beans. – Luiggi Mendoza Sep 11 '13 at 15:53
  • Thanks @LuiggiMendoza I have edited the post, hope it makes sense now – sanbhat Sep 11 '13 at 15:58