Получение Spring Application Context

216

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

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

Джо Скора
источник

Ответы:

171

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

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

Дон Киркби
источник
15
Существует также лучший интерфейс для ApplicationContexts - ApplicationContextAware. BeanFactoryAware должен работать, но вам придется преобразовать его в контекст приложения, если вам нужна функциональность контекста приложения.
MetroidFan2002
@Don Kirkby Использование одноэлементного шаблона означает создание экземпляра класса контейнера из статического метода внутри класса контейнера ... как только вы «вручную» создаете объект, которым Spring больше не управляет: как вы решили эту проблему?
Антонин
Мои воспоминания немного расплывчаты после девяти лет, @Antonin, но я не думаю, что синглтон управлялся в контейнере Spring. Я думаю, что единственная работа синглтона состояла в том, чтобы загрузить контейнер из файла XML и сохранить его в статической переменной-члене. Я не возвращал экземпляр его собственного класса, он возвращал экземпляр контейнера Spring.
Дон Киркби
1
Спасибо Дону Киркби, синглтону Spring, обладающему статической ссылкой на себя, который может быть использован не объектами Spring.
Антонин
Это может сработать, @Antonin, если вы сказали контейнеру Spring использовать метод синглтона instance()как фабрику. Однако, я думаю, что я просто позволил всему коду вне контейнера сначала обращаться к контейнеру. Затем этот код может запрашивать объекты из контейнера.
Дон Киркби
118

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

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

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

main application context <- (child) MVC context

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

ом Ном ном
источник
2
Это помогло куче. У меня возникли странные проблемы со старым приложением в Spring 2.0, и ваш ответ был единственным способом, с помощью которого я мог разумно заставить вещи работать с одним ApplicationContext, с одним контейнером Spring IoC.
Стю Томпсон
1
Читатели .. Не забудьте объявить этот SpringBean в вашем springconfig.xml как bean-компонент.
сверхновая
Что, если это уже Бин, и я использую Application.getApplicationContext () (шаблон Singleton), который возвращает экземпляр нового XXXXApplicationContext (XXXX), почему он не работает? Почему я должен это сделать автоматически?
Jaskey
Вы можете использовать @Injectтоже
Алиреза Фаттахи
39

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

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

Посмотрите на оригинальную ссылку, это очень ясно.

Стив Б.
источник
4
Этот подход может потерпеть неудачу, если вы вызываете getBeanиз кода, который выполняется во время модульного теста, поскольку контекст Spring не будет настроен до того, как вы его попросите. Это состояние гонки, в которое я попал сегодня после двух лет успешного использования этого подхода.
HDave
Я сталкиваюсь с тем же самым ... не из модульного теста, а из триггера базы данных .. какие-либо предложения?
Джон Дэвераль
Отличный ответ. Спасибо.
Сагнета
17

Я считаю, что вы могли бы использовать 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-компонента из контекста приложения от whereever будет выглядеть примерно так:

BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
BeanFactoryReference bf = bfl.useBeanFactory("mainContext");
SomeService someService = (SomeService) bf.getFactory().getBean("someService");

Команда Spring не рекомендует использовать этот класс и yadayada, но там, где я его использовал, он мне очень понравился.

Stian
источник
11

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

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

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

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

belugabob
источник
5
Хороший пример - теги JSP; их создание поддерживается контейнером сервлетов, поэтому у них нет выбора, кроме как получить контекст статически. Spring предоставляет базовые классы Tag, и они используют BeanFactoryLocators для получения нужного им контекста.
Скаффман
6

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

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

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

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

Ханс-Петер Стёрр
источник
6
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

Ванесса Скиссато
источник
5

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

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

skaffman
источник
4

Существует множество способов получить контекст приложения в приложении 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)методом вы получите приложениеContext

ApplicationContextAware :

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

  1. Через Autowired :

    @Autowired
    private ApplicationContext applicationContext;

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

Г-н Саджедул Карим
источник
3

Обратите внимание, что, сохраняя любое состояние из текущего ApplicationContextили самого ApplicationContextсебя в статической переменной - например, с помощью одноэлементного шаблона - вы сделаете свои тесты нестабильными и непредсказуемыми, если вы используете 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"})

Когда выполняется тест A, создается an ApplicationContext, и любые компоненты bean, реализующие ApplicationContextAwareили выполняющие автоматическую разводку, ApplicationContextмогут записывать в статическую переменную.

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

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

gogstad
источник
1

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

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

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;
    }
}
Канагавелу Сугамар
источник
0

Я знаю, что на этот вопрос дан ответ, но я хотел бы поделиться кодом 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;
        }
    }
}

Теперь весенний контекст общедоступен и может вызывать тот же метод, не зависящий от контекста (тесты 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() {

    }
}
Джон Джон Пихлер
источник
0

Выполните autowire в Spring bean, как показано ниже: @Autowired private ApplicationContext appContext;

у вас будет объект applicationcontext.

Сандип Джайн
источник
0

Подход 1. Вы можете внедрить ApplicationContext, реализовав интерфейс ApplicationContextAware. Ссылочная ссылка .

@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: Контекст приложения Autowire в любом из управляемых bean-компонентов.

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

Ссылочная ссылка .

Хари Кришна
источник