Nhận bối cảnh ứng dụng mùa xuân


216

Có cách nào để yêu cầu tĩnh / toàn cầu một bản sao của ApplicationContext trong ứng dụng Spring không?

Giả sử lớp chính khởi động và khởi tạo bối cảnh ứng dụng, nó có cần chuyển nó qua ngăn xếp cuộc gọi đến bất kỳ lớp nào cần nó không, hoặc có cách nào để một lớp hỏi về bối cảnh được tạo trước đó không? (Mà tôi cho rằng phải là người độc thân?)

Câu trả lời:


171

Nếu đối tượng cần truy cập vào vùng chứa là một bean trong vùng chứa, chỉ cần thực hiện các giao diện BeanFactoryAware hoặc ApplicationContextAware .

Nếu một đối tượng bên ngoài vùng chứa cần truy cập vào vùng chứa, tôi đã sử dụng một mẫu đơn GoF tiêu chuẩn cho vùng chứa lò xo. Bằng cách đó, bạn chỉ có một singleton trong ứng dụng của mình, phần còn lại là tất cả các hạt singleton trong container.


15
Ngoài ra còn có một giao diện tốt hơn cho ApplicationContexts - ApplicationContextAware. BeanFactoryAware sẽ hoạt động nhưng bạn phải chuyển nó sang ngữ cảnh ứng dụng nếu bạn cần chức năng ngữ cảnh ứng dụng.
MetroidFan2002

@Don Kirkby Sử dụng mẫu singleton có nghĩa là kích hoạt lớp container của bạn từ một phương thức tĩnh trong lớp container của bạn ... một khi bạn "thủ công" tạo ra một đối tượng mà Spring không quản lý nữa: bạn đã giải quyết vấn đề này như thế nào?
Antonin

Trí nhớ của tôi hơi mơ hồ sau chín năm, @Antonin, nhưng tôi không nghĩ rằng người độc thân được quản lý trong container Mùa xuân. Tôi nghĩ rằng công việc duy nhất của singleton là tải container từ tệp XML và giữ nó trong một biến thành viên tĩnh. Tôi đã không trả về một thể hiện của lớp riêng của nó, nó trả về một thể hiện của vùng chứa Spring.
Don Kirkby

1
Cảm ơn Don Kirkby, một người độc thân mùa xuân sở hữu một tài liệu tham khảo tĩnh cho chính nó, do đó có thể sử dụng được bởi các đối tượng không phải là Spring.
Antonin

Điều đó có thể hoạt động, @Antonin, nếu bạn bảo container Spring sử dụng instance()phương thức của singleton như một nhà máy. Tuy nhiên, tôi nghĩ rằng tôi chỉ để tất cả các mã bên ngoài container truy cập vào container trước. Sau đó, mã đó có thể yêu cầu các đối tượng từ container.
Don Kirkby

118

Bạn có thể thực hiện ApplicationContextAwarehoặc chỉ sử dụng @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeansẽ ApplicationContextđược tiêm, trong đó đậu này được khởi tạo. Ví dụ: nếu bạn có ứng dụng web với hệ thống phân cấp bối cảnh khá chuẩn:

main application context <- (child) MVC context

SpringBeanđược khai báo trong bối cảnh chính, nó sẽ có bối cảnh chính được đưa vào; mặt khác, nếu nó được khai báo trong bối cảnh MVC, nó sẽ được thêm vào bối cảnh MVC.


2
Điều này đã giúp một bó. Tôi đã gặp một số vấn đề kỳ lạ với một ứng dụng cũ hơn với Spring 2.0 và câu trả lời của bạn là cách duy nhất tôi có thể khiến mọi thứ hoạt động với một ApplicationContext duy nhất, với một bộ chứa Spring IoC duy nhất.
Stu Thompson

1
Độc giả..Đừng quên khai báo SpringBean này trong springconfig.xml của bạn dưới dạng bean.
siêu tân tinh

Điều gì sẽ xảy ra nếu đây đã là Bean và tôi sử dụng Application.getApplicationContext () (mẫu Singleton), trả về một thể hiện của XXXXApplicationContext (XXXX) mới, tại sao nó không hoạt động? Tại sao tôi phải tự động mua nó?
Jaskey

Bạn có thể sử dụng @Injectquá
Alireza Fattahi

39

