获取Spring应用上下文

245

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

假设主类启动并初始化应用程序上下文,是否需要将其通过调用堆栈传递到需要它的任何类中,还是有一种方法可供类请求先前创建的上下文? (我认为必须是单例模式?)

17个回答

185
如果需要访问容器的对象是容器中的bean,只需实现BeanFactoryAwareApplicationContextAware接口。
如果容器外的对象需要访问容器,我使用了标准GoF单例模式来创建Spring容器。这样,您的应用程序中只有一个单例,其余都是容器中的单例bean。

16
还有一个更好的接口可以用于ApplicationContexts,即ApplicationContextAware。BeanFactoryAware也可以使用,但如果需要应用程序上下文的功能,则需要将其转换为应用程序上下文。 - MetroidFan2002
使用单例模式意味着从容器类内部的静态方法实例化容器类...一旦您“手动”实例化了一个对象,它就不再由Spring管理:您如何解决这个问题? - Antonin
九年后,我的记忆有些模糊了,@Antonin,但我认为单例模式并没有在Spring容器内进行管理。我认为单例的唯一工作是从XML文件中加载容器并将其保存在静态成员变量中。它没有返回自己类的实例,而是返回Spring容器的实例。 - Don Kirkby
1
感谢Don Kirkby,一个Spring单例拥有对自身的静态引用,因此可被非Spring对象使用。 - Antonin
如果您告诉Spring容器使用单例的instance()方法作为工厂,@Antonin,那可能会起作用。然而,我认为我刚刚让所有代码在容器外部首先访问容器。然后该代码可以从容器请求对象。 - Don Kirkby
有时我会创建静态的“singleton”变量,以便让旧代码可以访问我的Beans。这很容易做到。您只需在Bean类中定义一个“static MyBean singleton”,然后在该bean的所有构造函数中设置“singleton = this”。如果您尚未定义构造函数,则只需添加默认构造函数即可设置单例。您可以使“singleton”公共并直接访问它。我反而将“singleton”设为私有,然后在类上提供一个“public static MyBean get()”方法来返回“singleton”。简单得像个馅饼。 - CryptoFool

134

您可以实现ApplicationContextAware接口,也可以直接使用@Autowired注解:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBean 将会注入 ApplicationContext,在其中实例化该bean。例如,如果您有一个带有相当标准的上下文层次结构的Web应用程序:

main application context <- (child) MVC context

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


2
这真的帮了很大的忙。我在一个使用Spring 2.0的旧应用程序中遇到了一些奇怪的问题,而你的答案是唯一让我能够理智地使用单个ApplicationContext和单个Spring IoC容器来使事情正常工作的方法。 - Stu Thompson
1
读者们,不要忘记在你的springconfig.xml文件中声明这个SpringBean作为一个bean。 - supernova
如果这已经是一个Bean,我使用Application.getApplicationContext()(单例模式),它返回一个新的XXXXApplicationContext(XXXX)实例,为什么它不起作用?为什么我必须自动装配它? - JaskeyLam
你也可以使用 @Inject - Alireza Fattahi
1
没有自动装配?我的意思是这个属性是单例的,所以我不明白为什么它不能通过静态方法访问。 - mjs

40

这里有一个不错的方法(非本人所创,原始参考链接在此:http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html)。

我使用过这种方法,并且它很有效。基本上,这是一个简单的 bean,它包含一个(静态)应用程序上下文引用。通过在 spring 配置中引用它,就可以初始化它。

请参考原始链接,它非常清晰易懂。


5
如果您在单元测试期间从代码中调用getBean,那么这种方法可能会失败,因为在请求之前Spring上下文不会设置。这是一个竞争条件,今天我刚刚碰到它,尽管在成功使用此方法2年后。 - Dave
我也遇到了同样的问题,不是来自单元测试,而是来自数据库触发器。有什么建议吗? - John Deverall

18

我相信你可以使用SingletonBeanFactoryLocator。beanRefFactory.xml文件将保存实际的applicationContext,做法可能是这样的:

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

