【问题标题】:Getting Spring Application Context获取 Spring 应用程序上下文
【发布时间】:2010-09-12 20:29:24
【问题描述】:

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

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

【问题讨论】:

    标签: java spring configuration


    【解决方案1】:

    如果需要访问容器的对象是容器中的一个bean,只需实现BeanFactoryAware或ApplicationContextAware接口即可。

    如果容器外的一个对象需要访问容器,我使用了standard GoF singleton pattern 作为spring 容器。这样,您的应用程序中只有一个单例,其余的都是容器中的单例 bean。

    【讨论】:

    • ApplicationContexts还有一个更好的接口——ApplicationContextAware。 BeanFactoryAware 应该可以工作,但如果您需要应用上下文功能,则必须将其转换为应用上下文。
    • @Don Kirkby 使用单例模式意味着从容器类中的静态方法实例化容器类......一旦你“手动”实例化一个不再由 Spring 管理的对象:你是怎么做到的解决这个问题?
    • 我的记忆在九年后有点模糊,@Antonin,但我不认为单例是在 Spring 容器中管理的。我认为单例的唯一工作是从 XML 文件加载容器并将其保存在静态成员变量中。我没有返回它自己的类的实例,它返回的是 Spring 容器的实例。
    • 感谢 Don Kirkby,一个 Spring 单例拥有对自身的静态引用,因此非 Spring 对象可能也可以使用。
    • @Antonin,如果您告诉 Spring 容器使用单例的 instance() 方法作为工厂,那可能会起作用。但是,我想我只是让容器外的所有代码先访问容器。然后该代码可以从容器中请求对象。
    【解决方案2】:

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

    public class SpringBean {
      @Autowired
      private ApplicationContext appContext;
    }
    

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

    main application context <- (child) MVC context
    

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

    【讨论】:

    • 这帮助了很多人。我在使用 Spring 2.0 的较旧应用程序时遇到了一些奇怪的问题,而您的回答是我可以通过单个 Spring IoC 容器明智地使用单个 ApplicationContext 的唯一方法。
    • 读者..不要忘记在你的 springconfig.xml 中将这个 SpringBean 声明为一个 bean。
    • 如果这已经是一个 Bean,我使用 Application.getApplicationContext()(Singleton pattern),它返回一个 new XXXXApplicationContext(XXXX) 的实例,为什么它不起作用?为什么我必须自动接线?
    • 你也可以使用@Inject
    • 没有自动装配?我的意思是这个属性是一个单例,所以我不明白为什么它不能通过静态方法访问。
    【解决方案3】:

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

    我用过这种方法,效果很好。基本上,它是一个简单的 bean,它持有对应用程序上下文的(静态)引用。通过在 spring 配置中引用它,它被初始化。

    看看原文,很清楚。

    【讨论】:

    • 如果您从单元测试期间运行的代码中调用getBean,该方法可能会失败,因为在您请求之前不会设置 Spring 上下文。在成功使用这种方法 2 年后,我今天刚刚遇到了一个竞争条件。
    • 我遇到了同样的事情。不是来自单元测试,而是来自数据库触发器。有什么建议吗?
    【解决方案4】:

    我相信你可以使用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,但它在我使用它的地方很适合我。

    【讨论】:

      【解决方案5】:

      在您实施任何其他建议之前,请先问自己这些问题...

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

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

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

      【讨论】:

      • 一个很好的例子是JSP标签;它们的创建是由 servlet 容器控制的,所以它们别无选择,只能静态获取上下文。 Spring 提供了基本的 Tag 类,它们使用 BeanFactoryLocators 来获取所需的上下文。
      【解决方案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

      【讨论】:

        【解决方案7】:

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

        警告:如果您忘记取消设置 ThreadLocal,在尝试取消部署应用程序时会遇到麻烦!因此,您应该设置它并立即开始尝试在 finally 部分取消设置 ThreadLocal。

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

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

        【讨论】:

          【解决方案8】:

          看看ContextSingletonBeanFactoryLocator。它提供静态访问器来获取 Spring 的上下文,假设它们已经以某种方式注册。

          它并不漂亮,而且比你想要的更复杂,但它确实有效。

          【讨论】:

            【解决方案9】:

            在 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 。实现这个接口 例如,当一个对象需要访问一组 合作豆。

            1. 通过自动接线:

              @Autowired
              private ApplicationContext applicationContext;
              

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

            【讨论】:

              【解决方案10】:

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

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

              当测试 A 运行时,会创建一个 ApplicationContext,并且任何实现 ApplicationContextAware 或自动装配 ApplicationContext 的 bean 都可能写入静态变量。

              当测试 B 运行时,同样的事情发生了,静态变量现在指向测试 B 的 ApplicationContext

              当测试 C 运行时,没有创建任何 bean,因为来自测试 A 的 TestContext(以及这里的 ApplicationContext)被重新使用。现在,您有一个静态变量指向另一个 ApplicationContext,而不是当前为您的测试保存 bean 的变量。

              【讨论】:

                【解决方案11】:

                不确定这会有多大用处,但您也可以在初始化应用程序时获取上下文。即使在@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);
                

                【讨论】:

                  【解决方案12】:

                  在 Spring bean 中进行自动装配,如下所示: @自动连线 私有 ApplicationContext appContext;

                  你将应用程序上下文对象。

                  【讨论】:

                    【解决方案13】:

                    我使用一种简单、标准化的方式来允许外部访问我自己的任何单例 Spring Bean。用这个方法,我继续让Spring实例化Bean。这是我的工作:

                    1. 定义一个与封闭类相同类型的私有静态变量。
                    2. 在每个类的构造函数中将该变量设置为this。如果该类没有构造函数,请添加一个默认构造函数来设置变量。
                    3. 定义一个返回单例变量的公共静态 getter 方法。

                    这是一个例子:

                    @Component
                    public class MyBean {
                        ...
                    
                        private static MyBean singleton = null;
                    
                        public MyBean() {
                            ...
                            singleton = this;
                        }
                    
                        ...
                        
                        public void someMethod() {
                            ...
                        }
                    
                        ...
                    
                        public static MyBean get() {
                            return singleton;
                        }
                    }
                    

                    然后我可以在单例 bean 上调用 someMethod,在我的代码中的任何位置,通过:

                    MyBean.get().someMethod();
                    

                    如果你已经继承了ApplicationContext,你可以直接向它添加这个机制。否则,您可以子类化它只是为了做到这一点,或者将此机制添加到任何可以访问ApplicationContext 的bean,然后使用它从任何地方访问ApplicationContext。重要的是,正是这种机制让你进入 Spring 环境。

                    【讨论】:

                      【解决方案14】:

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

                      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>
                      

                      ClassPathXmlApplicationContext 构造函数中提到applicationContext.xml 路径是difficult。 ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml") 将无法找到该文件。

                      所以最好通过注解来使用已有的applicationContext。

                      @Component
                      public class OperatorRequestHandlerFactory {
                      
                          public static ApplicationContext context;
                      
                          @Autowired
                          public void setApplicationContext(ApplicationContext applicationContext) {
                              context = applicationContext;
                          }
                      }
                      

                      【讨论】:

                        【解决方案15】:

                        方法一:你可以通过实现ApplicationContextAware接口来注入ApplicationContext。参考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;
                            }
                        }
                        

                        方法 2: 在任何 Spring 托管 bean 中自动装配应用程序上下文。

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

                        参考link。

                        【讨论】:

                          【解决方案16】:

                          我知道这个问题已经得到解答,但我想分享我为检索 Spring 上下文所做的 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 上下文是公开可用的,能够独立于上下文(junit 测试、bean、手动实例化的类)调用相同的方法,就像在这个 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() {
                          
                              }
                          }
                          

                          【讨论】:

                            【解决方案17】:

                            如果您的类不是 RestController 或配置类,即使在添加 @Autowire 之后,applicationContext 对象仍为 null。尝试使用以下创建新类,它工作正常:

                            @Component
                            public class SpringContext implements ApplicationContextAware{
                            
                               private static ApplicationContext applicationContext;
                            
                               @Override
                                public void setApplicationContext(ApplicationContext applicationContext) throws 
                                 BeansException {
                                this.applicationContext=applicationContext;
                               }
                             }
                            

                            然后,您可以根据需要在同一类中实现 getter 方法,例如通过以下方式获取已实现的类引用:

                                applicationContext.getBean(String serviceName,Interface.Class)
                            

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 2011-10-31
                              • 1970-01-01
                              • 2014-02-14
                              • 1970-01-01
                              • 2016-05-20
                              • 2017-05-01
                              • 2012-07-25
                              • 1970-01-01
                              相关资源
                              最近更新 更多