获取Spring应用程序上下文


216

有没有办法在Spring应用程序中静态/全局地请求ApplicationContext的副本?

假设主类启动并初始化了应用程序上下文,它是否需要通过调用堆栈将其向下传递给需要它的任何类,还是有一种方法可以让一个类请求先前创建的上下文?(我认为必须是单身人士?)

Answers:


171

如果需要访问容器的对象是容器中的Bean,则只需实现BeanFactoryAwareApplicationContextAware接口。

如果容器外部的对象需要访问该容器,那么我已经对弹簧容器使用了标准的GoF单例模式。这样,您的应用程序中只有一个单例,其余都是容器中的所有单例bean。


15
ApplicationContexts还有一个更好的接口-ApplicationContextAware。BeanFactoryAware应该可以工作,但是如果需要应用程序上下文功能,则必须将其强制转换为应用程序上下文。
MetroidFan2002 2009年

@Don Kirkby使用单例模式意味着从容器类中的静态方法实例化容器类...一旦“手动”实例化了一个对象,该对象不再由Spring管理:您如何解决此问题?
Antonin

@Antonin在九年后的记忆有些模糊,但是我不认为单例是在Spring容器中管理的。我认为单例的唯一工作是从XML文件加载容器并将其保存在静态成员变量中。我没有返回其自己的类的实例,而是返回了Spring容器的实例。
唐·柯比

1
感谢Don Kirkby,一个Spring单身人士拥有对自己的静态引用,因此可以被非Spring对象使用。
Antonin

如果您告诉Spring容器将singleton的instance()方法用作工厂,那可能会起作用,@ Antonin 。但是,我想我只是让容器外部的所有代码都首先访问该容器。然后,该代码可以从容器中请求对象。
唐·柯比

118

您可以实现ApplicationContextAware或只使用@Autowired

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeanApplicationContext注入,在其中实例化此bean。例如,如果您的Web应用程序具有相当标准的上下文层次结构:

main application context <- (child) MVC context

SpringBean在主上下文中声明,将注入主上下文;否则,如果在MVC上下文中声明,则将注入MVC​​上下文。


2
这帮了一大堆。我在使用Spring 2.0的旧版应用程序时遇到了一些奇怪的问题,您的回答是我可以明智地使事情与单个ApplicationContext和单个Spring IoC容器一起工作的唯一方法。
斯图·汤普森

1
读者..不要忘记在springconfig.xml中将这个SpringBean声明为bean。
supernova

如果这已经是一个Bean,并且我使用Application.getApplicationContext()(Singleton pattern),该方法返回新XXXXApplicationContext(XXXX)的实例,为什么它不起作用怎么办?为什么必须自动接线?
Jaskey 2014年

您可以使用@Inject
阿里Fattahi

39

这是一个不错的方法(不是我的,这里是原始参考文献:http : //sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

我已经使用了这种方法,并且效果很好。基本上,它是一个简单的bean,其中包含对应用程序上下文的(静态)引用。通过在spring config中引用它,可以对其进行初始化。

看一下原始参考,这很清楚。


4
如果您getBean从单元测试期间运行的代码中调用,则该方法可能会失败,因为在您请求之前不会设置Spring上下文。在成功使用此方法两年后,我才陷入了今天的比赛环境。
HDave 2012年

我遇到了同样的事情..不是来自单元测试,而是来自数据库触发器..有什么建议吗?
John Deverall

反应出色。谢谢。
sagneta

17

我相信您可以使用SingletonBeanFactoryLocator。beanRefFactory.xml文件将保存实际的applicationContext,它将如下所示:

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

从任何地方从applicationcontext获取bean的代码都是这样的:

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

Spring团队不鼓励使用此类和yadayada,但在使用它的地方非常适合我。


11

在实施其他任何建议之前,请先问自己以下问题...

  • 为什么我要获取ApplicationContext?
  • 我可以有效地将ApplicationContext用作服务定位器吗?
  • 我可以完全避免访问ApplicationContext吗?

这些问题的答案在某些类型的应用程序(例如Web应用程序)中比在其他类型的应用程序中容易,但是无论如何都值得提出。

访问ApplicationContext确实违反了整个依赖注入原理,但是有时您没有太多选择。


5
一个很好的例子是JSP标签。它们的创建由servlet容器控制,因此他们别无选择,只能静态获取上下文。Spring提供了基本的Tag类,并且它们使用BeanFactoryLocators来获取所需的上下文。
skaffman's

6

如果您使用Web应用程序,则还有另一种方法可以通过使用servletfilter和ThreadLocal来访问应用程序上下文而不使用单例。在过滤器中,您可以使用WebApplicationContextUtils访问应用程序上下文,并将应用程序上下文或所需的bean存储在TheadLocal中。

警告:如果您忘记取消设置ThreadLocal,则在尝试取消部署应用程序时会遇到讨厌的问题!因此,您应该对其进行设置,然后立即开始尝试以在最后部分取消ThreadLocal的设置。

当然,这仍然使用单例:ThreadLocal。但是实际的bean不再需要了。甚至可以在请求范围内进行操作,如果您的应用程序中有多个WAR,并且EAR中有库,则此解决方案也可以使用。不过,您可能会认为ThreadLocal的这种使用与普通单例的使用一样糟糕。;-)

