Ottenere il contesto dell'applicazione Spring
-
02-07-2019 - |
Domanda
Esiste un modo per richiedere staticamente / globalmente una copia di ApplicationContext in un'applicazione Spring?
Supponendo che la classe principale si avvii e inizializzi il contesto dell'applicazione, deve passarlo attraverso lo stack di chiamate a tutte le classi che ne hanno bisogno o c'è un modo per una classe di chiedere il contesto precedentemente creato? (Che presumo debba essere un singleton?)
Soluzione
Se l'oggetto che deve accedere al contenitore è un bean nel contenitore, implementare BeanFactoryAware o ApplicationContextAware interfacce.
Se un oggetto esterno al contenitore necessita dell'accesso al contenitore, ho usato un singleton GoF standard modello per il contenitore a molla. In questo modo, hai solo un singleton nella tua applicazione, gli altri sono tutti chicchi di singleton nel contenitore.
Altri suggerimenti
Puoi implementare ApplicationContextAware
o semplicemente usare @Autowired
:
public class SpringBean {
@Autowired
private ApplicationContext appContext;
}
A SpringBean
verrà iniettato ApplicationContext
, all'interno del quale viene istanziato questo bean. Ad esempio, se si dispone di un'applicazione Web con una gerarchia di contesti piuttosto standard:
main application context <- (child) MVC context
e SpringBean
sono dichiarati nel contesto principale, verrà iniettato il contesto principale;
in caso contrario, se viene dichiarato nel contesto MVC, verrà inserito il contesto MVC.
Ecco un bel modo (non mio, il riferimento originale è qui: http://sujitpal.blogspot.com/2007 /03/accessing-spring-beans-from-legacy-code.html
Ho usato questo approccio e funziona benissimo. Fondamentalmente è un semplice bean che contiene un riferimento (statico) al contesto dell'applicazione. Facendo riferimento nella configurazione di primavera è inizializzato.
Dai un'occhiata al riferimento originale, è molto chiaro.
Credo che potresti usare SingletonBeanFactoryLocator . Il file beanRefFactory.xml conterrebbe il vero applicationContext, sarebbe simile a questo:
<bean id="mainContext" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<list>
<value>../applicationContext.xml</value>
</list>
</constructor-arg>
</bean>
E il codice per ottenere un bean dal applicationcontext da qualunque posizione sarebbe:
BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
BeanFactoryReference bf = bfl.useBeanFactory("mainContext");
SomeService someService = (SomeService) bf.getFactory().getBean("someService");
Il team di Spring scoraggia l'uso di questa classe e yadayada, ma mi si adatta bene dove l'ho usato.
Prima di implementare uno qualsiasi degli altri suggerimenti, poniti queste domande ...
- Perché sto cercando di ottenere ApplicationContext?
- Uso effettivamente ApplicationContext come localizzatore di servizi?
- Posso evitare di accedere a ApplicationContext?
Le risposte a queste domande sono più facili in alcuni tipi di applicazioni (ad esempio le app Web) rispetto ad altre, ma vale comunque la pena chiederle.
L'accesso a ApplicationContext in qualche modo viola l'intero principio dell'iniezione di dipendenza, ma a volte non hai molta scelta.
Se usi un'app web c'è anche un altro modo per accedere al contesto dell'applicazione senza usare i singleton usando un servletfilter e un ThreadLocal. Nel filtro è possibile accedere al contesto dell'applicazione utilizzando WebApplicationContextUtils e archiviare il contesto dell'applicazione oi bean necessari in TheadLocal.
Attenzione: se si dimentica di annullare l'impostazione di ThreadLocal, si verificheranno problemi spiacevoli quando si tenta di annullare la distribuzione dell'applicazione! Quindi, dovresti impostarlo e iniziare immediatamente un tentativo che disattiva ThreadLocal nella parte finale.
Ovviamente, utilizza ancora un singleton: ThreadLocal. Ma i fagioli reali non devono più essere. Può anche essere nell'ambito della richiesta, e questa soluzione funziona anche se hai più WAR in un'applicazione con le librerie nell'AER. Tuttavia, potresti considerare questo uso di ThreadLocal cattivo come l'uso di singoli singleton. ; -)
Forse Spring fornisce già una soluzione simile? Non ne ho trovato uno, ma non lo so per certo.
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);
}
}
Fonte: http: // sujitpal. blogspot.de/2007/03/accessing-spring-beans-from-legacy-code.html
Dai un'occhiata a ContextSingletonBeanFactoryLocator . Fornisce accessori statici per acquisire i contesti di Spring, supponendo che siano stati registrati in alcuni modi.
Non è carino e più complesso di quanto forse ti piacerebbe, ma funziona.
Nota che memorizzando qualsiasi stato dall'attuale ApplicationContext
o dallo stesso ApplicationContext
in una variabile statica - ad esempio utilizzando il modello singleton - renderai instabili i tuoi test e imprevedibile se si utilizza Spring-test. Questo perché Spring-test memorizza nella cache e riutilizza i contesti applicativi nella stessa JVM. Ad esempio:
- Test Una corsa ed è annotato con
@ContextConfiguration ({" classpath: foo.xml "})
. - Esegui il test B ed è annotato con
@ContextConfiguration ({" classpath: foo.xml " ;, " classpath: bar.xml})
- Esegui test C ed è annotato con
@ContextConfiguration({"classpath:foo.xml"})
Quando viene eseguito il test A, viene creato un ApplicationContext
e qualsiasi bean che implementa ApplicationContextAware
o che autorizza ApplicationContext
potrebbe scrivere nella variabile statica.
Quando il test B viene eseguito, accade la stessa cosa e la variabile statica ora punta al ApplicationContext
Quando viene eseguito Test C, non vengono creati bean come TestContext
(e qui ApplicationContext
) dal Test A viene ripristinato. Ora hai una variabile statica che punta a un altro ApplicationContext
rispetto a quello che attualmente contiene i bean per il test.
Esistono molti modi per ottenere il contesto dell'applicazione nell'applicazione Spring. Quelli sono indicati di seguito:
Tramite 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; } }
Qui setApplicationContext (ApplicationContext applicationContext)
otterrai il applicationContext
ApplicationContextAware :
Interfaccia che deve essere implementata da qualsiasi oggetto che desideri essere avvisato di ApplicationContext in cui viene eseguito. Implementazione di questa interfaccia ha senso, ad esempio, quando un oggetto richiede l'accesso a un set di fagioli collaboranti.
Via Autowired :
@Autowired private ApplicationContext applicationContext;
Qui la parola chiave @Autowired
fornirà applicationContext. Autowired ha qualche problema. Creerà problemi durante i test unitari.
Non sei sicuro di quanto utile sarà, ma puoi anche ottenere il contesto quando inizializzi l'app. Questo è il più presto possibile ottenere il contesto, anche prima di un @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);
Si noti che; il codice seguente creerà un nuovo contesto dell'applicazione anziché utilizzare quello già caricato.
private static final ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
Nota anche che beans.xml
dovrebbe far parte di src / main / resources
significa che in guerra fa parte di WEB_INF / classes
, dove come l'applicazione reale verrà caricata tramite applicationContext.xml
menzionato in Web.xml
.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>META-INF/spring/applicationContext.xml</param-value>
</context-param>
È difficile menzionare applicationContext.xml
nel costruttore ClassPathXmlApplicationContext
. ClassPathXmlApplicationContext (" META-INF / spring / applicationContext.xml ")
non sarà in grado di individuare il file.
Quindi è meglio usare applicationContext esistente usando le annotazioni.
@Component
public class OperatorRequestHandlerFactory {
public static ApplicationContext context;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
}
Esegui autowire nel bean Spring come di seguito: @Autowired applicationContext privato appContext;
sarai l'oggetto applicationcontext.