224
votes

Is there a way to statically/globally request a copy of the ApplicationContext in a Spring application?

Assuming the main class starts up and initializes the application context, does it need to pass that down through the call stack to any classes that need it, or is there a way for a class to ask for the previously created context? (Which I assume has to be a singleton?)

17

17 Answers

176
votes

If the object that needs access to the container is a bean in the container, just implement the BeanFactoryAware or ApplicationContextAware interfaces.

If an object outside the container needs access to the container, I've used a standard GoF singleton pattern for the spring container. That way, you only have one singleton in your application, the rest are all singleton beans in the container.

124
votes

You can implement ApplicationContextAware or just use @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBean will have ApplicationContext injected, within which this bean is instantiated. For example if you have web application with a pretty standard contexts hierarchy:

main application context <- (child) MVC context

and SpringBean is declared within main context, it will have main context injected; otherwise, if it's declared within MVC context, it will have MVC context injected.

40
votes

Here's a nice way (not mine, the original reference is here: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

I've used this approach and it works fine. Basically it's a simple bean that holds a (static) reference to the application context. By referencing it in the spring config it's initialized.

Take a look at the original ref, it's very clear.

18
votes

I believe you could use SingletonBeanFactoryLocator. The beanRefFactory.xml file would hold the actual applicationContext, It would go something like this:

<bean id="mainContext" class="org.springframework.context.support.ClassPathXmlApplicationContext">
     <constructor-arg>
        <list>
            <value>../applicationContext.xml</value>
        </list>
     </constructor-arg>
 </bean>

And the code to get a bean from the applicationcontext from whereever would be something like this:

BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
BeanFactoryReference bf = bfl.useBeanFactory("mainContext");
SomeService someService = (SomeService) bf.getFactory().getBean("someService");

The Spring team discourage the use of this class and yadayada, but it has suited me well where I have used it.

11
votes

Before you implement any of the other suggestions, ask yourself these questions...

  • Why am I trying to get the ApplicationContext?
  • Am I effectively using the ApplicationContext as a service locator?
  • Can I avoid accessing the ApplicationContext at all?

The answers to these questions are easier in certain types of applications (Web apps, for example) than they are in others, but are worth asking anyway.

Accessing the ApplicationContext does kind of violate the whole dependency injection principle, but sometimes you've not got much choice.

7
votes
SpringApplicationContext.java

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * Wrapper to always return a reference to the Spring Application 
Context from
 * within non-Spring enabled beans. Unlike Spring MVC's 
WebApplicationContextUtils
 * we do not need a reference to the Servlet context for this. All we need is
 * for this bean to be initialized during application startup.
 */
public class SpringApplicationContext implements 
ApplicationContextAware {

  private static ApplicationContext CONTEXT;

  /**
   * This method is called from within the ApplicationContext once it is 
   * done starting up, it will stick a reference to itself into this bean.
  * @param context a reference to the ApplicationContext.
  */
  public void setApplicationContext(ApplicationContext context) throws BeansException {
    CONTEXT = context;
  }

  /**
   * This is about the same as context.getBean("beanName"), except it has its
   * own static handle to the Spring context, so calling this method statically
   * will give access to the beans by name in the Spring application context.
   * As in the context.getBean("beanName") call, the caller must cast to the
   * appropriate target class. If the bean does not exist, then a Runtime error
   * will be thrown.
   * @param beanName the name of the bean to get.
   * @return an Object reference to the named bean.
   */
  public static Object getBean(String beanName) {
    return CONTEXT.getBean(beanName);
  }
}

Source: http://sujitpal.blogspot.de/2007/03/accessing-spring-beans-from-legacy-code.html

6
votes

If you use a web-app there is also another way to access the application context without using singletons by using a servletfilter and a ThreadLocal. In the filter you can access the application context using WebApplicationContextUtils and store either the application context or the needed beans in the TheadLocal.

Caution: if you forget to unset the ThreadLocal you will get nasty problems when trying to undeploy the application! Thus, you should set it and immediately start a try that unsets the ThreadLocal in the finally-part.

Of course, this still uses a singleton: the ThreadLocal. But the actual beans do not need to be anymore. The can even be request-scoped, and this solution also works if you have multiple WARs in an Application with the libaries in the EAR. Still, you might consider this use of ThreadLocal as bad as the use of plain singletons. ;-)

