Mendapatkan Konteks Aplikasi Musim Semi

216

Apakah ada cara untuk secara statis / global meminta salinan ApplicationContext dalam aplikasi Spring?

Dengan asumsi kelas utama memulai dan menginisialisasi konteks aplikasi, apakah itu perlu meneruskannya melalui tumpukan panggilan ke kelas yang membutuhkannya, atau adakah cara bagi kelas untuk meminta konteks yang dibuat sebelumnya? (Yang saya anggap harus singleton?)

Joe Skora
sumber

Jawaban:

171

Jika objek yang membutuhkan akses ke wadah adalah kacang di wadah, cukup laksanakan BeanFactoryAware atau ApplicationContextAware antarmuka .

Jika objek di luar wadah membutuhkan akses ke wadah, saya telah menggunakan pola tunggal standar GoF untuk wadah pegas. Dengan begitu, Anda hanya memiliki satu singleton dalam aplikasi Anda, sisanya adalah kacang tunggal dalam wadah.

Don Kirkby
sumber
15
Ada juga antarmuka yang lebih baik untuk ApplicationContexts - ApplicationContextAware. BeanFactoryAware seharusnya berfungsi tetapi Anda harus mengubahnya ke konteks aplikasi jika Anda memerlukan fungsionalitas konteks aplikasi.
MetroidFan2002
@Don Kirkby Menggunakan pola singleton berarti melembagakan kelas kontainer Anda dari metode statis dalam kelas kontainer Anda ... setelah Anda "secara manual" instanciate objek yang tidak dikelola oleh Spring lagi: bagaimana Anda mengatasi masalah ini?
Antonin
Ingatan saya sedikit kabur setelah sembilan tahun, @Antonin, tapi saya rasa singleton tidak dikelola dalam wadah Spring. Saya pikir satu-satunya tugas singleton adalah memuat wadah dari file XML dan menyimpannya dalam variabel anggota statis. Saya tidak mengembalikan instance dari kelasnya sendiri, itu mengembalikan instance wadah Spring.
Don Kirkby
1
Terima kasih Don Kirkby, singleton Spring yang memiliki referensi statis untuk dirinya sendiri, sehingga dapat digunakan oleh objek non Spring mungkin.
Antonin
Itu mungkin berhasil, @Antonin, jika Anda memberi tahu kontainer Spring untuk menggunakan metode singleton instance()sebagai pabrik. Namun, saya pikir saya hanya membiarkan semua kode di luar wadah mengakses wadah terlebih dahulu. Kemudian kode itu dapat meminta objek dari wadah.
Don Kirkby
118

Anda dapat menerapkan ApplicationContextAwareatau hanya menggunakan @Autowired:

public class SpringBean {
  @Autowired
  private ApplicationContext appContext;
}

SpringBeanakan ApplicationContextdisuntikkan, di mana kacang ini dipakai. Misalnya jika Anda memiliki aplikasi web dengan hierarki konteks standar yang cantik:

main application context <- (child) MVC context

dan SpringBeandinyatakan dalam konteks utama, konteks utama akan disuntikkan; jika tidak, jika dinyatakan dalam konteks MVC, konteks MVC akan disuntikkan.

omnomnom
sumber
2
Ini banyak membantu. Saya punya beberapa masalah aneh dengan aplikasi yang lebih lama dengan Spring 2.0 dan jawaban Anda adalah satu-satunya cara saya bisa dengan waras mendapatkan hal-hal untuk bekerja dengan ApplicationContext tunggal, dengan satu wadah Spring IoC.
Stu Thompson
1
Pembaca..Jangan lupa untuk menyatakan SpringBean ini di springconfig.xml Anda sebagai kacang.
supernova
Bagaimana jika ini sudah menjadi Kacang, dan saya menggunakan Application.getApplicationContext () (pola Singleton), yang mengembalikan instance dari XXXXApplicationContext (XXXX) baru, mengapa itu tidak berfungsi? Kenapa saya harus autowired itu?
Jaskey
Anda dapat menggunakan @Injectjuga
Alireza Fattahi
39