Đây là một cách hay (không phải của tôi, tài liệu tham khảo ban đầu có ở đây: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

Tôi đã sử dụng phương pháp này và nó hoạt động tốt. Về cơ bản, nó là một bean đơn giản chứa tham chiếu (tĩnh) cho bối cảnh ứng dụng. Bằng cách tham chiếu nó trong cấu hình mùa xuân, nó được khởi tạo.

Hãy nhìn vào ref gốc, nó rất rõ ràng.


4
Cách tiếp cận đó có thể thất bại nếu bạn gọi getBeantừ mã chạy trong bài kiểm tra Đơn vị vì bối cảnh Mùa xuân sẽ không được thiết lập trước khi bạn yêu cầu. Đó là một điều kiện cuộc đua tôi đã đâm sầm vào ngày hôm nay sau 2 năm sử dụng thành công phương pháp này.
HDave

Tôi đang chạy vào cùng một thứ .. không phải từ một bài kiểm tra đơn vị mà từ một trình kích hoạt cơ sở dữ liệu .. có đề xuất nào không?
John Deverall

Phản ứng tuyệt vời. Cảm ơn bạn.
sagneta

17

Tôi tin rằng bạn có thể sử dụng SingletonBeanFactoryLocator . Tệp beanRefFactory.xml sẽ chứa applicationContext thực tế. Nó sẽ giống như thế này:

<bean id="mainContext" class="org.springframework.context.support.ClassPathXmlApplicationContext">
     <constructor-arg>
        <list>
            <value>../applicationContext.xml</value>
        </list>
     </constructor-arg>
 </bean>

Và mã để lấy một bean từ ứng dụng liên kết từ bất cứ nơi nào sẽ là một cái gì đó như thế này:

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

Nhóm Spring không khuyến khích việc sử dụng lớp này và yadayada, nhưng nó rất phù hợp với tôi nơi tôi đã sử dụng nó.


11

Trước khi bạn thực hiện bất kỳ đề xuất nào khác, hãy tự hỏi mình những câu hỏi này ...

  • Tại sao tôi lại cố gắng lấy ApplicationContext?
  • Tôi có hiệu quả khi sử dụng ApplicationContext làm công cụ định vị dịch vụ không?
  • Tôi có thể tránh truy cập ApplicationContext không?

Câu trả lời cho những câu hỏi này dễ dàng hơn trong một số loại ứng dụng (ví dụ: ứng dụng Web) so với những câu hỏi khác, nhưng dù sao cũng đáng để hỏi.

Truy cập ApplicationContext không vi phạm nguyên tắc tiêm phụ thuộc toàn bộ, nhưng đôi khi bạn không có nhiều sự lựa chọn.


5
Một ví dụ điển hình là các thẻ JSP; sáng tạo của họ được điều khiển bởi thùng chứa servlet, vì vậy họ không có lựa chọn nào khác ngoài việc có được bối cảnh tĩnh. Spring cung cấp các lớp Tag cơ sở và họ sử dụng BeanFactoryLocators để có được bối cảnh họ cần.
skaffman

6

Nếu bạn sử dụng một ứng dụng web thì cũng có một cách khác để truy cập vào bối cảnh ứng dụng mà không cần sử dụng singletons bằng cách sử dụng bộ lọc dịch vụ và ThreadLocal. Trong bộ lọc, bạn có thể truy cập vào bối cảnh ứng dụng bằng WebApplicationContextUtils và lưu trữ bối cảnh ứng dụng hoặc các bean cần thiết trong TheadLocal.

Thận trọng: nếu bạn quên hủy đặt ThreadLocal, bạn sẽ gặp các vấn đề khó chịu khi cố gắng không triển khai ứng dụng! Vì vậy, bạn nên thiết lập nó và ngay lập tức bắt đầu thử mà bỏ cài đặt ThreadLocal trong phần cuối cùng.

Tất nhiên, điều này vẫn sử dụng một singleton: ThreadLocal. Nhưng đậu thực sự không cần phải nữa. Thậm chí có thể được yêu cầu trong phạm vi yêu cầu và giải pháp này cũng hoạt động nếu bạn có nhiều WAR trong một Ứng dụng với các từ khóa trong EAR. Tuy nhiên, bạn có thể coi việc sử dụng ThreadLocal này cũng tệ như việc sử dụng các singletons đơn giản. ;-)

Có lẽ Spring đã cung cấp một giải pháp tương tự? Tôi đã không tìm thấy một, nhưng tôi không biết chắc chắn.


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

Nguồn: http://sujitpal.blogspot.de/2007/03/accessing-spring-beans-from-legacy-code.html


5

Hãy xem ContextSingletonBeanFactoryLocator . Nó cung cấp các bộ truy cập tĩnh để có được các bối cảnh của Spring, giả sử rằng chúng đã được đăng ký theo một số cách nhất định.

Nó không đẹp và phức tạp hơn có lẽ bạn thích, nhưng nó hoạt động.


4

