รับบริบทของแอปพลิเคชัน Spring


216

มีวิธีการแบบคงที่ / ทั่วโลกร้องขอสำเนาของ ApplicationContext ในแอปพลิเคชันสปริงหรือไม่?

สมมติว่าคลาสหลักเริ่มต้นขึ้นและเริ่มต้นบริบทแอปพลิเคชันจำเป็นต้องส่งผ่าน down call ไปยังคลาสใด ๆ ที่ต้องการหรือมีวิธีที่คลาสจะถามบริบทที่สร้างขึ้นก่อนหน้านี้หรือไม่? (ซึ่งฉันคิดว่าจะต้องเป็นซิงเกิล)

คำตอบ:


171

หากวัตถุที่ต้องการเข้าถึงคอนเทนเนอร์คือ bean ในคอนเทนเนอร์ให้ใช้อินเตอร์เฟสBeanFactoryAwareหรือApplicationContextAware

หากวัตถุนอกภาชนะต้องการเข้าถึงภาชนะฉันใช้รูปแบบ GoF singleton มาตรฐานสำหรับคอนเทนเนอร์ฤดูใบไม้ผลิ ด้วยวิธีนี้คุณมีเพียงใบเดียวในใบสมัครของคุณส่วนที่เหลือเป็นถั่วใบเดียวในภาชนะ


15
นอกจากนี้ยังมีอินเตอร์เฟสที่ดีกว่าสำหรับ ApplicationContexts - ApplicationContextAware BeanFactoryAware ควรใช้งานได้ แต่คุณต้องส่งไปยังบริบทของแอปพลิเคชันหากคุณต้องการฟังก์ชั่นบริบทของแอพ
MetroidFan2002

@ Don Kirkby การใช้รูปแบบซิงเกิลหมายถึงการติดตั้งคลาสคอนเทนเนอร์ของคุณจากวิธีการคงที่ภายในคลาสคอนเทนเนอร์ของคุณ ... เมื่อคุณ "ติดตั้งด้วยตนเอง" เพื่อย้ายวัตถุจะไม่ถูกจัดการโดยสปริงอีกต่อไป: คุณจัดการปัญหานี้อย่างไร
Antonin

ความทรงจำของฉันค่อนข้างคลุมเครือเล็กน้อยหลังจากผ่านไปเก้าปี @ แอนโทนิน แต่ฉันไม่คิดว่าซิงเกิลจะถูกจัดการภายในคอนเทนเนอร์ของสปริง ฉันคิดว่างานเดียวของ singleton คือการโหลด container จากไฟล์ XML และเก็บไว้ในตัวแปรสมาชิกแบบคงที่ ฉันไม่ได้คืนอินสแตนซ์ของคลาสของตัวเองมันส่งคืนอินสแตนซ์ของ Spring container
Don Kirkby

1
ขอบคุณดอน Kirkby, ฤดูใบไม้ผลิซิงเกิลที่มีการอ้างอิงแบบคงที่กับตัวเองจึงอาจใช้งานได้โดยวัตถุสปริง
Antonin

อาจเป็นไปได้ว่า @Antonin ถ้าคุณบอกให้ Spring container ใช้instance()วิธีการของ singleton เป็นโรงงาน อย่างไรก็ตามฉันคิดว่าฉันแค่ปล่อยให้โค้ดทั้งหมดนอกคอนเทนเนอร์เข้าถึงคอนเทนเนอร์ก่อน จากนั้นรหัสสามารถร้องขอวัตถุจากภาชนะ
Don Kirkby

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 เดียวโดยมีคอนเทนเนอร์ IoC ของ Spring เดียว
Stu Thompson

1
ผู้อ่าน .. อย่าลืมที่จะประกาศ SpringBean นี้ใน springconfig.xml ของคุณเป็นถั่ว
supernova

จะเกิดอะไรขึ้นถ้านี่เป็น Bean อยู่แล้วและฉันใช้ Application.getApplicationContext () (รูปแบบซิงเกิล) ซึ่งส่งคืนอินสแตนซ์ของ XXXXApplicationContext (XXXX) ใหม่ทำไมมันไม่ทำงาน ทำไมฉันต้องถามตอบอัตโนมัติ
Jaskey

คุณสามารถใช้ได้@Injectเช่นกัน
Alireza Fattahi

39

