Les dessous du
framework Spring
3 décembre 2015
Objectifs
 Exploiter certaines fonctionnalités méconnues de Spring
 Découvrir le fonctionnement interne du conteneur
 Eviter certains pièges
 Apprendre à partir d’exemples concrets issus d’applications métiers
Sommaire
 Post-processeurs et fabriques de beans Spring
 Intercepteur transactionnel et pièges de l’annotation @Transactional
 Lever des ambiguïtés lors de l’injection de beans
 Injection de beans de portées différentes
 Hiérarchie de contextes Spring
 Créer sa propre annotation
 Architecture pluggable
 Accès au contexte Spring
 Caractéristiques
o Appelé pendant le processus de création de tout bean Spring
o Implémente l’interface BeanPostProcessor
o 2 méthodes appelées avant et après la méthode d’initialisation du bean
o Peuvent être ordonnés
 Etapes de mise à disposition d’un bean
1. Création du bean par constructeur ou appel de méthode statique
2. Valorisation des propriétés du bean
3. Résolution des références vers d’autres beans (wiring)
4. Appel de la méthode postProcessBeforeInitialization() de chaque post-processor
5. Appel des méthodes d’initialisation du bean (@PostConstruct, afterPropertiesSet)
6. Appel de la méthode postProcessAfterInitialization() de chaque post-processor
7. Le bean est prêt à être utilisé par le conteneur
Les post-processeurs de bean (1/5)
Les post-processeurs de bean (2/5)
Chargement en
mémoire de la
définition des
beans
XML
Conf
Spring
Java
Classes
annotées
Instanciation du
bean ou appel de
la fabrique beans
 Etapes de mise à disposition des beans de portées singleton d’une application
Pour chaque
bean trouvé Appel des setters
Injection de beans
*
postProcess
BeforeInitialization
Pour chaque
BeanPost
Processor
*
Initalisation
Bean prêt
à l’emploi postProcess
AfterInitialization
afterPropertiesSet
@PostConstruct
*
Possibilités multiples :
1. modifier un bean
2. renvoyer un proxy
3. ajouer un intercepteur
 Usage des post-processeurs par Spring et des frameworks tiers
Les post-processeurs de bean (3/5)
Post-Processeur Description
AutowiredAnnotationBeanPostProcessor Active l’auto-wiring via les annotations @Autowired ou
@Inject
CommonAnnotationBeanPostProcessor Active la détection des annotations de la JSR-250
AsyncAnnotationBeanPostProcessor Active la détection de l’annotation @Async
ScheduledAnnotationBeanPostProcessor Active la détection de l’annotation @Scheduled
JmsListenerAnnotationBeanPostProcessor Active la détection de l’annotation @JmsListener
ApplicationContextAwareProcessor Permet de passer le contexte applicatif Spring aux beans
implémentant l’interface ApplicationContextAware
ScriptFactoryPostProcessor Permet d’accéder au résultat de scripts (Groovy, JRuby )
BusExtensionPostProcessor Active la détection automatique d’extension de bus CXF
JaxWsWebServicePublisherBeanPostProcessor Support CXF des annotations JAX-WS
CamelBeanPostProcessor Intégration de beans Spring dans Apache Camel
 Exemple d’utilisation
 Disposer d’une annotation @Alias se substituant à la syntaxe XML
 Demande d’évolution SPR-6736
 Définition d’un alias en XML :
 Equivalent en annotation :
Les post-processeurs de bean (4/5)
<bean id="movieController" class="MovieController"/>
<alias name="movieController" alias="filmController"/>
@Controller("movieController")
@Alias("filmController")
public class MovieController { }
 Mise en œuvre d’un AliasPostProcessor
Les post-processeurs de bean (5/5)
@Target(TYPE)
@Retention(RUNTIME)
@Documented
public @interface Alias {
String value();
}
public class AliasPostProcessor
implements BeanPostProcessor, BeanFactoryPostProcessor {
private ConfigurableListableBeanFactory configurableBeanFactory;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
Class<?> targetClass = AopUtils.getTargetClass(bean);
Annotation annotation = targetClass.getAnnotation(Alias.class);
if (annotation != null) {
configurableBeanFactory.registerAlias(beanName, ((Alias) annotation).value());
}
return bean;
}
 Lors du mécanisme d’injection, met un bean à disposition du conteneur
 Instancie ou réutilisation des beans (partage)
 Massivement utilisé par le conteneur Spring
 Exemple : HibernateSessionFactoryBean
 Implémente l’interface FactoryBean
 Ce n’est pas la fabrique qui est injectée mais le bean créé par la
