Obtenir le contexte d'application du printemps
-
02-07-2019 - |
Question
Existe-t-il un moyen de demander de manière statique / globale une copie du champ ApplicationContext dans une application Spring?
En supposant que la classe principale démarre et initialise le contexte de l'application, doit-elle la transmettre à toutes les classes qui en ont besoin via la pile d'appels, ou existe-t-il un moyen pour une classe de demander le contexte créé précédemment? (Ce qui, je suppose, doit être un singleton?)
La solution
Si l'objet qui a besoin d'accéder au conteneur est un bean dans le conteneur, il suffit d'implémenter le BeanFactoryAware ou interfaces ApplicationContextAware .
Si un objet hors du conteneur nécessite un accès au conteneur, j'ai utilisé un singleton GoF standard. motif pour le conteneur à ressort. De cette façon, vous n’avez qu’un seul singleton dans votre application, les autres sont tous des singleton beans dans le conteneur.
Autres conseils
Vous pouvez implémenter ApplicationContextAware
ou simplement utiliser @Autowired
:
public class SpringBean {
@Autowired
private ApplicationContext appContext;
}
SpringBean
recevra ApplicationContext
, dans lequel ce bean est instancié. Par exemple, si vous avez une application Web avec une hiérarchie assez standard:
main application context <- (child) MVC context
et SpringBean
étant déclaré dans le contexte principal, le contexte principal sera injecté;
sinon, s'il est déclaré dans le contexte MVC, le contexte MVC sera injecté.
Voici un bon moyen (pas le mien, la référence originale est ici: http://sujitpal.blogspot.com/2007 /03/accessing-spring-beans-from-legacy-code.html
J'ai utilisé cette approche et cela fonctionne bien. Fondamentalement, il s’agit d’un simple haricot contenant une référence (statique) au contexte de l’application. En le référençant dans la configuration de printemps, il est initialisé.
Jetez un coup d'œil à la référence originale, c'est très clair.
Je pense que vous pourriez utiliser SingletonBeanFactoryLocator . Le fichier beanRefFactory.xml contiendrait le contexte d'application actuel. Il ressemblerait à ceci:
<bean id="mainContext" class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<list>
<value>../applicationContext.xml</value>
</list>
</constructor-arg>
</bean>
Et le code pour obtenir un haricot du contexte de l'application d'où qu'il vienne, ressemble à ceci:
BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
BeanFactoryReference bf = bfl.useBeanFactory("mainContext");
SomeService someService = (SomeService) bf.getFactory().getBean("someService");
L’équipe Spring a découragé l’utilisation de cette classe et de yadayada, mais cela m’arrange bien là où je l’ai utilisé.
Avant de mettre en œuvre l'une des suggestions proposées, posez-vous ces questions ...
- Pourquoi j'essaie d'obtenir le champ ApplicationContext?
- Est-ce que j'utilise efficacement ApplicationContext en tant que localisateur de services?
- Puis-je éviter tout accès à ApplicationContext?
Les réponses à ces questions sont plus faciles dans certains types d'applications (applications Web, par exemple) que dans d'autres, mais méritent néanmoins d'être posées.
L’accès à ApplicationContext enfreint en quelque sorte le principe de l’injection de dépendance, mais vous n’avez parfois pas le choix.
Si vous utilisez une application Web, il existe également un autre moyen d'accéder au contexte de l'application sans utiliser de singletons à l'aide d'un servletfilter et d'un ThreadLocal. Dans le filtre, vous pouvez accéder au contexte de l'application à l'aide de WebApplicationContextUtils et stocker le contexte de l'application ou les beans nécessaires dans le TheadLocal.
Attention: si vous oubliez de désélectionner le ThreadLocal, vous rencontrerez de vilains problèmes en essayant d'annuler le déploiement de l'application! Ainsi, vous devriez le définir et commencer immédiatement un essai qui annule le ThreadLocal dans la partie-finally.
Bien sûr, cela utilise toujours un singleton: le ThreadLocal. Mais les haricots réels ne doivent plus l'être. Le peut même être limité à la demande, et cette solution fonctionne également si vous avez plusieurs fichiers WAR dans une application avec les bibliothèques dans le fichier EAR. Néanmoins, vous pouvez considérer cette utilisation de ThreadLocal aussi néfaste que celle de singletons simples. ; -)
Peut-être que Spring fournit déjà une solution similaire? Je n'en ai pas trouvé, mais je ne suis pas sûr.
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);
}
}
Source: http: // sujitpal. blogspot.de/2007/03/accessing-spring-beans-from-legacy-code.html
Consultez ContextSingletonBeanFactoryLocator . Il fournit des accesseurs statiques pour obtenir les contextes de Spring, à condition qu'ils aient été enregistrés de certaines manières.
Ce n'est pas joli et plus complexe que vous ne le souhaitez peut-être, mais ça marche.
Notez qu'en stockant n'importe quel état du ApplicationContext
actuel ou du ApplicationContext
lui-même dans une variable statique (par exemple, à l'aide du modèle singleton), vous rendrez vos tests instables. et imprévisible si vous utilisez Spring-test. En effet, Spring-test met en cache et réutilise les contextes d'application dans la même machine virtuelle. Par exemple:
- Test Exécute et est annotée avec
@ContextConfiguration ({& classpath: foo.xml "}})
. - Le test B s'exécute et est annoté avec
@ContextConfiguration ({"classpath: foo.xml", "& classpath: bar.xml})
- Le test C est exécuté et il est annoté avec
@ContextConfiguration ({& classpath: foo.xml "}})
Lorsque le test A est exécuté, un ApplicationContext
est créé et tout beans implémentant ApplicationContextAware
ou autowiring ApplicationContext
peut écrire dans la variable statique. / p>
Lorsque le test B s'exécute, la même chose se produit et la variable statique pointe maintenant sur ApplicationContext
Lorsque le test C est exécuté, aucun bean n'est créé en tant que TestContext
(et dans ce cas, le ApplicationContext
) à partir du test A est renvoyé. Vous avez maintenant une variable statique pointant vers un autre ApplicationContext
que celui contenant actuellement les beans pour votre test.
Il existe de nombreuses façons d'obtenir le contexte d'application dans l'application Spring. Ceux-ci sont donnés ci-dessous:
-
Via 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; } }
Ici, la méthode setApplicationContext (ApplicationContext applicationContext)
vous permettra d'obtenir le champ applicationContext
ApplicationContextAware :
Interface à implémenter par tout objet souhaitant être notifié de l'ApplicationContext dans lequel il s'exécute. Implémentation de cette interface logique par exemple lorsqu'un objet nécessite l'accès à un ensemble de haricots collaborateurs.
-
Via le câblage automatique :
@Autowired private ApplicationContext applicationContext;
Ici, le mot clé @Autowired
fournira le champ applicationContext. Le câblage automatique a un problème. Cela créera un problème lors des tests unitaires.
Vous ne savez pas à quel point cela sera utile, mais vous pouvez également obtenir le contexte lors de l'initialisation de l'application. C’est le plus tôt possible pour obtenir le contexte, même avant 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);
Veuillez noter que; le code ci-dessous créera un nouveau contexte d'application au lieu d'utiliser celui déjà chargé.
private static final ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
Notez également que beans.xml
doit faire partie de src / main / resources
signifie en guerre qu'il fait partie de WEB_INF / classes
, où la véritable application sera chargée via applicationContext.xml
mentionné sous Web.xml
.
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>META-INF/spring/applicationContext.xml</param-value>
</context-param>
Il est difficile de mentionner applicationContext.xml
chemin dans le constructeur ClassPathXmlApplicationContext
. ClassPathXmlApplicationContext ("META-INF / spring / applicationContext.xml")
ne pourra pas localiser le fichier.
Il est donc préférable d'utiliser le contexte d'application existant en utilisant des annotations.
@Component
public class OperatorRequestHandlerFactory {
public static ApplicationContext context;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
}
Faites un saut automatique dans Spring bean comme ci-dessous: @Autowired private ApplicationContext appContext;
vous utiliserez l'objet applicationcontext.