繁体   English   中英

获取 Spring 应用程序上下文

[英]Getting Spring Application Context

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

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

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

如果容器外的对象需要访问容器,我对 spring 容器使用了标准的 GoF 单例模式 这样,您的应用程序中只有一个单例,其余的都是容器中的单例 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,但它非常适合我使用它的地方。

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

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

这些问题的答案在某些类型的应用程序(例如 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。 但实际的 bean 不再需要了。 甚至可以是请求范围的,如果应用程序中有多个 WAR,并且 EAR 中有库,则此解决方案也适用。 不过,您可能认为 ThreadLocal 的这种使用与普通单例的使用一样糟糕。 ;-)

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

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

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

在 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

应用上下文感知

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

  1. 通过自动连线

     @Autowired private ApplicationContext applicationContext;

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

请注意,通过将来自当前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 的那个。

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

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

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

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路径。 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 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 环境。

我知道这个问题得到了回答,但我想分享我为检索 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 上下文是公开可用的,能够像在这个 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() {

    }
}

即使在添加 @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)

暂无
暂无

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

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