Вопрос

Есть ли способ статически/глобально запросить копию ApplicationContext в приложении Spring?

Предполагая, что основной класс запускается и инициализирует контекст приложения, нужно ли ему передавать его через стек вызовов любым классам, которым он нужен, или есть ли у класса способ запросить ранее созданный контекст?(Как я полагаю, это должен быть синглтон?)

Это было полезно?

Решение

Если объект, которому требуется доступ к контейнеру, является компонентом в контейнере, просто реализуйте BeanFactoryAware или ПриложениеContextAware интерфейсы.

Если объекту вне контейнера требуется доступ к контейнеру, я использовал стандартный одноэлементный шаблон GoF для весеннего контейнера.Таким образом, в вашем приложении будет только один синглтон, а все остальные — синглтон-компоненты в контейнере.

Другие советы

Вы можете реализовать ApplicationContextAware или просто используйте @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBean будет ApplicationContext Injected, внутри которого создается экземпляр этого bean-компонента.Например, если у вас есть веб-приложение с довольно стандартной иерархией контекстов:

main application context <- (child) MVC context

и SpringBean объявлен в основном контексте, в него будет введен основной контекст;в противном случае, если он объявлен в контексте MVC, в него будет введен контекст MVC.

Вот хороший способ (не мой, оригинальная ссылка здесь:http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

Я использовал этот подход, и он отлично работает.По сути, это простой компонент, который содержит (статическую) ссылку на контекст приложения.Ссылаясь на него в конфигурации Spring, он инициализируется.

Посмотрите исходную ссылку, там все очень понятно.

Я считаю, что ты мог бы использовать СинглтонBeanFactoryLocator.Файл beanRefFactory.xml будет содержать фактический контекст приложения. Это будет выглядеть примерно так:

<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 не рекомендует использовать этот класс и ядаяду, но мне он хорошо подошёл там, где я его использовал.

Прежде чем реализовать какие-либо другие предложения, задайте себе эти вопросы...

  • Почему я пытаюсь получить ApplicationContext?
  • Эффективно ли я использую ApplicationContext в качестве локатора сервисов?
  • Могу ли я вообще избежать доступа к ApplicationContext?

Ответить на эти вопросы в определенных типах приложений (например, веб-приложениях) проще, чем в других, но их все равно стоит задать.

Доступ к ApplicationContext в некоторой степени нарушает весь принцип внедрения зависимостей, но иногда у вас нет особого выбора.

Если вы используете веб-приложение, есть еще один способ получить доступ к контексту приложения без использования синглтонов, используя фильтр сервлетов и ThreadLocal.В фильтре вы можете получить доступ к контексту приложения с помощью WebApplicationContextUtils и сохранить либо контекст приложения, либо необходимые bean-компоненты в TheadLocal.

Осторожность:если вы забудете отключить ThreadLocal, у вас возникнут неприятные проблемы при попытке отменить развертывание приложения!Таким образом, вам следует установить его и немедленно начать попытку, которая сбрасывает ThreadLocal в окончательной части.

Конечно, здесь по-прежнему используется синглтон:ThreadLocal.Но настоящие бобы больше не нужны.Их можно даже ограничить областью запроса, и это решение также работает, если у вас есть несколько WAR-файлов в приложении с библиотеками в EAR.Тем не менее, вы можете считать такое использование ThreadLocal таким же плохим, как и использование простых синглтонов.;-)

Возможно, Spring уже предоставляет подобное решение?Я такого не нашел, но точно не знаю.

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

Взгляни на ContextSingletonBeanFactoryLocator.Он предоставляет статические методы доступа для доступа к контекстам Spring, при условии, что они были зарегистрированы определенным образом.

Это некрасиво и сложнее, чем вам хотелось бы, но это работает.

Обратите внимание, что сохраняя любое состояние из текущего ApplicationContext, или ApplicationContext себя в статической переменной - например, используя шаблон Singleton - вы сделаете свои тесты нестабильными и непредсказуемыми, если вы используете Spring-test.Это связано с тем, что Spring-test кэширует и повторно использует контексты приложений в одной и той же JVM.Например:

  1. Тест А выполняется, и он помечен значком @ContextConfiguration({"classpath:foo.xml"}).
  2. Тест B выполняется, и он помечен значком @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Тест C запускается, и он помечен @ContextConfiguration({"classpath:foo.xml"})

При запуске теста А появляется ApplicationContext создается, и все bean-компоненты, реализующие ApplicationContextAware или автоматическое подключение ApplicationContext может писать в статическую переменную.

При запуске теста B происходит то же самое, и статическая переменная теперь указывает на тест B. ApplicationContext

Когда тест C запускается, никакие бобы не создаются как TestContext (и здесь ApplicationContext) из теста A используется повторно.Теперь у вас есть статическая переменная, указывающая на другую ApplicationContext чем тот, который в данный момент содержит бобы для вашего теста.

Есть много способов получить контекст приложения в приложении 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

ПриложениеContextAware:

Интерфейс, который будет реализован любым объектом, который желает быть уведомленным о приложении Context, в котором он работает.Реализация этого интерфейса имеет смысл, например, когда объект требует доступа к набору совместных бобов.

  1. Через Autowired:

    @Autowired
    private ApplicationContext applicationContext;
    

Здесь @Autowired Ключевое слово предоставит applicationContext.У Autowired есть проблемы.Это создаст проблемы во время модульного тестирования.

Не уверен, насколько это будет полезно, но вы также можете получить контекст при инициализации приложения.Это самый быстрый способ получить контекст, даже до того, как @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);

Обратите внимание:приведенный ниже код создаст новый контекст приложения вместо использования уже загруженного.

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

Я знаю, что на этот вопрос есть ответ, но я хотел бы поделиться кодом Kotlin, который я сделал для получения контекста Spring.

Я не специалист, поэтому открыт для критики, отзывов и советов:

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-сервлете:

@WebServlet(name = "MyWebHook", value = "/WebHook")
public class MyWebServlet extends HttpServlet {


    private MyBean byBean
            = SpringUtils.INSTANCE.ctx(this, true).getBean(MyBean.class);


    public MyWebServlet() {

    }
}

Выполните автоподключение в Spring bean, как показано ниже:@Autowired Private ApplicationContext AppContext;

вы получите объект applicationcontext.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top