简体   繁体   English

获取 Spring 应用程序上下文

[英]Getting Spring Application Context

Is there a way to statically/globally request a copy of the ApplicationContext in a Spring application?有没有办法在 Spring 应用程序中静态/全局请求 ApplicationContext 的副本?

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?) (我认为必须是单身人士?)

If the object that needs access to the container is a bean in the container, just implement the BeanFactoryAware or ApplicationContextAware interfaces.如果需要访问容器的对象是容器中的 bean,只需实现BeanFactoryAwareApplicationContextAware接口。

If an object outside the container needs access to the container, I've used a standard GoF singleton pattern for the spring container.如果容器外的对象需要访问容器,我对 spring 容器使用了标准的 GoF 单例模式 That way, you only have one singleton in your application, the rest are all singleton beans in the container.这样,您的应用程序中只有一个单例,其余的都是容器中的单例 bean。

You can implement ApplicationContextAware or just use @Autowired :您可以实现ApplicationContextAware或仅使用@Autowired

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBean will have ApplicationContext injected, within which this bean is instantiated. SpringBean将注入ApplicationContext ,在其中实例化这个 bean。 For example if you have web application with a pretty standard contexts hierarchy:例如,如果您的 Web 应用程序具有非常标准的上下文层次结构:

main application context <- (child) MVC context

and SpringBean is declared within main context, it will have main context injected;并且SpringBean在主上下文中声明,它将注入主上下文; otherwise, if it's declared within MVC context, it will have MVC context injected.否则,如果它是在 MVC 上下文中声明的,它将注入 MVC 上下文。

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这是一个很好的方法(不是我的,原始参考在这里: 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.基本上它是一个简单的 bean,它包含对应用程序上下文的(静态)引用。 By referencing it in the spring config it's initialized.通过在 spring 配置中引用它,它被初始化。

Take a look at the original ref, it's very clear.看一下原始参考,它很清楚。

I believe you could use SingletonBeanFactoryLocator .我相信你可以使用SingletonBeanFactoryLocator The beanRefFactory.xml file would hold the actual applicationContext, It would go something like this: beanRefFactory.xml 文件将保存实际的 applicationContext,它会是这样的:

<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:以及从任何地方从应用程序上下文中获取 bean 的代码是这样的:

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. Spring 团队不鼓励使用这个类和 yadayada,但它非常适合我使用它的地方。

Before you implement any of the other suggestions, ask yourself these questions...在你实施任何其他建议之前,问问自己这些问题......

  • Why am I trying to get the ApplicationContext?为什么我要尝试获取 ApplicationContext?
  • Am I effectively using the ApplicationContext as a service locator?我是否有效地使用 ApplicationContext 作为服务定位器?
  • Can I avoid accessing the ApplicationContext at all?我可以完全避免访问 ApplicationContext 吗?

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

Accessing the ApplicationContext does kind of violate the whole dependency injection principle, but sometimes you've not got much choice.访问 ApplicationContext 确实违反了整个依赖注入原则,但有时您没有太多选择。

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来源: http : //sujitpal.blogspot.de/2007/03/accessing-spring-beans-from-legacy-code.html

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.如果您使用 Web 应用程序,还有另一种方法可以通过使用 servletfilter 和 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.在过滤器中,您可以使用 WebApplicationContextUtils 访问应用程序上下文并将应用程序上下文或所需的 bean 存储在 TheadLocal 中。

Caution: if you forget to unset the ThreadLocal you will get nasty problems when trying to undeploy the application!注意:如果您忘记取消设置 ThreadLocal,您将在尝试取消部署应用程序时遇到令人讨厌的问题! Thus, you should set it and immediately start a try that unsets the ThreadLocal in the finally-part.因此,您应该设置它并立即开始尝试在 finally 部分取消设置 ThreadLocal 。

Of course, this still uses a singleton: the ThreadLocal.当然,这仍然使用单例:ThreadLocal。 But the actual beans do not need to be anymore.但实际的 bean 不再需要了。 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.甚至可以是请求范围的,如果应用程序中有多个 WAR,并且 EAR 中有库,则此解决方案也适用。 Still, you might consider this use of ThreadLocal as bad as the use of plain singletons.不过,您可能认为 ThreadLocal 的这种使用与普通单例的使用一样糟糕。 ;-) ;-)

Perhaps Spring already provides a similar solution?也许 Spring 已经提供了类似的解决方案? I did not find one, but I don't know for sure.我没有找到,但我不确定。

Take a look at ContextSingletonBeanFactoryLocator .看看ContextSingletonBeanFactoryLocator It provides static accessors to get hold of Spring's contexts, assuming they have been registered in certain ways.它提供静态访问器来获取 Spring 的上下文,假设它们已以某种方式注册。

It's not pretty, and more complex than perhaps you'd like, but it works.它并不漂亮,而且可能比您想要的更复杂,但它确实有效。

