有没有办法在 Spring 应用程序中静态/全局请求 ApplicationContext 的副本?
假设主类启动并初始化应用程序上下文,它是否需要通过调用堆栈将其传递给任何需要它的类,或者有没有办法让一个类请求先前创建的上下文?(我认为必须是单身人士?)
有没有办法在 Spring 应用程序中静态/全局请求 ApplicationContext 的副本?
假设主类启动并初始化应用程序上下文,它是否需要通过调用堆栈将其传递给任何需要它的类,或者有没有办法让一个类请求先前创建的上下文?(我认为必须是单身人士?)
如果需要访问容器的对象是容器中的一个bean,只需实现BeanFactoryAware或ApplicationContextAware接口即可。
如果容器外部的对象需要访问容器,我使用了标准的 GoF 单例模式作为 spring 容器。这样,您的应用程序中只有一个单例,其余的都是容器中的单例 bean。
您可以实施ApplicationContextAware
或仅使用@Autowired
:
public class SpringBean {
@Autowired
private ApplicationContext appContext;
}
SpringBean
将被ApplicationContext
注入,在其中实例化这个 bean。例如,如果您的 Web 应用程序具有非常标准的上下文层次结构:
main application context <- (child) MVC context
并且SpringBean
在主上下文中声明,它将注入主上下文;否则,如果它在 MVC 上下文中声明,它将注入 MVC 上下文。
这是一个不错的方法(不是我的,原始参考在这里: http ://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html
我已经使用了这种方法,并且效果很好。基本上,它是一个简单的 bean,它持有对应用程序上下文的(静态)引用。通过在 spring 配置中引用它,它被初始化。
看看原文,很清楚。
我相信你可以使用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 应用程序)中比在其他应用程序中更容易,但无论如何都值得一问。
访问 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
如果您使用 web 应用程序,还有另一种方法可以访问应用程序上下文,而无需使用单例,即使用 servletfilter 和 ThreadLocal。在过滤器中,您可以使用 WebApplicationContextUtils 访问应用程序上下文,并将应用程序上下文或所需的 bean 存储在 TheadLocal 中。
注意:如果您忘记取消设置 ThreadLocal,在尝试取消部署应用程序时会遇到麻烦!因此,您应该设置它并立即开始尝试在 finally 部分中取消设置 ThreadLocal。
当然,这仍然使用一个单例:ThreadLocal。但实际的豆子不再需要了。甚至可以是请求范围的,如果您在一个应用程序中有多个 WAR,并且 EAR 中有库,则此解决方案也可以工作。不过,您可能会认为使用 ThreadLocal 与使用普通单例一样糟糕。;-)
也许 Spring 已经提供了类似的解决方案?我没有找到,但我不确定。
看看ContextSingletonBeanFactoryLocator。它提供静态访问器来获取 Spring 的上下文,假设它们已经以某种方式注册。
它并不漂亮,而且比你想要的更复杂,但它确实有效。
在 Spring 应用程序中有很多方法可以获取应用程序上下文。这些在下面给出:
通过 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
应用上下文感知:
由任何希望通知它运行的 ApplicationContext 的对象实现的接口。例如,当对象需要访问一组协作 bean 时,实现此接口是有意义的。
通过自动连线:
@Autowired
private ApplicationContext applicationContext;
这里@Autowired
的关键字将提供 applicationContext。自动接线有一些问题。它会在单元测试期间产生问题。
请注意,通过将来自 currentApplicationContext
或ApplicationContext
自身的任何状态存储在静态变量中 - 例如使用单例模式 - 如果您使用 Spring-test,您的测试将变得不稳定且不可预测。这是因为 Spring-test 在同一个 JVM 中缓存和重用应用程序上下文。例如:
@ContextConfiguration({"classpath:foo.xml"})
。@ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
@ContextConfiguration({"classpath:foo.xml"})
当测试 A 运行时,ApplicationContext
会创建一个,并且任何实现ApplicationContextAware
或自动装配ApplicationContext
的 bean 都可能写入静态变量。
当测试 B 运行时,同样的事情发生了,静态变量现在指向测试 BApplicationContext
当测试 C 运行时,不会创建任何 bean,因为来自测试 A的TestContext
(以及这里的)被重新使用。ApplicationContext
现在你有一个静态变量指向另一个ApplicationContext
,而不是当前为你的测试保存 bean 的那个。
不确定这会有多大用处,但您也可以在初始化应用程序时获取上下文。即使在@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;
您将应用程序上下文对象。
我使用一种简单、标准化的方式来允许外部访问我自己的任何单例 Spring Bean。用这个方法,我继续让Spring实例化Bean。这就是我所做的:
this
在每个类的构造函数中将该变量设置为。如果该类没有构造函数,则添加一个默认构造函数来设置变量。这是一个例子:
@Component
public class MyBean {
...
private static MyBean singleton = null;
public MyBean() {
...
singleton = this;
}
...
public void someMethod() {
...
}
...
public static MyBean get() {
return singleton;
}
}
然后,我可以通过以下方式在代码中的任何位置调用someMethod
单例 bean:
MyBean.get().someMethod();
如果你已经继承了你的子类ApplicationContext
,你可以直接向它添加这个机制。否则,您可以子类化它只是为了做到这一点,或者将此机制添加到任何可以访问 的 bean,ApplicationContext
然后使用它ApplicationContext
从任何地方访问 。重要的是,正是这种机制让您进入 Spring 环境。
请注意;下面的代码将创建新的应用程序上下文,而不是使用已经加载的上下文。
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;
}
}
方法一:你可以通过实现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 中自动装配应用程序上下文。
@Component
public class SpringBean {
@Autowired
private ApplicationContext appContext;
}
参考链接。
我知道这个问题已经得到解答,但我想分享我为检索 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() {
}
}
如果您的类不是 RestController 或 Configuration Class,即使在添加 @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)