Получение контекста приложения Spring

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

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
222
0
357 070
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

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

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

Взгляните на исходную ссылку, это очень ясно.

Этот подход может потерпеть неудачу, если вы вызовете getBean из кода, который выполняется во время модульного теста, потому что контекст Spring не будет настроен до того, как вы его попросите. Это состояние гонки, в которое я только что врезался сегодня после 2 лет успешного использования этого подхода.

HDave 24.08.2012 07:58

Я сталкиваюсь с тем же ... не из модульного теста, а из триггера базы данных ... есть предложения?

John Deverall 15.04.2017 22:14

Отличный ответ. Спасибо.

sagneta 05.02.2020 16:57

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

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

Ответ принят как подходящий

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

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

Также есть лучший интерфейс для ApplicationContexts - ApplicationContextAware. BeanFactoryAware должен работать, но вам придется преобразовать его в контекст приложения, если вам нужна функциональность контекста приложения.

MetroidFan2002 30.03.2009 20:07

@Don Kirkby Использование шаблона singleton означает создание экземпляра класса контейнера из статического метода в классе контейнера ... как только вы создаете экземпляр объекта «вручную», Spring больше не управляет им: как вы справились с этой проблемой?

Antonin 16.10.2017 12:45

Моя память немного расплывчата после девяти лет, @Antonin, но я не думаю, что синглтон управлялся в контейнере Spring. Я думаю, что единственная задача синглтона заключалась в том, чтобы загрузить контейнер из файла XML и сохранить его в статической переменной-члене. Я не вернул экземпляр своего собственного класса, он вернул экземпляр контейнера Spring.

Don Kirkby 16.10.2017 19:32

Спасибо Дону Киркби, синглтону Spring, имеющему статическую ссылку на себя, поэтому его можно использовать не объектами Spring.

Antonin 17.10.2017 18:56

Это может сработать, @Antonin, если вы укажете контейнеру Spring использовать метод синглтона instance() в качестве фабрики. Однако я думаю, что просто позволю всему коду за пределами контейнера сначала получить доступ к контейнеру. Затем этот код может запрашивать объекты из контейнера.

Don Kirkby 17.10.2017 19:14

Время от времени я создаю статические переменные singleton, чтобы позволить бинам получить доступ к устаревшему коду. Сделать это легко. Вы просто определяете static MyBean singleton в классе Bean, а затем устанавливаете singleton = this во всех конструкторах этого bean-компонента. Если вы еще не определили конструктор, просто добавьте конструктор по умолчанию, чтобы вы могли установить синглтон. Вы можете сделать singleton общедоступным и получить к нему прямой доступ. Вместо этого я делаю singleton закрытым, а затем предоставляю метод public static MyBean get() для класса, который возвращает singleton. Проще простого.

CryptoFool 07.03.2021 03:06

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

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

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

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

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

skaffman 27.09.2008 12:31

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

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

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

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

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

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

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

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

main application context <- (child) MVC context

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

Это очень помогло. У меня есть некоторые странные проблемы со старым приложением с Spring 2.0, и ваш ответ был единственным способом, которым я мог разумно заставить все работать с одним ApplicationContext, с одним контейнером Spring IoC.

Stu Thompson 27.11.2012 19:57

Читатели ... Не забудьте объявить этот SpringBean в вашем springconfig.xml как bean-компонент.

supernova 28.04.2014 03:38

Что, если это уже Bean, и я использую Application.getApplicationContext () (шаблон Singleton), который возвращает экземпляр нового XXXXApplicationContext (XXXX), почему это не работает? Почему я должен его автоматически подключать?

JaskeyLam 02.11.2014 17:26

Вы тоже можете использовать @Inject

Alireza Fattahi 22.11.2015 11:40

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

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

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

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

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

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

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

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

  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

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. Через 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);

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

вы будете объектом applicationcontext.

Подход 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 в любом из Spring управляемых bean-компонентов.

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

Ссылка связь.

Даже после добавления @Autowire, если ваш класс не является RestController или классом конфигурации, объект applicationContext получал нулевое значение. Пытался создать новый класс с помощью ниже, и он работает нормально:

@Component
public class SpringContext implements ApplicationContextAware{

   private static ApplicationContext applicationContext;

   @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws 
     BeansException {
    this.applicationContext=applicationContext;
   }
 }

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

    applicationContext.getBean(String serviceName,Interface.Class)

Я использую простой стандартизованный способ разрешить внешний доступ к любому из моих собственных синглтон-компонентов Spring Beans. С помощью этого метода я продолжаю позволять Spring создавать экземпляр Bean. Вот что я делаю:

  1. Определите частную статическую переменную того же типа, что и включающий класс.
  2. Установите для этой переменной значение this в каждом конструкторе класса. Если у класса нет конструкторов, добавьте конструктор по умолчанию, в котором нужно установить переменную.
  3. Определите общедоступный статический метод получения, который возвращает одноэлементную переменную.

Вот пример:

@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, вы можете добавить этот механизм напрямую к нему. В противном случае вы можете либо создать подкласс для этого, либо добавить этот механизм к любому компоненту, имеющему доступ к ApplicationContext, а затем использовать его для получения доступа к ApplicationContext из любого места. Важно то, что именно этот механизм позволит вам попасть в среду Spring.

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