Ini cara yang bagus (bukan milik saya, referensi aslinya ada di sini: http://sujitpal.blogspot.com/2007/03/accessing-spring-beans-from-legacy-code.html

Saya telah menggunakan pendekatan ini dan berfungsi dengan baik. Pada dasarnya ini adalah kacang sederhana yang menyimpan referensi (statis) untuk konteks aplikasi. Dengan merujuknya pada konfigurasi pegas, itu diinisialisasi.

Lihatlah referensi asli, sangat jelas.

Steve B.
sumber
4
Pendekatan itu bisa gagal jika Anda menelepon getBeandari kode yang berjalan selama tes Unit karena konteks Spring tidak akan diatur sebelum Anda memintanya. Ini adalah kondisi balapan yang baru saja saya alami hari ini setelah 2 tahun berhasil menggunakan pendekatan ini.
HDave
Saya mengalami hal yang sama .. bukan dari unit test tetapi dari pemicu basis data .. ada saran?
John Deverall
Respon luar biasa. Terima kasih.
sagneta
17

Saya yakin Anda bisa menggunakan SingletonBeanFactoryLocator . File beanRefFactory.xml akan menampung applicationContext yang sebenarnya, akan seperti ini:

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

Dan kode untuk mendapatkan kacang dari konteks aplikasi dari mana pun akan menjadi seperti ini:

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

Tim Spring mencegah penggunaan kelas ini dan yadayada, tapi itu sangat cocok untukku di tempat aku menggunakannya.

stian
sumber
11

Sebelum Anda menerapkan saran lainnya, tanyakan pada diri sendiri pertanyaan-pertanyaan ini ...

  • Mengapa saya mencoba mendapatkan ApplicationContext?
  • Apakah saya secara efektif menggunakan ApplicationContext sebagai pencari layanan?
  • Bisakah saya menghindari mengakses ApplicationContext sama sekali?

Jawaban atas pertanyaan-pertanyaan ini lebih mudah di beberapa jenis aplikasi (aplikasi Web, misalnya) daripada yang lain, tetapi tetap layak ditanyakan.

Mengakses ApplicationContext memang melanggar prinsip injeksi dependensi secara keseluruhan, tetapi terkadang Anda tidak punya banyak pilihan.

belugabob
sumber
5
Contoh yang baik adalah tag JSP; ciptaan mereka diatur oleh wadah servlet, sehingga mereka tidak punya pilihan selain mendapatkan konteksnya secara statis. Spring menyediakan kelas-kelas Tag dasar, dan mereka menggunakan BeanFactoryLocators untuk mendapatkan konteks yang mereka butuhkan.
skaffman
6

Jika Anda menggunakan aplikasi web, ada juga cara lain untuk mengakses konteks aplikasi tanpa menggunakan lajang dengan menggunakan servletfilter dan ThreadLocal. Dalam filter Anda dapat mengakses konteks aplikasi menggunakan WebApplicationContextUtils dan menyimpan konteks aplikasi atau kacang yang diperlukan di TheadLocal.

Perhatian: jika Anda lupa untuk membatalkan ThreadLocal Anda akan mendapatkan masalah buruk ketika mencoba untuk tidak menggunakan aplikasi! Dengan demikian, Anda harus mengaturnya dan segera memulai percobaan yang membatalkan pengaturan ThreadLocal di bagian terakhir.

Tentu saja, ini masih menggunakan singleton: the ThreadLocal. Tetapi kacang yang sebenarnya tidak perlu lagi. Bahkan dapat dibatasi permintaan, dan solusi ini juga berfungsi jika Anda memiliki beberapa PERANG dalam Aplikasi dengan perpustakaan di EAR. Namun, Anda mungkin menganggap penggunaan ThreadLocal ini seburuk penggunaan lajang biasa. ;-)

Mungkin Spring sudah memberikan solusi serupa? Saya tidak menemukan satu, tetapi saya tidak tahu pasti.

Hans-Peter Störr
sumber
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);
  }
}

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

Vanessa Schissato
sumber
5

Lihatlah ContextSingletonBeanFactoryLocator . Ini menyediakan accessors statis untuk memahami konteks Spring, dengan asumsi mereka telah terdaftar dengan cara tertentu.

Ini tidak cantik, dan lebih kompleks dari yang Anda inginkan, tetapi berhasil.

skaffman
sumber
4

Ada banyak cara untuk mendapatkan konteks aplikasi dalam aplikasi Spring. Itu diberikan di bawah ini:

  1. Melalui 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;
    }
    }

Di sini setApplicationContext(ApplicationContext applicationContext)metode Anda akan mendapatkan applicationContext

ApplicationContextAware :