There are many way to get application context in Spring application.在 Spring 应用程序中有多种获取应用程序上下文的方法。 Those are given bellow:这些是在下面给出的:

  1. Via ApplicationContextAware :通过 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这里setApplicationContext(ApplicationContext applicationContext)方法你会得到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.由任何希望被通知它运行的 ApplicationContext 的对象实现的接口。例如,当一个对象需要访问一组协作 bean 时,实现这个接口是有意义的。

  1. Via Autowired :通过自动连线

     @Autowired private ApplicationContext applicationContext;

Here @Autowired keyword will provide the applicationContext.这里@Autowired关键字将提供applicationContext。 Autowired has some problem. Autowired 有一些问题。 It will create problem during unit-testing.它会在单元测试期间产生问题。

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.请注意,通过将来自当前ApplicationContext任何状态或ApplicationContext本身存储在静态变量中 - 例如使用单例模式 - 如果您使用 Spring-test,您将使您的测试不稳定和不可预测。 This is because Spring-test caches and reuses application contexts in the same JVM.这是因为 Spring-test 在同一个 JVM 中缓存和重用应用程序上下文。 For example:例如:

  1. Test A run and it is annotated with @ContextConfiguration({"classpath:foo.xml"}) .测试 A 运行,它用@ContextConfiguration({"classpath:foo.xml"})注释。
  2. Test B run and it is annotated with @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})测试 B 运行并用@ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Test C run and it is annotated with @ContextConfiguration({"classpath:foo.xml"})测试 C 运行并用@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.当测试 A 运行时,会创建一个ApplicationContext ,并且任何实现ApplicationContextAware或自动装配ApplicationContext bean 都可能写入静态变量。

When Test B runs the same thing happens, and the static variable now points to Test B's ApplicationContext当测试 B 运行时,同样的事情发生,静态变量现在指向测试 B 的ApplicationContext

When Test C runs, no beans are created as the TestContext (and herein the ApplicationContext ) from Test A is resused.当测试 C 运行时,没有 bean 被创建,因为来自测试 A 的TestContext (这里是ApplicationContext )被重用。 Now you got a static variable pointing to another ApplicationContext than the one currently holding the beans for your test.现在,您有一个静态变量指向另一个ApplicationContext不是当前为您的测试持有 bean 的那个。

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 .这是您可以获得上下文的最快速度,甚至在@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);

Do autowire in Spring bean as below : @Autowired private ApplicationContext appContext;在 Spring bean 中执行自动装配如下:@Autowired private ApplicationContext appContext;

you will the applicationcontext object.您将应用程序上下文对象。

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 .还要注意的是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>

It is difficult to mention applicationContext.xml path in ClassPathXmlApplicationContext constructor. 很难ClassPathXmlApplicationContext构造函数中提及applicationContext.xml路径。 ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml") wont be able to locate the file. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")将无法定位该文件。

So it is better to use existing applicationContext by using annotations.所以最好通过使用注解来使用现有的 applicationContext。

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

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

Approach 1: You can inject ApplicationContext by implementing ApplicationContextAware interface.方法一:可以通过实现ApplicationContextAware接口来注入ApplicationContext。 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.方法 2:在任何 Spring 管理的 bean 中自动装配应用程序上下文。

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

Reference link .参考链接

I use a simple, standardized way to allow external access to any of my own singleton Spring Beans.我使用一种简单、标准化的方式来允许外部访问我自己的任何单例 Spring Bean。 With this method, I continue to let Spring instantiate the Bean.用这个方法,我继续让Spring实例化这个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.在每个类的构造函数中将该变量设置为this 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.定义一个返回单例变量的公共静态 getter 方法。

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:然后我可以在我的代码中的任何地方调用单例 bean 上的someMethod ,方法是:

MyBean.get().someMethod();

If you are already subclassing your ApplicationContext , you can add this mechanism to it directly.如果您已经在子类化ApplicationContext ,则可以直接向其添加此机制。 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.否则,您可以子类化它只是为了做到这一点,或者将此机制添加到任何可以访问ApplicationContext bean,然后使用它从任何地方获取对ApplicationContext访问权限。 The important thing is that it is this mechanism that will let you get into the Spring environment.重要的是,正是这种机制可以让你进入 Spring 环境。

I know this question is answered, but I would like to share the Kotlin code I did to retrieve the Spring Context.我知道这个问题得到了回答,但我想分享我为检索 Spring 上下文所做的 Kotlin 代码。

I am not a specialist, so I am open to critics, reviews and advices:我不是专家,所以我对批评、评论和建议持开放态度:

https://gist.github.com/edpichler/9e22309a86b97dbd4cb1ffe011aa69dd 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:现在,一个 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() {

    }
}

Even after adding @Autowire if your class is not a RestController or Configuration Class, the applicationContext object was coming as null.即使在添加 @Autowire 之后,如果您的类不是 RestController 或 Configuration Class,applicationContext 对象也会为空。 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:然后,您可以根据需要在同一个类中实现一个 getter 方法,例如通过以下方式获取已实现的类引用:

    applicationContext.getBean(String serviceName,Interface.Class)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM