Les dessous du framework spring

29
Les dessous du framework Spring 3 décembre 2015

Transcript of Les dessous du framework spring

Page 1: Les dessous du framework spring

Les dessous du framework Spring

3 décembre 2015

Page 2: Les dessous du framework spring

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

Page 3: Les dessous du framework spring

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

Page 4: Les dessous du framework spring

Caractéristiqueso Appelé pendant le processus de création de tout bean Springo Implémente l’interface BeanPostProcessor

o 2 méthodes appelées avant et après la méthode d’initialisation du beano Peuvent être ordonnés

Etapes de mise à disposition d’un bean1. Création du bean par constructeur ou appel de méthode statique2. Valorisation des propriétés du bean3. Résolution des références vers d’autres beans (wiring)4. Appel de la méthode postProcessBeforeInitialization() de chaque post-processor5. Appel des méthodes d’initialisation du bean (@PostConstruct, afterPropertiesSet)6. Appel de la méthode postProcessAfterInitialization() de chaque post-processor7. Le bean est prêt à être utilisé par le conteneur

Les post-processeurs de bean (1/5)

Page 5: Les dessous du framework spring

Les post-processeurs de bean (2/5)

Chargement en mémoire de

la définition des beans

XML

Conf Spring Java

Classes annotée

s

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 *

postProcessBeforeInitializati

on

Pour chaque BeanPostProcessor

*

InitalisationBean prêtà l’emploi

postProcessAfterInitialization

afterPropertiesSet@PostConstruct

*

Possibilités multiples :1. modifier un bean2. renvoyer un proxy3. ajouer un intercepteur

Page 6: Les dessous du framework spring

Usage des post-processeurs par Spring et des frameworks tiers

Les post-processeurs de bean (3/5)

Post-Processeur DescriptionAutowiredAnnotationBeanPostProcessor Active l’auto-wiring via les annotations @Autowired

ou @InjectCommonAnnotationBeanPostProcessor Active la détection des annotations de la JSR-250AsyncAnnotationBeanPostProcessor Active la détection de l’annotation @AsyncScheduledAnnotationBeanPostProcessor Active la détection de l’annotation @ScheduledJmsListenerAnnotationBeanPostProcessor

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

Page 7: Les dessous du framework spring

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

Page 8: Les dessous du framework spring

Mise en œuvre d’un AliasPostProcessor

Les post-processeurs de bean (5/5)@Target(TYPE)@Retention(RUNTIME)@Documentedpublic @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; }

Page 9: Les dessous du framework spring

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)

Page 10: Les dessous du framework spring

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>

Page 11: Les dessous du framework spring

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

Page 12: Les dessous du framework spring

Points de vigilance avec @Transactional (1/2) L’annotation @Transactional permet de délimiter des transactions

Point 1 : Commit ou Rollback ?

@Transactionalpublic void addMovie(Movie movie) throws BusinessException { movieDao.save(movie); throw new BusinessException();}

@Transactionalpublic 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

Utilisateur de Microsoft Office
Page 13: Les dessous du framework spring

Points de vigilance avec @Transactional (2/2) Point 2: le film est-il sauvegardé dans une transaction ?

@Servicepublic 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

Utilisateur de Microsoft Office
Page 14: Les dessous du framework spring

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)

Utilisateur de Microsoft Office
Page 15: Les dessous du framework spring

Intercepteur transactionnel (2/2)MovieControlle

rJdkDynamicAopProx

yTransactionIntercept

or MovieService

addMovieinvoke

addMovie

Ouverture d’une transaction car

addMovie annoté

addThenIndexMovieaddThenIndexMovie addMovie

Pas de transaction car on ne repasse pas par le

proxy

Utilisateur de Microsoft Office
Page 16: Les dessous du framework spring

Levée d’ambiguité avec @Primary (1/2) 2 implémentations d’une même interface@Servicepublic class NetflixService implements IMovieService { }@Servicepublic class ImdbService implements IMovieService { }

Spring est incapable de déterminer quel bean injecter

@Autowiredprivate IMovieService movieService;

NoUniqueBeanDefinitionException : expected single matching bean but found 2: imdbService,netflixService

Page 17: Les dessous du framework spring

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@Primarypublic 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

Page 18: Les dessous du framework spring

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)

Page 19: Les dessous du framework spring

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)

Servicesingleton

Conteneur Spring

ProxyInformationsUtilisateur

Contrôleursingleton

Jamessession

Johnsession

getName() getName()

Page 20: Les dessous du framework spring

Exemples de mise en oeuvre

Injection de beans de portée différente (3/3)

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

Page 21: Les dessous du framework spring

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

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

Page 22: Les dessous du framework spring

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 WebApplicationConte

xtDAO

Services métiers

ChildWebApplicationConte

xtContrôleurs IHM

ChildWebApplicationConte

xtContrôleurs REST

Page 23: Les dessous du framework spring

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>

Page 24: Les dessous du framework spring

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>

Page 25: Les dessous du framework spring

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

Page 26: Les dessous du framework spring

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@Componentpublic @interface Mapper { String value() default "";}

@Pointcut("within(@com.mycompagny.spring.Mapper *)")public void mapper() { /**/ }

Exemple d’utilisation en AOP dans les points de coupe :

Page 27: Les dessous du framework spring

Spring permet d’injecter une collection de beans du même type

Architecture pluggable

@Servicepublic 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

Page 28: Les dessous du framework spring

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

@Componentpublic class MonBean {

@Autowired private ApplicationContext applicationContext;

Depuis un bean Spring public class MonBean implements ApplicationContextAware {

@Override public void setApplicationContext( ApplicationContext applicationContext) { this.applicationContext = applicationContext; }

Page 29: Les dessous du framework spring

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