也许Spring已经提供了类似的解决方案?我没有找到一个,但我不确定。


6
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);
  }
}

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



4

在Spring应用程序中有很多方法可以获取应用程序上下文。以下是这些:

  1. 通过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;
    }
    }

在这里,setApplicationContext(ApplicationContext applicationContext)您将获得applicationContext

ApplicationContextAware

希望由希望在其运行的ApplicationContext通知的任何对象实现的接口。例如,当对象需要访问一组协作bean时,实现此接口是有意义的。

  1. 通过自动连线

    @Autowired
    private ApplicationContext applicationContext;

这里的@Autowired关键字将提供applicationContext。自动连线有一些问题。它将在单元测试期间产生问题。


3

请注意,通过将current ApplicationContext或状态ApplicationContext本身的任何状态存储在静态变量中(例如使用单例模式),如果您使用的是Spring-test,则会使测试变得不稳定且不可预测。这是因为Spring-test在同一JVM中缓存和重用应用程序上下文。例如:

  1. 测试运行,并用注释@ContextConfiguration({"classpath:foo.xml"})
  2. 测试B运行,并带有注释 @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. 测试C运行,并带有注释 @ContextConfiguration({"classpath:foo.xml"})

运行测试A时,将ApplicationContext创建一个,实现ApplicationContextAware或自动装配的所有bean都ApplicationContext可能写入静态变量。

当测试B运行时,会发生相同的事情,并且静态变量现在指向测试B的 ApplicationContext

当试验C运行时,不产生豆作为TestContext(和本文中的ApplicationContext)从试验A是resused。现在,您有一个静态变量指向另一个变量,而ApplicationContext不是当前用于测试的bean。


1

不确定这将有多大用处,但在初始化应用程序时也可以获取上下文。即使在之前,这也是最快可以获取上下文的方法@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);

0

请注意;下面的代码将创建新的应用程序上下文,而不是使用已经加载的上下文。

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

还要注意,它beans.xml应该是src/main/resources战争手段的一部分,它是的一部分WEB_INF/classes,因为真正的应用程序将通过applicationContext.xml在处提到进行加载Web.xml

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

这是很难applicationContext.xml的路径ClassPathXmlApplicationContext构造。ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")将无法找到该文件。

因此,最好通过注释使用现有的applicationContext。

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

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

0

我知道这个问题已经回答,但是我想分享我为检索Spring Context而做的Kotlin代码。

我不是专家,因此我欢迎批评家,评论和建议:

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;
        }
    }
}

现在,spring上下文是公开可用的,能够像该Java Servlet一样独立于上下文调用相同的方法(junit测试,bean,手动实例化的类):

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


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


    public MyWebServlet() {

    }
}


0

方法1:您可以通过实现ApplicationContextAware接口来注入ApplicationContext。参考链接

@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;
    }
}

方法2:在任何Spring托管Bean中使用Autowire Application上下文。

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

参考链接

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.