Отримання контексту програми для весни


216

Чи є спосіб статично / глобально вимагати копії ApplicationContext у додатку Spring?

Якщо припустити, що основний клас запускається та ініціалізує контекст програми, чи потрібно передавати це вниз через стек виклику будь-яким класам, які йому потрібні, чи є спосіб клас запитувати раніше створений контекст? (Який я вважаю, що він повинен бути одиноким?)

Відповіді:


171

Якщо об'єктом, який потребує доступу до контейнера, є боб у контейнері, просто реалізуйте інтерфейси BeanFactoryAware або ApplicationContextAware .

Якщо об’єкт поза контейнером потребує доступу до контейнера, я використовував стандартний шаблон однофазного GoF для весняного контейнера. Таким чином, у вас є лише один сингтон у вашій заявці, решта - це всі квасоля в контейнері.


15
Існує також кращий інтерфейс для ApplicationContexts - ApplicationContextAware. BeanFactoryAware має працювати, але вам доведеться привести його в контекст програми, якщо вам потрібна функція контексту програми.
MetroidFan2002

@Don Kirkby Використання шаблону singleton означає присвоєння класу контейнерів статичним методом у вашому класі контейнерів ... як тільки ви "вручну" інстанціюєте об'єкт, його вже не управляє Spring: як ви вирішили цю проблему?
Антонін

Моя пам’ять стає дещо невиразною після дев'яти років, @Antonin, але я не думаю, що сингл управлявся в контейнері Spring. Я думаю, що єдиним завданням одиночки було завантаження контейнера з XML-файлу та зберігання його в статичній змінній члена. Я не повертав екземпляр власного класу, він повертав екземпляр контейнера Spring.
Дон Кіркбі

1
Дякуємо Дон Кіркбі, весняному синглу, що володіє статичною посиланням на себе, таким чином, можливо, може бути використаний не об'єктами 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 Spring.
Стю Томпсон

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 не буде налаштований перед тим, як запитати його. Його перегонний стан я щойно заграв сьогодні після 2 років успішного використання цього підходу.
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>

І код, щоб отримати квасоля з applicationcontext з будь-якого місця, був би приблизно таким:

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

Колектив весна перешкоджає використанню цього класу та ядаяди, але це мені добре підходило там, де я ним користувався.


11

Перш ніж реалізувати будь-яку з інших пропозицій, задайте собі ці питання ...

  • Чому я намагаюся отримати ApplicationContext?
  • Чи ефективно я використовую ApplicationContext як локатор служби?
  • Чи можу я взагалі уникати доступу до ApplicationContext?

Відповіді на ці запитання у певних типах додатків (наприклад, веб-додатки) простіші, ніж в інших, але їх варто все-таки запитати.

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


5
Хороший приклад - теги JSP; їх створення регулюється контейнером сервлетів, тому їм не залишається іншого вибору, крім як статичний отримання. Spring пропонує базові класи тегів, і вони використовують BeanFactoryLocators, щоб отримати необхідні контексти.
skaffman

6

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

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

Звичайно, для цього все ще використовується синглтон: ThreadLocal. Але справжніх бобів більше не потрібно. Можна навіть визначити обсяг запиту, і це рішення також працює, якщо у програмі є декілька ВІН у програмі з бібліотеками EAR. Тим не менш, ти можеш вважати таке використання ThreadLocal таким же поганим, як і використання простих однотонних. ;-)

Можливо, Весна вже пропонує подібне рішення? Я не знайшов його, але точно не знаю.


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, якщо припустити, що вони були зареєстровані певними способами.

Це не красиво і складніше, ніж, можливо, ви хотіли б, але це працює.


4

Існує багато способів отримати контекст програми у весняній програмі. Ці дані наведені нижче:

  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 :

Інтерфейс, який повинен реалізовувати будь-який об’єкт, який бажає отримувати повідомлення про ApplicationContext, в якому він працює. Реалізація цього інтерфейсу має сенс, наприклад, коли об’єкт вимагає доступу до набору співпрацюючих бобів.

  1. Через автопровід :

    @Autowired
    private ApplicationContext applicationContext;

Тут @Autowiredключове слово надасть applicationContext. Автопровід має певну проблему. Це створить проблему під час тестування одиниць.


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 запускається, ApplicationContextстворюється і будь-які боби, що ApplicationContextAwareреалізують або автопроводжують, ApplicationContextможуть записати в статичну змінну.

Коли тест B працює, те ж саме відбувається, і статична змінна тепер вказує на тест B ApplicationContext

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


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

Я знаю, що на це питання відповіли, але я хотів би поділитися кодом Котліна, який я зробив, щоб отримати Весняний контекст.

Я не фахівець, тому я відкритий для критиків, рецензій та порад:

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

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

    }
}

0

Зробіть автопровід у весняному бобі, як зазначено нижче: @Autwired 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: Контекст застосування автоматичної проводки в будь-якій з весняних бобів.

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

Посилання на посилання .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.