Perhaps Spring already provides a similar solution? I did not find one, but I don't know for sure.

5
votes

Take a look at ContextSingletonBeanFactoryLocator. It provides static accessors to get hold of Spring's contexts, assuming they have been registered in certain ways.

It's not pretty, and more complex than perhaps you'd like, but it works.

5
votes

There are many way to get application context in Spring application. Those are given bellow:

  1. Via ApplicationContextAware:

    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    
    public class AppContextProvider implements ApplicationContextAware {
    
    private ApplicationContext applicationContext;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    }
    

Here setApplicationContext(ApplicationContext applicationContext) method you will get the applicationContext

ApplicationContextAware:

Interface to be implemented by any object that wishes to be notified of the ApplicationContext that it runs in. Implementing this interface makes sense for example when an object requires access to a set of collaborating beans.

  1. Via Autowired:

    @Autowired
    private ApplicationContext applicationContext;
    

Here @Autowired keyword will provide the applicationContext. Autowired has some problem. It will create problem during unit-testing.

4
votes

Note that by storing any state from the current ApplicationContext, or the ApplicationContext itself in a static variable - for example using the singleton pattern - you will make your tests unstable and unpredictable if you're using Spring-test. This is because Spring-test caches and reuses application contexts in the same JVM. For example:

  1. Test A run and it is annotated with @ContextConfiguration({"classpath:foo.xml"}).
  2. Test B run and it is annotated with @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Test C run and it is annotated with @ContextConfiguration({"classpath:foo.xml"})

When Test A runs, an ApplicationContext is created, and any beans implemeting ApplicationContextAware or autowiring ApplicationContext might write to the static variable.

When Test B runs the same thing happens, and the static variable now points to Test B's ApplicationContext

When Test C runs, no beans are created as the TestContext (and herein the ApplicationContext) from Test A is resused. Now you got a static variable pointing to another ApplicationContext than the one currently holding the beans for your test.

2
votes

Not sure how useful this will be, but you can also get the context when you initialize the app. This is the soonest you can get the context, even before an @Autowire.

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    private static ApplicationContext context;

    // I believe this only runs during an embedded Tomcat with `mvn spring-boot:run`. 
    // I don't believe it runs when deploying to Tomcat on AWS.
    public static void main(String[] args) {
        context = SpringApplication.run(Application.class, args);
        DataSource dataSource = context.getBean(javax.sql.DataSource.class);
        Logger.getLogger("Application").info("DATASOURCE = " + dataSource);
1
votes

Do autowire in Spring bean as below : @Autowired private ApplicationContext appContext;

you will the applicationcontext object.

0
votes

Please note that; the below code will create new application context instead of using the already loaded one.

private static final ApplicationContext context = 
               new ClassPathXmlApplicationContext("beans.xml");

Also note that beans.xml should be part of src/main/resources means in war it is part of WEB_INF/classes, where as the real application will be loaded through applicationContext.xml mentioned at Web.xml.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>META-INF/spring/applicationContext.xml</param-value>
</context-param>

It is difficult to mention applicationContext.xml path in ClassPathXmlApplicationContext constructor. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml") wont be able to locate the file.

So it is better to use existing applicationContext by using annotations.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }
}
0
votes

Approach 1: You can inject ApplicationContext by implementing ApplicationContextAware interface. Reference link.

@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

Approach 2: Autowire Application context in any of spring managed beans.

@Component
public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

Reference link.

0
votes

I use a simple, standardized way to allow external access to any of my own singleton Spring Beans. With this method, I continue to let Spring instantiate the Bean. Here's what I do:

  1. Define a private static variable of the same type as the enclosing class.
  2. Set that variable to this in each of the class's constructors. If the class has no constructors, add a default constructor in which to set the variable.
  3. Define a public static getter method that returns the singleton variable.

Here's an example:

@Component
public class MyBean {
    ...

    private static MyBean singleton = null;

    public MyBean() {
        ...
        singleton = this;
    }

    ...
    
    public void someMethod() {
        ...
    }

    ...

    public static MyBean get() {
        return singleton;
    }
}

I can then call someMethod on the singleton bean, anywhere in my code, via:

MyBean.get().someMethod();

If you are already subclassing your ApplicationContext, you can add this mechanism to it directly. Otherwise, you could either subclass it just to do this, or add this mechanism to any bean that has access to the ApplicationContext, and then use it to gain access to the ApplicationContext from anywhere. The important thing is that it is this mechanism that will let you get into the Spring environment.