fabrique (méthode getObject())
Fabrique de beans Spring (1/3)
 Exemple d’une fabrique fusionnant des listes de beans
 Input : plusieurs listes de beans Spring
 Output : une seule liste de beans Spring
 Utilisation avec la syntaxe XML :
Fabrique de beans Spring (2/3)
<bean id="annotatedClasses" class="ListMerger">
<property name="sourceLists">
<util:list>
<ref bean="customerAnnotatedClasses" />
<ref bean="aggreementAnnotatedClasses" />
</util:list>
</property>
</bean>
 Implémentation de la fabrique fusionnant des listes de beans
Fabrique de beans Spring (3/3)
public class ListMerger<V> implements FactoryBean<List<V>> {
private List<V> result = new ArrayList<V>();
@Override
public List<V> getObject() { return result; }
@Override
public boolean isSingleton() { return true; }
@Override
public Class<?> getObjectType() { return List.class; }
public void setSourceLists(List<List<V>> sourceLists) {
for (List<V> l : sourceLists) { this.result.addAll(l); }
}
}
Points de vigilance avec @Transactional (1/2)
 L’annotation @Transactional permet de délimiter des transactions
 Point 1 : Commit ou Rollback ?
@Transactional
public void addMovie(Movie movie) throws BusinessException {
movieDao.save(movie);
throw new BusinessException();
}
@Transactional
public void addMovie(Movie movie) throws BusinessException {
movieDao.save(movie);
throw new TechnicalException();
}
Par défaut, lorsqu’une checked exception est levée, Spring valide la transaction
Remédiation : utilisation du rollbackFor @Transactional(rollbackFor = Exception.class)
ou création d’une annotation dédiée @MyTransactional
Commit
Rollback
Points de vigilance avec @Transactional (2/2)
 Point 2: le film est-il sauvegardé dans une transaction ?
@Service
public class MovieService {
@Autowired IMovieDao movieDao;
public void addThenIndexMovie(Movie movie) {
addMovie(movie);
indexMovie(movie);
}
@Transactional
public void addMovie(Movie movie) { … }
private void indexMovie(Movie movie) { … }
}
@RequestMapping(value = "/movie/new")
public String create(@Valid Movie movie) {
movieService.addThenIndexMovie(movie);
return "redirect:/movie/" + movie.getId();
}
@RequestMapping(value = "/movie/new")
public String create(@Valid Movie movie) {
movieService.addMovie(movie);
return "redirect:/movie/" + movie.getId();
}
 Scénario 1 : appel direct à addMovie
 Scénario 2 : appel indirect à addMovie
Transactionnel
Non transactionnel
 Lors de l’injection de beans, Spring peut modifier la chaîne d’appel
 Design Pattern Proxy ou Intercepteur
 Exemples d’usage :
 Greffer des aspects
 Insérer un comportement transactionnel
 2 types de proxy en fonction de l’appelé :
 Interface par l’utilisation de proxy dynamique java.lang.reflect.Proxy
 Classe par instrumentation de code
Intercepteur transactionnel (1/2)
Intercepteur transactionnel (2/2)
MovieController JdkDynamicAopProxy TransactionInterceptor MovieService
addMovie
invoke
addMovie
Ouverture d’une
transaction car
addMovie annoté
addThenIndexMovie
addThenIndexMovie
addMovie
Pas de transaction car on ne
repasse pas par le proxy
Levée d’ambiguité avec @Primary (1/2)
 2 implémentations d’une même interface
@Service
public class NetflixService implements IMovieService { }
@Service
public class ImdbService implements IMovieService { }
 Spring est incapable de déterminer quel bean injecter
@Autowired
private IMovieService movieService;
NoUniqueBeanDefinitionException : expected single matching bean but found 2:
imdbService,netflixService
Levée d’ambiguité avec @Primary (2/2)
 Solution 1 : utilisation d’un qualifier
@Autowired
@Qualifier("netflixService")
private IMovieService movieService;
 Solution 2 : définition d’un bean principal (bean par défaut)