นี่เป็นวิธีที่ดี (ไม่ใช่ของฉันการอ้างอิงดั้งเดิมอยู่ที่นี่: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

ฉันใช้วิธีนี้และใช้งานได้ดี โดยทั่วไปมันเป็นถั่วธรรมดาที่เก็บ (คงที่) อ้างอิงถึงบริบทของแอปพลิเคชัน โดยการอ้างอิงในการกำหนดค่าสปริงมันเริ่มต้นได้

ลองดูที่อ้างอิงต้นฉบับมันชัดเจนมาก


4
วิธีการนั้นอาจล้มเหลวหากคุณโทรgetBeanจากรหัสที่ทำงานในระหว่างการทดสอบหน่วยเนื่องจากบริบท Spring จะไม่ได้รับการตั้งค่าก่อนที่คุณจะถาม มันเป็นเงื่อนไขการแข่งขันที่ฉันเพิ่งเข้ามาในวันนี้หลังจาก 2 ปีที่ประสบความสำเร็จในการใช้วิธีการนี้
HDave

ฉันกำลังทำงานในสิ่งเดียวกัน .. ไม่ได้มาจากการทดสอบหน่วย แต่จากทริกเกอร์ฐานข้อมูล .. ข้อเสนอแนะใด ๆ ?
John Deverall

การตอบสนองที่ยอดเยี่ยม ขอบคุณ.
sagneta

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 จาก applicationcontext จากที่ใดจะเป็นดังนี้:

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

ทีมฤดูใบไม้ผลิไม่สนับสนุนการใช้คลาสนี้และ yadayada แต่มันเหมาะกับฉันอย่างมากที่ฉันใช้มัน


11

ก่อนที่คุณจะดำเนินการตามคำแนะนำอื่น ๆ ให้ถามตัวเองด้วยคำถามเหล่านี้ ...

  • เหตุใดฉันจึงพยายามรับ ApplicationContext
  • ฉันใช้ ApplicationContext เป็นตัวระบุบริการอย่างมีประสิทธิภาพหรือไม่
  • ฉันสามารถหลีกเลี่ยงการเข้าถึง ApplicationContext ได้ทั้งหมดหรือไม่

คำตอบของคำถามเหล่านี้ง่ายกว่าในบางประเภทของแอปพลิเคชั่น (ตัวอย่างเช่นแอพพลิเคชั่นเว็บ) กว่าที่อยู่ในแอพพลิเคชั่นอื่น ๆ

การเข้าถึง ApplicationContext นั้นเป็นการละเมิดหลักการการฉีดขึ้นรูปทั้งหมด แต่บางครั้งคุณก็ไม่มีทางเลือกมากนัก


5
ตัวอย่างที่ดีคือแท็ก JSP การสร้างของพวกเขาถูกควบคุมโดยภาชนะ servlet ดังนั้นพวกเขาจึงไม่มีทางเลือกนอกจากจะได้รับบริบทแบบคงที่ Spring ให้คลาส Tag พื้นฐานและใช้ BeanFactoryLocators เพื่อรับบริบทที่ต้องการ
skaffman

6

หากคุณใช้เว็บแอพก็มีอีกวิธีหนึ่งในการเข้าถึงบริบทแอปพลิเคชันโดยไม่ต้องใช้ซิงเกิลตันโดยใช้ servletfilter และ ThreadLocal ในตัวกรองคุณสามารถเข้าถึงบริบทแอปพลิเคชันโดยใช้ WebApplicationContextUtils และเก็บบริบทแอปพลิเคชันหรือถั่วที่จำเป็นใน TheadLocal

ข้อควรระวัง: หากคุณลืมที่จะยกเลิกการตั้งค่า ThreadLocal คุณจะได้รับปัญหาที่น่ารังเกียจเมื่อพยายามยกเลิกการปรับใช้แอปพลิเคชัน! ดังนั้นคุณควรตั้งค่าและเริ่มลองทันทีที่ยกเลิกการ ThreadLocal ในส่วนสุดท้าย

แน่นอนว่าสิ่งนี้ยังคงใช้ซิงเกิลตัน: ThreadLocal แต่ถั่วจริงไม่จำเป็นต้องเป็นอีกต่อไป สามารถกำหนดขอบเขตการร้องขอได้และวิธีนี้ยังใช้งานได้หากคุณมีสงครามหลายรายการในแอปพลิเคชันที่มี libaries ใน EAR อย่างไรก็ตามคุณอาจพิจารณาใช้ ThreadLocal นี้ไม่ดีเท่าการใช้ Singletons ธรรมดา ;-)

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 ContextSingletonBeanFactoryLocatorมันให้ผู้เข้าถึงแบบคงที่ที่จะได้รับบริบทของฤดูใบไม้ผลิสมมติว่าพวกเขาได้รับการลงทะเบียนในบางวิธี

มันไม่ได้สวยและซับซ้อนกว่าที่คุณต้องการ แต่มันใช้งานได้


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)วิธีการที่นี่คุณจะได้รับ applicationContext

ApplicationContextAware :

อินเทอร์เฟซที่จะนำไปใช้โดยวัตถุใด ๆ ที่ต้องการรับการแจ้งเตือนของ ApplicationContext ที่ทำงานการใช้อินเทอร์เฟซนี้ทำให้รู้สึกเช่นเมื่อวัตถุต้องเข้าถึงชุดถั่วร่วมมือ

  1. ผ่าน Autowired :

    @Autowired
    private ApplicationContext applicationContext;

@Autowiredคำหลักที่นี่จะให้ applicationContext Autowired มีปัญหาบางอย่าง มันจะสร้างปัญหาระหว่างการทดสอบหน่วย


3

โปรดทราบว่าโดยการจัดเก็บสถานะใด ๆ จากปัจจุบันApplicationContextหรือApplicationContextตัวเองในตัวแปรคงที่ - ตัวอย่างเช่นการใช้รูปแบบซิงเกิล - คุณจะทำให้การทดสอบของคุณไม่เสถียรและไม่แน่นอนถ้าคุณใช้การทดสอบสปริง นี่เป็นเพราะแคชการทดสอบสปริงและใช้บริบทของแอปพลิเคชันซ้ำใน JVM เดียวกัน ตัวอย่างเช่น:

  1. ทดสอบ A และมันมีหมายเหตุประกอบไว้ด้วย @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.xmlWeb.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 Context

ฉันไม่ใช่ผู้เชี่ยวชาญดังนั้นฉันจึงเปิดให้นักวิจารณ์คำวิจารณ์และคำแนะนำ:

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, beans, คลาสอินสแตนซ์ที่สร้างด้วยตนเอง) เช่นบน 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

ทำ autowire ใน Spring bean ดังต่อไปนี้: @Autowired ส่วนตัว 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 Application context ในถั่วใด ๆ ที่มีการจัดการของสปริง

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

การอ้างอิงการเชื่อมโยง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.