-1
votes

I know this question is answered, but I would like to share the Kotlin code I did to retrieve the Spring Context.

I am not a specialist, so I am open to critics, reviews and advices:

https://gist.github.com/edpichler/9e22309a86b97dbd4cb1ffe011aa69dd

package com.company.web.spring

import com.company.jpa.spring.MyBusinessAppConfig
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.stereotype.Component
import org.springframework.web.context.ContextLoader
import org.springframework.web.context.WebApplicationContext
import org.springframework.web.context.support.WebApplicationContextUtils
import javax.servlet.http.HttpServlet

@Configuration
@Import(value = [MyBusinessAppConfig::class])
@ComponentScan(basePackageClasses  = [SpringUtils::class])
open class WebAppConfig {
}

/**
 *
 * Singleton object to create (only if necessary), return and reuse a Spring Application Context.
 *
 * When you instantiates a class by yourself, spring context does not autowire its properties, but you can wire by yourself.
 * This class helps to find a context or create a new one, so you can wire properties inside objects that are not
 * created by Spring (e.g.: Servlets, usually created by the web server).
 *
 * Sometimes a SpringContext is created inside jUnit tests, or in the application server, or just manually. Independent
 * where it was created, I recommend you to configure your spring configuration to scan this SpringUtils package, so the 'springAppContext'
 * property will be used and autowired at the SpringUtils object the start of your spring context, and you will have just one instance of spring context public available.
 *
 *Ps: Even if your spring configuration doesn't include the SpringUtils @Component, it will works tto, but it will create a second Spring Context o your application.
 */
@Component
object SpringUtils {

        var springAppContext: ApplicationContext? = null
    @Autowired
    set(value) {
        field = value
    }



    /**
     * Tries to find and reuse the Application Spring Context. If none found, creates one and save for reuse.
     * @return returns a Spring Context.
     */
    fun ctx(): ApplicationContext {
        if (springAppContext!= null) {
            println("achou")
            return springAppContext as ApplicationContext;
        }

        //springcontext not autowired. Trying to find on the thread...
        val webContext = ContextLoader.getCurrentWebApplicationContext()
        if (webContext != null) {
            springAppContext = webContext;
            println("achou no servidor")
            return springAppContext as WebApplicationContext;
        }

        println("nao achou, vai criar")
        //None spring context found. Start creating a new one...
        val applicationContext = AnnotationConfigApplicationContext ( WebAppConfig::class.java )

        //saving the context for reusing next time
        springAppContext = applicationContext
        return applicationContext
    }

    /**
     * @return a Spring context of the WebApplication.
     * @param createNewWhenNotFound when true, creates a new Spring Context to return, when no one found in the ServletContext.
     * @param httpServlet the @WebServlet.
     */
    fun ctx(httpServlet: HttpServlet, createNewWhenNotFound: Boolean): ApplicationContext {
        try {
            val webContext = WebApplicationContextUtils.findWebApplicationContext(httpServlet.servletContext)
            if (webContext != null) {
                return webContext
            }
            if (createNewWhenNotFound) {
                //creates a new one
                return ctx()
            } else {
                throw NullPointerException("Cannot found a Spring Application Context.");
            }
        }catch (er: IllegalStateException){
            if (createNewWhenNotFound) {
                //creates a new one
                return ctx()
            }
            throw er;
        }
    }
}

Now, a spring context is publicly available, being able to call the same method independent of the context (junit tests, beans, manually instantiated classes) like on this Java Servlet:

@WebServlet(name = "MyWebHook", value = "/WebHook")
public class MyWebServlet extends HttpServlet {


    private MyBean byBean
            = SpringUtils.INSTANCE.ctx(this, true).getBean(MyBean.class);


    public MyWebServlet() {

    }
}
-1
votes

Even after adding @Autowire if your class is not a RestController or Configuration Class, the applicationContext object was coming as null. Tried Creating new class with below and it is working fine:

@Component
public class SpringContext implements ApplicationContextAware{

   private static ApplicationContext applicationContext;

   @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws 
     BeansException {
    this.applicationContext=applicationContext;
   }
 }

you can then implement a getter method in the same class as per your need like getting the Implemented class reference by:

    applicationContext.getBean(String serviceName,Interface.Class)