Les dessous du framework spring

8 121 vues

Publié le


Chaque jour, de nombreux développeurs utilisent le framework Spring pour l’injection de dépendances et la gestion des transactions. Majeures, ces 2 fonctionnalités ne nécessitent pas un gros effort d’apprentissage. Pour autant, leurs mises en œuvre par le framework est complexe. Par curiosité intellectuelle, mais également afin d’éviter certains pièges et de profiter pleinement des capacités de Spring, il est intéressant de comprendre les mécanismes internes du framework qu’on utilise au quotidien : cycle de vie d’un bean, proxy, intercepteur, post-processeur, fabrique de beans, hiérarchie de contextes, portée …
Les slides de cette présentation ont pour objectif de vous les faire les introduire.

Publié dans : Technologie
0 commentaire
5 j’aime
Statistiques
Remarques
  • Soyez le premier à commenter

Aucun téléchargement
Vues
Nombre de vues
8 121
Sur SlideShare
0
Issues des intégrations
0
Intégrations
5 642
Actions
Partages
0
Téléchargements
65
Commentaires
0
J’aime
5
Intégrations 0
Aucune incorporation

Aucune remarque pour cette diapositive
  • Démystifier le côté magique de certains comportements (ex: @Async)
  • 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
  • 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
  • Autre exemple d’utilisation bien plus complexe : mécanisme de notification
  • BeanFactoryPostProcessor permet d’avoir accès au ConfigurableListableBeanFactory
  • Utilisation : création de beans complexes
    Autres exemples : TransactionProxyFactoryBean, EhCacheFactoryBean, ThreadPoolExecutorFactoryBean, VelocityEngineFactoryBean
  • Combinaison fréquente : annotation + fabrique de beans
  • Les dessous du framework spring

    1. 1. Les dessous du framework Spring 3 décembre 2015
    2. 2. 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
    3. 3. 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
    4. 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. 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
    6. 6.  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
    7. 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. 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. 9.  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)
    10. 10.  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>
    11. 11.  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); } } }
    12. 12. 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
    13. 13. 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
    14. 14.  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)
    15. 15. 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
    16. 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. 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. 18.  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)
    19. 19.  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()
    20. 20.  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; … }
    21. 21.  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; } }
    22. 22.  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
    23. 23.  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>
    24. 24.  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>
    25. 25.  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")); } }
    26. 26.  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 :
    27. 27.  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
    28. 28.  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; }
    29. 29. 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

    ×