从任何地方获取bean的代码将类似于以下内容:

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

Spring团队不鼓励使用这个类,等等,但在我使用它的地方,它很适合我。


10
在实施其他建议之前,请询问自己以下问题:
  • 我为什么要获取ApplicationContext?
  • 我是否有效地将ApplicationContext作为服务定位器使用?
  • 我能否完全避免访问ApplicationContext?
这些问题的答案在某些类型的应用程序(例如Web应用程序)中比其他应用程序更容易,但无论如何都值得询问。
访问ApplicationContext确实有点违反整个依赖注入原则,但有时你别无选择。

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

10
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

这篇文章介绍了如何从遗留代码中访问Spring框架的Bean。我们可以通过将Spring应用程序上下文初始化为一个静态成员变量来实现这一点。然后,在需要访问Spring Bean的类中,我们只需要使用该静态成员变量即可访问Bean。要访问Bean,我们还必须调用getBean方法,并指定要访问的Bean的名称。由于在Spring配置文件中将Bean定义为单例,因此我们可以始终获得相同的Bean实例。

6

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

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

这里的setApplicationContext(ApplicationContext applicationContext)方法将会获取到应用程序上下文。

ApplicationContextAware

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

  1. Via Autowired:

    @Autowired
    private ApplicationContext applicationContext;
    

这里的@Autowired关键字将提供applicationContext。Autowired关键字存在一些问题。它在单元测试期间可能会出现问题。


6
请注意,如果您在静态变量中存储当前应用程序上下文或应用程序上下文中的任何状态 - 例如使用单例模式 - 则会使您的测试在使用Spring-test时不稳定和不可预测。这是因为Spring-test在同一JVM中缓存和重用应用程序上下文。例如:
  1. 运行Test A,并对其进行注释@ContextConfiguration({"classpath:foo.xml"})
  2. 运行Test B,并对其进行注释@ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. 运行Test C,并对其进行注释@ContextConfiguration({"classpath:foo.xml"})
当Test A运行时,将创建一个ApplicationContext,并且任何实现ApplicationContextAware或自动装配ApplicationContext的bean可能会写入静态变量。
当运行Test B时,同样的事情发生了,静态变量现在指向Test B的ApplicationContext
当运行Test C时,不会创建任何bean,因为Test A的TestContext(以及其中的ApplicationContext)被重用。现在你得到了一个静态变量,它指向另一个ApplicationContext而不是当前持有你的测试bean的ApplicationContext。

你说得完全正确,我在工作中已经遇到了你描述的完全相同的问题两年了。我已经花了几个星期来调试这个问题,而且一年前我就知道确切地这就是问题所在,一直担心它会再次触发。 这里有一个关于如何防止这个问题的并行问题。 这个周末,我终于找到了解决办法,请看我在那里的回答,以及我在GitLab上的代码片段,其中包含更多细节 - undefined

6
如果您使用Web应用程序,还有另一种方法可以访问应用程序上下文而不使用单例,即使用ServletFilter和ThreadLocal。在过滤器中,您可以使用WebApplicationContextUtils访问应用程序上下文,并将应用程序上下文或所需的bean存储在TheadLocal中。
注意:如果您忘记取消设置ThreadLocal,则在尝试卸载应用程序时会遇到问题!因此,您应该设置它并立即开始一个try,以在finally部分取消设置ThreadLocal。
当然,这仍然使用单例:ThreadLocal。但是实际的bean不再需要了。它们甚至可以是请求范围的,如果您在应用程序中有多个WAR并且库位于EAR中,则此解决方案也适用。尽管如此,您可能认为这种使用ThreadLocal与使用纯单例一样糟糕。;-)
也许Spring已经提供了类似的解决方案?我没有找到,但我不确定。

5

请看 ContextSingletonBeanFactoryLocator。它提供了静态访问器,用于获取Spring上下文(假设以某种方式注册)。

虽然不太美观,比您想象的更加复杂,但确实有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接