获取Spring应用程序背景信息

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

假设主类启动并初始化了应用程序上下文,它是否需要通过调用堆栈向下传递给任何需要它的类,或者类是否有一种方法来请求先前创建的上下文?(我假设它必须是单例的?)

399944 次浏览
这里有一个很好的方法(不是我的,原始参考在这里: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html < / p >

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

看看原来的裁判,非常清楚。

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

它并不漂亮,而且可能比您想要的还要复杂,但它是有效的。

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

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

在你执行任何其他建议之前,问自己这些问题…

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

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

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

我相信你可以使用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团队不鼓励使用这个类和yadayada,但它很适合我使用它的地方。

如果你使用的是web应用,还有另一种不使用单例的方式来访问应用上下文,那就是使用servletfilter和ThreadLocal。在过滤器中,您可以使用WebApplicationContextUtils访问应用程序上下文,并在TheadLocal中存储应用程序上下文或所需的bean。

警告:如果你忘记撤消ThreadLocal,你将在尝试撤消应用程序时遇到严重的问题!因此,您应该设置它,并立即开始尝试,在最后部分取消对ThreadLocal的设置。

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

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

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

public class SpringBean {
@Autowired
private ApplicationContext appContext;
}

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

main application context <- (child) MVC context

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

请注意,通过将当前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的ApplicationContext

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

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

还要注意,beans.xml应该是src/main/resources的一部分,这意味着在战争中,它是WEB_INF/classes的一部分,而真正的应用程序将通过Web.xml中提到的applicationContext.xml加载。

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

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

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

@Component
public class OperatorRequestHandlerFactory {


public static ApplicationContext context;


@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
context = 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);
}
}

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

我知道这个问题已经有了答案,但是我想分享一下我用来检索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上下文是公开可用的,能够独立于上下文调用相同的方法(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() {


}
}

在Spring应用程序中有许多获取应用程序上下文的方法。这些因素如下:

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

不确定这有多有用,但你也可以在初始化应用程序时获得上下文。这是你可以获得上下文的最快时间,甚至在@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);

在Spring bean中进行自动装配,如下所示:

@Autowired
private ApplicationContext appContext;

你会得到ApplicationContext对象。

你可以通过实现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;
}
}

在任何spring托管bean中自动装配应用程序上下文。

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

参考链接

即使在添加了@Autowire之后,如果你的类不是一个RestController或Configuration class, applicationContext对象仍然是空的。尝试用下面创建新类,它工作正常:

@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)

我使用一种简单、标准化的方法允许外部访问我自己的任何单例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环境。