Có nhiều cách để có được bối cảnh ứng dụng trong ứng dụng Spring. Những người được đưa ra dưới đây:

  1. Thông qua 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;
    }
    }

Ở đây setApplicationContext(ApplicationContext applicationContext)phương thức bạn sẽ nhận được applicationContext

ApplicationContextAware :

Giao diện được triển khai bởi bất kỳ đối tượng nào muốn được thông báo về ApplicationContext mà nó chạy. Triển khai giao diện này có ý nghĩa ví dụ khi một đối tượng yêu cầu quyền truy cập vào một nhóm các bean cộng tác.

  1. Qua Autowired :

    @Autowired
    private ApplicationContext applicationContext;

Ở đây @Autowiredtừ khóa sẽ cung cấp applicationContext. Autowired có một số vấn đề. Nó sẽ tạo ra vấn đề trong quá trình kiểm tra đơn vị.


3

Lưu ý rằng bằng cách lưu trữ bất kỳ trạng thái nào từ hiện tại ApplicationContexthoặc ApplicationContextchính nó trong một biến tĩnh - ví dụ: sử dụng mẫu đơn - bạn sẽ làm cho các bài kiểm tra của mình không ổn định và không thể đoán trước nếu bạn đang sử dụng Spring-test. Điều này là do Spring-test cache và tái sử dụng các bối cảnh ứng dụng trong cùng một JVM. Ví dụ:

  1. Kiểm tra A chạy và nó được chú thích với @ContextConfiguration({"classpath:foo.xml"}).
  2. Chạy thử B và nó được chú thích với @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Chạy thử C và nó được chú thích với @ContextConfiguration({"classpath:foo.xml"})

Khi Test A chạy, một cái ApplicationContextđược tạo ra và bất kỳ hạt đậu nào đang ẩn ApplicationContextAwarehoặc tự động ApplicationContextcó thể ghi vào biến tĩnh.

Khi Test B chạy điều tương tự xảy ra và biến tĩnh bây giờ trỏ đến Test B ApplicationContext

Khi Test C chạy, không có hạt nào được tạo ra khi TestContext(và ở đây ApplicationContext) từ Test A được sử dụng lại. Bây giờ bạn đã có một biến tĩnh trỏ đến một biến khác ApplicationContextso với biến hiện đang giữ các hạt đậu cho bài kiểm tra của bạn.


1

Không chắc chắn điều này sẽ hữu ích như thế nào, nhưng bạn cũng có thể có được bối cảnh khi bạn khởi tạo ứng dụng. Đây là sớm nhất bạn có thể có được bối cảnh, ngay cả trước khi một @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

Xin lưu ý rằng; đoạn mã dưới đây sẽ tạo bối cảnh ứng dụng mới thay vì sử dụng bối cảnh đã được tải.

private static final ApplicationContext context = 
               new ClassPathXmlApplicationContext("beans.xml");

Cũng lưu ý rằng beans.xmlnên là một phần của src/main/resourcesphương tiện trong chiến tranh, đó là một phần của WEB_INF/classesứng dụng thực sự sẽ được tải thông qua applicationContext.xmlđược đề cập tại Web.xml.

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>META-INF/spring/applicationContext.xml</param-value>
</context-param>

Rất khó để đề cập đến applicationContext.xmlđường dẫn trong ClassPathXmlApplicationContextconstructor. ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")sẽ không thể xác định vị trí các tập tin.

Vì vậy, tốt hơn là sử dụng applicationContext hiện có bằng cách sử dụng các chú thích.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }
}

0

Tôi biết câu hỏi này đã được trả lời, nhưng tôi muốn chia sẻ mã Kotlin mà tôi đã làm để lấy lại Bối cảnh mùa xuân.

Tôi không phải là một chuyên gia, vì vậy tôi cởi mở với các nhà phê bình, đánh giá và lời khuyên:

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

Bây giờ, một bối cảnh mùa xuân đã có sẵn công khai, có thể gọi cùng một phương thức độc lập với bối cảnh (kiểm tra Junit, đậu, các lớp được khởi tạo thủ công) như trên Servlet Java này:

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


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


    public MyWebServlet() {

    }
}

0

Thực hiện autowire trong Spring bean như sau: @Autowired private ApplicationContext appContext;

bạn sẽ là đối tượng ứng dụng.


0

Cách tiếp cận 1: Bạn có thể tiêm ApplicationContext bằng cách triển khai giao diện ApplicationContextAware. Liên kết tham khảo .

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

Cách tiếp cận 2: Bối cảnh ứng dụng Autowire trong bất kỳ hạt đậu nào được quản lý mùa xuân.

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

Liên kết tham khảo .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.