@Service
@Primary
public class NetflixService implements IMovieService { }
 Permet de ne pas alourdir l’injection de dépendance lorsque dans la plupart des cas c’est le bean
netflixService qui doit être injecté.
 Autres exemples : DataSourceTransactionManager vs JmsTransactionManager
NetflixService vs MockMovieService
 Comment injecter un bean de portée session dans un singleton ?
 Rappels
 Un bean de portée Singleton
 doit d’être thread-safe
 est créé au démarrage du conteneur
 Un bean de portée session est créé pour chaque session web utilisateur
 Cas d’usage
 Dans les contrôleurs, ne plus manipuler directement la session HTTP
 Toutes les données à mettre en session sont modélisées dans un ou plusieurs beans
 Avoir accès dans un service métier aux informations de l’utilisateur connecté
 Besoin : contrôle des habilitations, historisation, logs
 Un bean de portée requête peut remplacer l’utilisation du ThreadLocal
Injection de beans de portée différente (1/3)
 Solution
 Proxifier le bean de portée Session
 Le proxy est chargé d’aiguiller les appels vers le bean session approprié
 Illustration
Injection de beans de portée différente (2/3)
Service
singleton
Conteneur Spring
Proxy
Informations
Utilisateur
Contrôleur
singleton
James
session
John
session
getName() getName()
 Exemples de mise en oeuvre
Injection de beans de portée différente (3/3)
@Configuration
public class UserConfig {
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserDetails userDetails() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (UserDetails) authentication.getPrincipal();
}
}
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MovieModelView {
private Movie selectedMovie;
private String email;
…
}
 Les scopes request et session n’ont de sens que dans un conteneur web
 Erreur lorsqu’ils sont utilisés dans un simple ApplicationContext
 java.lang.IllegalStateException: No Scope registered for scope 'session’
Tests de beans de portée web
@WebAppConfiguration
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class TestWebScope {
 Solution 1 : l’annotation
@WebAppConfiguration
 Solution 2 : déclarer un bean
CustomScopeConfigurer et utiliser le scope
SimpleThreadScope
@Configuration
static class Config {
@Bean
public CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer csc = new CustomScopeConfigurer();
Map<String, Object> map = new HashMap<String, Object>();
map.put("session", new SimpleThreadScope());
map.put("request", new SimpleThreadScope());
csc.setScopes(map);
return csc;
} }
 Une application Spring est composée d’un ou plusieurs contextes applicatifs
 Les contextes applicatifs Spring peuvent être hiérarchiques
 C’est typiquement le cas dans les applications web basées sur Spring MVC
Hiérarchie de contextes (1/4)
Root
WebApplicationContext
DAO
Services métiers
Child
WebApplicationContext
Contrôleurs IHM
Child
WebApplicationContext
Contrôleurs REST
 Les beans déclarés dans le contexte parent sont visibles des contextes
enfants
 Respect du découpage en couche
 Alternative aux multi-wars
 Chargement du contexte parent depuis un listener JEE déclaré dans le
web.xml :
Hiérarchie de contextes (2/4)
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/business-config.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
 Chargement des contextes enfants via les DispatcherServlet déclarés
dans le web.xml
Hiérarchie de contextes (3/4)
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web
.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>org.springframework.web
.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:rest-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>rest</servlet-name>
<url-pattern>/rest</url-pattern>
</servlet-mapping>
 L’annotation @ContextHierarchy rend possible l’écriture de tests
d’intégration avec hiérarchie de contextes
Hiérarchie de contextes (4/4)
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(name = "parent", locations = "classpath:business-config.xml"),
@ContextConfiguration(name = "child", locations = "classpath:spring-mvc-config.xml") })
public class SpringConfigTest {
@Autowired
private WebApplicationContext childContext;
@Test
public void parentChildContext) {
ApplicationContext parentContext = childContext.getParent();
assertTrue(parentContext.containsBean("myService"));
assertTrue(childContext.containsBean("myService"));
assertFalse(parentContext.containsBean("myController"));
assertTrue(childContext.containsBean("myController"));
}
}
 Possibilité d’étendre le jeu d’annotations @Component, @Service,
@Repository et @Controller
 Exemple d’annotation utilisée sur les classes de mapping objet-objet :
Créer sa propre annotation @Component
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Mapper {
String value() default "";
}
@Pointcut("within(@com.mycompagny.spring.Mapper *)")
public void mapper() { /**/ }
 Exemple d’utilisation en AOP dans les points de coupe :
 Spring permet d’injecter une collection de beans du même type