Antarmuka yang akan diimplementasikan oleh objek apa pun yang ingin diberitahukan tentang ApplicationContext yang dijalankan. Menerapkan antarmuka ini masuk akal misalnya ketika sebuah objek membutuhkan akses ke serangkaian kacang yang berkolaborasi.

  1. Melalui Autowired :

    @Autowired
    private ApplicationContext applicationContext;

Di sini @Autowiredkata kunci akan memberikan konteks aplikasi. Autowired memiliki beberapa masalah. Ini akan menciptakan masalah selama pengujian unit.

Md. Sajedul Karim
sumber
3

Perhatikan bahwa dengan menyimpan keadaan apa pun dari arus ApplicationContext, atau ApplicationContextdirinya sendiri dalam variabel statis - misalnya menggunakan pola tunggal - Anda akan membuat tes Anda tidak stabil dan tidak dapat diprediksi jika Anda menggunakan uji Spring. Ini karena uji Spring cache dan menggunakan kembali konteks aplikasi dalam JVM yang sama. Sebagai contoh:

  1. Tes A run dan itu dijelaskan dengan @ContextConfiguration({"classpath:foo.xml"}).
  2. Tes B dijalankan dan dijelaskan dengan @ContextConfiguration({"classpath:foo.xml", "classpath:bar.xml})
  3. Tes C dijalankan dan dijelaskan dengan @ContextConfiguration({"classpath:foo.xml"})

Ketika Uji A berjalan, sebuah ApplicationContextdibuat, dan kacang apa pun yang menerapkan ApplicationContextAwareatau autiringiring ApplicationContextmungkin menulis ke variabel statis.

Ketika Uji B menjalankan hal yang sama terjadi, dan variabel statis sekarang menunjuk ke Uji B ApplicationContext

Saat Uji C berjalan, tidak ada kacang yang dibuat sebagai TestContext(dan di sini ApplicationContext) dari Uji A yang digunakan kembali. Sekarang Anda punya variabel statis yang menunjuk ke yang lain ApplicationContextdari yang saat ini memegang kacang untuk pengujian Anda.

gogstad
sumber
1

Tidak yakin seberapa berguna ini, tetapi Anda juga bisa mendapatkan konteks ketika Anda menginisialisasi aplikasi. Ini adalah yang tercepat Anda bisa mendapatkan konteksnya, bahkan sebelum @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);
Chloe
sumber
0

Harap dicatat bahwa; kode di bawah ini akan membuat konteks aplikasi baru alih-alih menggunakan yang sudah dimuat.

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

Juga perhatikan bahwa beans.xmlharus menjadi bagian dari src/main/resourcessarana dalam perang itu adalah bagian dari WEB_INF/classes, di mana aplikasi yang sebenarnya akan dimuat melalui applicationContext.xmldisebutkan di Web.xml.

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

Hal ini sulit untuk menyebutkan applicationContext.xmljalan di ClassPathXmlApplicationContextkonstruktor.ClassPathXmlApplicationContext("META-INF/spring/applicationContext.xml")tidak dapat menemukan file.

Jadi lebih baik menggunakan applicationContext yang sudah ada dengan menggunakan anotasi.

@Component
public class OperatorRequestHandlerFactory {

    public static ApplicationContext context;

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }
}
Kanagavelu Sugumar
sumber
0

Saya tahu pertanyaan ini dijawab, tetapi saya ingin membagikan kode Kotlin yang saya lakukan untuk mengambil Konteks Musim Semi.

Saya bukan spesialis, jadi saya terbuka untuk kritik, ulasan, dan saran:

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

Sekarang, konteks pegas tersedia untuk umum, dapat memanggil metode yang sama terlepas dari konteks (tes junit, kacang-kacangan, kelas yang dipakai secara manual) seperti pada Java Servlet ini:

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


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


    public MyWebServlet() {

    }
}
John John Pichler
sumber
0

Lakukan autowire dalam Spring bean seperti di bawah ini: @Autowired ApplicationContext private appContext;

Anda akan objek aplikasi konteks.

Sandeep Jain
sumber
0

Pendekatan 1: Anda dapat menyuntikkan ApplicationContext dengan mengimplementasikan antarmuka ApplicationContextAware. Tautan referensi .

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

Pendekatan 2: Konteks Aplikasi Autowire di sembarang biji yang dikelola pegas

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

Tautan referensi .

Hari Krishna
sumber