Architecture pluggable
@Service
public class PluginManager {
@Autowired
private List<IPlugin> pluginList;
@Autowired
private Map<String, IPlugin> pluginMap;
}
 Mécanisme d’extension très simple
 Exemple : ajout de nouveaux formats de fichiers dans un composant
d’upload de fichiers
 Depuis une Servlet
Accès au contexte Spring
public class MovieServlet extends HttpServlet {
private IMovieService movieService;
@Override
public void init() throws ServletException {
ApplicationContext applicationContext
= WebApplicationContextUtils.getWebApplicationContext(getServletContext());
movieService = applicationContext.getBean(IMovieService.class);
}
@Component
public class MonBean {
@Autowired
private ApplicationContext applicationContext;
 Depuis un bean Spring
public class MonBean implements
ApplicationContextAware {
@Override
public void setApplicationContext(
ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
Conclusion
 Les mécanismes utilisés en interne par Spring permettent
d’étendre les fonctionnalités du framework
 Connaître le fonctionnement des proxy et des
intercepteurs permet d’éviter des bugs
 Dans une application web, la portée des beans doit être
choisie judicieusement

Les dessous du framework spring

  • 1.
    Les dessous du frameworkSpring 3 décembre 2015
  • 2.
    Objectifs  Exploiter certainesfonctionnalités méconnues de Spring  Découvrir le fonctionnement interne du conteneur  Eviter certains pièges  Apprendre à partir d’exemples concrets issus d’applications métiers
  • 3.
    Sommaire  Post-processeurs etfabriques de beans Spring  Intercepteur transactionnel et pièges de l’annotation @Transactional  Lever des ambiguïtés lors de l’injection de beans  Injection de beans de portées différentes  Hiérarchie de contextes Spring  Créer sa propre annotation  Architecture pluggable  Accès au contexte Spring
  • 4.
     Caractéristiques o Appelépendant le processus de création de tout bean Spring o Implémente l’interface BeanPostProcessor o 2 méthodes appelées avant et après la méthode d’initialisation du bean o Peuvent être ordonnés  Etapes de mise à disposition d’un bean 1. Création du bean par constructeur ou appel de méthode statique 2. Valorisation des propriétés du bean 3. Résolution des références vers d’autres beans (wiring) 4. Appel de la méthode postProcessBeforeInitialization() de chaque post-processor 5. Appel des méthodes d’initialisation du bean (@PostConstruct, afterPropertiesSet) 6. Appel de la méthode postProcessAfterInitialization() de chaque post-processor 7. Le bean est prêt à être utilisé par le conteneur Les post-processeurs de bean (1/5)
  • 5.
    Les post-processeurs debean (2/5) Chargement en mémoire de la définition des beans XML Conf Spring Java Classes annotées Instanciation du bean ou appel de la fabrique beans  Etapes de mise à disposition des beans de portées singleton d’une application Pour chaque bean trouvé Appel des setters Injection de beans * postProcess BeforeInitialization Pour chaque BeanPost Processor * Initalisation Bean prêt à l’emploi postProcess AfterInitialization afterPropertiesSet @PostConstruct * Possibilités multiples : 1. modifier un bean 2. renvoyer un proxy 3. ajouer un intercepteur
  • 6.
     Usage despost-processeurs par Spring et des frameworks tiers Les post-processeurs de bean (3/5) Post-Processeur Description AutowiredAnnotationBeanPostProcessor Active l’auto-wiring via les annotations @Autowired ou @Inject CommonAnnotationBeanPostProcessor Active la détection des annotations de la JSR-250 AsyncAnnotationBeanPostProcessor Active la détection de l’annotation @Async ScheduledAnnotationBeanPostProcessor Active la détection de l’annotation @Scheduled JmsListenerAnnotationBeanPostProcessor Active la détection de l’annotation @JmsListener ApplicationContextAwareProcessor Permet de passer le contexte applicatif Spring aux beans implémentant l’interface ApplicationContextAware ScriptFactoryPostProcessor Permet d’accéder au résultat de scripts (Groovy, JRuby ) BusExtensionPostProcessor Active la détection automatique d’extension de bus CXF JaxWsWebServicePublisherBeanPostProcessor Support CXF des annotations JAX-WS CamelBeanPostProcessor Intégration de beans Spring dans Apache Camel
  • 7.
     Exemple d’utilisation Disposer d’une annotation @Alias se substituant à la syntaxe XML  Demande d’évolution SPR-6736  Définition d’un alias en XML :  Equivalent en annotation : Les post-processeurs de bean (4/5) <bean id="movieController" class="MovieController"/> <alias name="movieController" alias="filmController"/> @Controller("movieController") @Alias("filmController") public class MovieController { }
  • 8.
     Mise enœuvre d’un AliasPostProcessor Les post-processeurs de bean (5/5) @Target(TYPE) @Retention(RUNTIME) @Documented public @interface Alias { String value(); } public class AliasPostProcessor implements BeanPostProcessor, BeanFactoryPostProcessor { private ConfigurableListableBeanFactory configurableBeanFactory; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { Class<?> targetClass = AopUtils.getTargetClass(bean); Annotation annotation = targetClass.getAnnotation(Alias.class); if (annotation != null) { configurableBeanFactory.registerAlias(beanName, ((Alias) annotation).value()); } return bean; }
  • 9.
     Lors dumécanisme d’injection, met un bean à disposition du conteneur  Instancie ou réutilisation des beans (partage)  Massivement utilisé par le conteneur Spring  Exemple : HibernateSessionFactoryBean  Implémente l’interface FactoryBean  Ce n’est pas la fabrique qui est injectée mais le bean créé par la fabrique (méthode getObject()) Fabrique de beans Spring (1/3)
  • 10.
     Exemple d’unefabrique fusionnant des listes de beans  Input : plusieurs listes de beans Spring  Output : une seule liste de beans Spring  Utilisation avec la syntaxe XML : Fabrique de beans Spring (2/3) <bean id="annotatedClasses" class="ListMerger"> <property name="sourceLists"> <util:list> <ref bean="customerAnnotatedClasses" /> <ref bean="aggreementAnnotatedClasses" /> </util:list> </property> </bean>
  • 11.
     Implémentation dela fabrique fusionnant des listes de beans Fabrique de beans Spring (3/3) public class ListMerger<V> implements FactoryBean<List<V>> { private List<V> result = new ArrayList<V>(); @Override public List<V> getObject() { return result; } @Override public boolean isSingleton() { return true; } @Override public Class<?> getObjectType() { return List.class; } public void setSourceLists(List<List<V>> sourceLists) { for (List<V> l : sourceLists) { this.result.addAll(l); } } }
  • 12.
    Points de vigilanceavec @Transactional (1/2)  L’annotation @Transactional permet de délimiter des transactions  Point 1 : Commit ou Rollback ? @Transactional public void addMovie(Movie movie) throws BusinessException { movieDao.save(movie); throw new BusinessException(); } @Transactional public void addMovie(Movie movie) throws BusinessException { movieDao.save(movie); throw new TechnicalException(); } Par défaut, lorsqu’une checked exception est levée, Spring valide la transaction Remédiation : utilisation du rollbackFor @Transactional(rollbackFor = Exception.class) ou création d’une annotation dédiée @MyTransactional Commit Rollback
  • 13.
    Points de vigilanceavec @Transactional (2/2)  Point 2: le film est-il sauvegardé dans une transaction ? @Service public class MovieService { @Autowired IMovieDao movieDao; public void addThenIndexMovie(Movie movie) { addMovie(movie); indexMovie(movie); } @Transactional public void addMovie(Movie movie) { … } private void indexMovie(Movie movie) { … } } @RequestMapping(value = "/movie/new") public String create(@Valid Movie movie) { movieService.addThenIndexMovie(movie); return "redirect:/movie/" + movie.getId(); } @RequestMapping(value = "/movie/new") public String create(@Valid Movie movie) { movieService.addMovie(movie); return "redirect:/movie/" + movie.getId(); }  Scénario 1 : appel direct à addMovie  Scénario 2 : appel indirect à addMovie Transactionnel Non transactionnel
  • 14.
     Lors del’injection de beans, Spring peut modifier la chaîne d’appel  Design Pattern Proxy ou Intercepteur  Exemples d’usage :  Greffer des aspects  Insérer un comportement transactionnel  2 types de proxy en fonction de l’appelé :  Interface par l’utilisation de proxy dynamique java.lang.reflect.Proxy  Classe par instrumentation de code Intercepteur transactionnel (1/2)
  • 15.
    Intercepteur transactionnel (2/2) MovieControllerJdkDynamicAopProxy TransactionInterceptor MovieService addMovie invoke addMovie Ouverture d’une transaction car addMovie annoté addThenIndexMovie addThenIndexMovie addMovie Pas de transaction car on ne repasse pas par le proxy
  • 16.
    Levée d’ambiguité avec@Primary (1/2)  2 implémentations d’une même interface @Service public class NetflixService implements IMovieService { } @Service public class ImdbService implements IMovieService { }  Spring est incapable de déterminer quel bean injecter @Autowired private IMovieService movieService; NoUniqueBeanDefinitionException : expected single matching bean but found 2: imdbService,netflixService
  • 17.
    Levée d’ambiguité avec@Primary (2/2)  Solution 1 : utilisation d’un qualifier @Autowired @Qualifier("netflixService") private IMovieService movieService;  Solution 2 : définition d’un bean principal (bean par défaut) @Service @Primary public class NetflixService implements IMovieService { }  Permet de ne pas alourdir l’injection de dépendance lorsque dans la plupart des cas c’est le bean netflixService qui doit être injecté.  Autres exemples : DataSourceTransactionManager vs JmsTransactionManager NetflixService vs MockMovieService
  • 18.
     Comment injecterun bean de portée session dans un singleton ?  Rappels  Un bean de portée Singleton  doit d’être thread-safe  est créé au démarrage du conteneur  Un bean de portée session est créé pour chaque session web utilisateur  Cas d’usage  Dans les contrôleurs, ne plus manipuler directement la session HTTP  Toutes les données à mettre en session sont modélisées dans un ou plusieurs beans  Avoir accès dans un service métier aux informations de l’utilisateur connecté  Besoin : contrôle des habilitations, historisation, logs  Un bean de portée requête peut remplacer l’utilisation du ThreadLocal Injection de beans de portée différente (1/3)
  • 19.
     Solution  Proxifierle bean de portée Session  Le proxy est chargé d’aiguiller les appels vers le bean session approprié  Illustration Injection de beans de portée différente (2/3) Service singleton Conteneur Spring Proxy Informations Utilisateur Contrôleur singleton James session John session getName() getName()
  • 20.
     Exemples demise en oeuvre Injection de beans de portée différente (3/3) @Configuration public class UserConfig { @Bean @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public UserDetails userDetails() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return (UserDetails) authentication.getPrincipal(); } } @Component @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public class MovieModelView { private Movie selectedMovie; private String email; … }
  • 21.
     Les scopesrequest et session n’ont de sens que dans un conteneur web  Erreur lorsqu’ils sont utilisés dans un simple ApplicationContext  java.lang.IllegalStateException: No Scope registered for scope 'session’ Tests de beans de portée web @WebAppConfiguration @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) public class TestWebScope {  Solution 1 : l’annotation @WebAppConfiguration  Solution 2 : déclarer un bean CustomScopeConfigurer et utiliser le scope SimpleThreadScope @Configuration static class Config { @Bean public CustomScopeConfigurer customScopeConfigurer() { CustomScopeConfigurer csc = new CustomScopeConfigurer(); Map<String, Object> map = new HashMap<String, Object>(); map.put("session", new SimpleThreadScope()); map.put("request", new SimpleThreadScope()); csc.setScopes(map); return csc; } }
  • 22.
     Une applicationSpring est composée d’un ou plusieurs contextes applicatifs  Les contextes applicatifs Spring peuvent être hiérarchiques  C’est typiquement le cas dans les applications web basées sur Spring MVC Hiérarchie de contextes (1/4) Root WebApplicationContext DAO Services métiers Child WebApplicationContext Contrôleurs IHM Child WebApplicationContext Contrôleurs REST
  • 23.
     Les beansdéclarés dans le contexte parent sont visibles des contextes enfants  Respect du découpage en couche  Alternative aux multi-wars  Chargement du contexte parent depuis un listener JEE déclaré dans le web.xml : Hiérarchie de contextes (2/4) <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/business-config.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
  • 24.
     Chargement descontextes enfants via les DispatcherServlet déclarés dans le web.xml Hiérarchie de contextes (3/4) <servlet> <servlet-name>mvc</servlet-name> <servlet-class>org.springframework.web .servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:mvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <servlet> <servlet-name>rest</servlet-name> <servlet-class>org.springframework.web .servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:rest-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>rest</servlet-name> <url-pattern>/rest</url-pattern> </servlet-mapping>
  • 25.
     L’annotation @ContextHierarchyrend possible l’écriture de tests d’intégration avec hiérarchie de contextes Hiérarchie de contextes (4/4) @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextHierarchy({ @ContextConfiguration(name = "parent", locations = "classpath:business-config.xml"), @ContextConfiguration(name = "child", locations = "classpath:spring-mvc-config.xml") }) public class SpringConfigTest { @Autowired private WebApplicationContext childContext; @Test public void parentChildContext) { ApplicationContext parentContext = childContext.getParent(); assertTrue(parentContext.containsBean("myService")); assertTrue(childContext.containsBean("myService")); assertFalse(parentContext.containsBean("myController")); assertTrue(childContext.containsBean("myController")); } }
  • 26.
     Possibilité d’étendrele jeu d’annotations @Component, @Service, @Repository et @Controller  Exemple d’annotation utilisée sur les classes de mapping objet-objet : Créer sa propre annotation @Component @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Mapper { String value() default ""; } @Pointcut("within(@com.mycompagny.spring.Mapper *)") public void mapper() { /**/ }  Exemple d’utilisation en AOP dans les points de coupe :
  • 27.
     Spring permetd’injecter une collection de beans du même type Architecture pluggable @Service public class PluginManager { @Autowired private List<IPlugin> pluginList; @Autowired private Map<String, IPlugin> pluginMap; }  Mécanisme d’extension très simple  Exemple : ajout de nouveaux formats de fichiers dans un composant d’upload de fichiers
  • 28.
     Depuis uneServlet Accès au contexte Spring public class MovieServlet extends HttpServlet { private IMovieService movieService; @Override public void init() throws ServletException { ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); movieService = applicationContext.getBean(IMovieService.class); } @Component public class MonBean { @Autowired private ApplicationContext applicationContext;  Depuis un bean Spring public class MonBean implements ApplicationContextAware { @Override public void setApplicationContext( ApplicationContext applicationContext) { this.applicationContext = applicationContext; }
  • 29.
    Conclusion  Les mécanismesutilisés en interne par Spring permettent d’étendre les fonctionnalités du framework  Connaître le fonctionnement des proxy et des intercepteurs permet d’éviter des bugs  Dans une application web, la portée des beans doit être choisie judicieusement

Notes de l'éditeur

  • #3 Démystifier le côté magique de certains comportements (ex: @Async)
  • #5 Les directives XML telles que <context;annotation-config> activent une ou plusieurs BPP Ordre déterminé par l’interfacr Ordered Post-processeurs couramment utilisés dans Spring : CommonAnnotationBeanPostProcessor : active la détection des annotations de la JSR-250 utilisées notamment pour les méthodes d’initialisation, de destruction et l’autowiring de ressources JEE. RequiredAnnotationBeanPostProcessor : active la détection de l’annotation @Required qui permet de préciser les propriétés obligatoires dans un Bean. AsyncAnnotationBeanPostProcessor : active la détection de l’annotation @Async qui permet d’exécuter des méthodes en asynchrone
  • #6 Les directives XML telles que <context;annotation-config> activent une ou plusieurs BPP Ordre déterminé par l’interfacr Ordered Post-processeurs couramment utilisés dans Spring : CommonAnnotationBeanPostProcessor : active la détection des annotations de la JSR-250 utilisées notamment pour les méthodes d’initialisation, de destruction et l’autowiring de ressources JEE. RequiredAnnotationBeanPostProcessor : active la détection de l’annotation @Required qui permet de préciser les propriétés obligatoires dans un Bean. AsyncAnnotationBeanPostProcessor : active la détection de l’annotation @Async qui permet d’exécuter des méthodes en asynchrone
  • #8 Autre exemple d’utilisation bien plus complexe : mécanisme de notification
  • #9 BeanFactoryPostProcessor permet d’avoir accès au ConfigurableListableBeanFactory
  • #10 Utilisation : création de beans complexes Autres exemples : TransactionProxyFactoryBean, EhCacheFactoryBean, ThreadPoolExecutorFactoryBean, VelocityEngineFactoryBean
  • #27 Combinaison fréquente : annotation + fabrique de beans