Workshop Spring 3 - Tests et techniques avancées du conteneur Spring

21
Workshop Spring - Session 3 Tests & Techniques avancées du conteneur Spring Conçu en décembre 2011 Réactualisé en novembre 2014 # 1

Transcript of Workshop Spring 3 - Tests et techniques avancées du conteneur Spring

Page 1: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

Workshop Spring - Session 3Tests & Techniques avancées

du conteneur Spring

Conçu en décembre 2011Réactualisé en novembre 2014

# 1

Page 2: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

Sommaire

Tests avec pring-test , DBUnit et Mockito 3

Injection de beans de portées différentes 8

Support des JSR 250 et 330 11

Usage des post-processeurs de bean 13

Externalisation de la configuration 17

Accès aux ressources externes 19

2

Page 3: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

Conteneur léger accessible aux tests unitaires et d’intégration

Support de JUnit 4 et TestNGChargement du contexte SpringInjection de dépendancesMise en cache du contexte

Extensions JUnit parRunner : SpringJUnit4ClassRunnerAnnotations : @ContextConfiguration, @Rollback, @Repeat …Listeners : DependencyInjectionTestExecutionListener …

Bouchons prêts à l’emploiMockHttpSession, MockHttpServletRequest, MockPortletSession ...

3

Les apports du module Spring Test

Spring et les Tests

Page 4: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

4

Test d’intégration d’un DAO

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("applicationContext-daos.xml")

@Transactionnal

public class IntTestHibernateAccountDao {

@Autowired

private HibernateAccountDao accountDao;

@Rollback(false)

@Test

public void chargerCompteParIBAN(){

String iban = = "FR70 3000 2005 5000 0015 7845 Z02";

Account account = accountDao.findAccountByIBan(iban);

assertNotNull(account);

assertEquals("Account 1", account.getDescription());

}

}

Spring et les Tests

Page 5: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

Fixe un cadre technique d’utilisation de frameworks pour les tests :JUnit, spring-test, DbUnit, H2, Logback et Mockito

Fournit une hiérarchie de classes réutilisables pour écrire un test unitaire

Selon le besoin : test simple, avec Spring, JDBC, Hibernate, JSFDans le but de tester une classe : notion de systemUnderTest (SUT)

Fournit des annotations simplifiant la configuration des tests :@InjectInto : injection de propriétés dans l’instance testée@DataSet : charge un dataset DBUnit avant l’exécution du test@LoggingConfiguration : spécifie le fichier de configuration logback

Outil de génération de dataset XML DbUnit à partir d’entités Hibernate ou JPA

5

Présentation d’une extension maison

Retour d’Expérience projet

Page 6: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

6

Test unitaire d’un DAO avec cette extension

Jeu de données DbUnit : TestHibernateAccountDao-dataset.xml

<?xml version='1.0' encoding='UTF-8'?>

<dataset>

<ACCOUNT ID="1" BIC="FR7030002005500000157845Z02" LABEL=“Account 1"/>

<ACCOUNT ID="2" BIC="FR70300023455000021Z4234Y45" LABEL="Account 2"/>

</dataset>

+@LoggingConfiguration("classpath:logback-test-dao.xml")

public class TestHibernateAccountDao extends

AbstractDatasetSpringContextTest<HibernateAccountDao> {

@Test

public void findAccountByIBan() {

String iban = "FR70 3000 2005 5000 0015 7845 Z02";

Account account = systemUnderTest.findAccountByIBan(iban);

assertNotNull(account);

assertEquals("Account 2", account.getDescription());

}

}

Retour d’Expérience projet

Page 7: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

7

Test unitaire d’un service avec cette extension

@LoggingConfiguration("classpath:logback-test-service.xml")

public class TestPublishingService extends

AbstractCustomSpringContextTest<IPublishingService> {

@Mock

@InjectInto

private IPublishingWebService publishingWs;

@Test(expected = UnavailablePublishingException.class) // Assertions

public void generatePDFwithPublishingTimeout () throws

InvalidDataException, UnavailablePublishingException {

// Prépare les données

financialInvestment = new FinancialInvestment();

// Bouchonne les services tiers

when(publishingWs.save(any(Document.class)

.thenThrow(new SocketTimeoutException());

// Exécute le scénario

systemUnderTest.generatePDF(financialInvestment );

}

}

Retour d’Expérience projet

Page 8: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Rappels• Un bean de portée Singleton se doit d’être thread-safe• Par défaut, un singleton est créé au démarrage du conteneur• Un bean de portée session est créé pour chaque session web utilisateur

• Problématique– Injecter par auto-wiring un bean de portée Session dans un bean

de portée Singleton• Cas d’usage

– Avoir accès dans un service métier aux informations de l’utilisateur connecté– Besoin : contrôle des habilitations, historisation, logs …

• Solution• Proxifier le bean de portée Session• Le proxy est chargé d’aiguiller les appels vers le bean session approprié

8

Injecter un bean de portée session dans un singleton (1/2)

@Autowired

protected UserDetails userDetails;

Injection de beans de portées différentes

Page 9: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

9

Injecter un bean de portée session dans un singleton (2/2)

Servicesingleton

Conteneur Spring

Proxy

Informations

Utilisateur

Contrôleursingleton

Jamessession

Johnsession

+MISE EN ŒUVRE<bean id="authenticatedUserDetails"

scope="session"

class="com.javaetmoi.core.security.UserDetailsFactory"

factory-method="getUserDetails">

<aop:scoped-proxy />

</bean>

getName() getName()

Injection de beans de portées différentes

+ILLUSTRATION

Implémentation basée sur le SecurityContextHolder de Spring Security

Page 10: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• RAPPEL– Une nouvelle instance est créée à chaque fois qu’un bean prototype est

référencé

• Problématique– Avoir un nouveau bean à chaque fois qu’on y fait appel depuis un singleton– Cas d’usage : Utiliser le pattern commande ou une classe non thread-safe

depuis un singleton

• Solution : Lookup ou injection de méthode

10

Injecter un prototype dans un singleton

<bean id=“contractBuilder"

class=“ContractBuilder"

scope=“prototype“/>

<bean id=“publishingService"

class=“PublishingService">

<lookup-method

name="getContractBuilder"

bean="contractBuilder"/>

</bean>

public abstract class PublishingService {

public void generatePDF() {

ContractBuilder bld = getContractBuilder();

bld.setNumber("CA0000019876")

// ...

}

abstract ContractBuilder getContractBuilder();

}

Injection de beans de portées différentes

Page 11: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Common annotations 1.0• Introduites dans Java EE 5 et dans Java 6 dans le package javax.annotation• Leur usage permet de coller aux standards et de se découpler de Spring

• Annotations supportées• @PostConstruct : méthode appelée une fois le bean instancié et ses

dépendances injectées• @PreDestroy : méthode appelée avant la destruction du bean• @RolesAllowed : sécurise l’appel d’une méthode (équivalent à @Secured)• @Resource : permet d’injecter une ressource JNDI (ex: DataSource)

• Activation des annotations

1. Par un post processeur de beans Spring<bean class="org.springframework.context.

annotation.CommonAnnotationBeanPostProcessor"/>

2. Ou par la balise XML annotation-config : <context:annotation-config/>

11

JSR 250 - Common annotations

Support des JSR

Page 12: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• API Légère dédiée à l’injection de dépendanceso Standardisation des annotations utilisées dans Spring et Google

Guiceo 5 annotations et 1 interface disponibles dans le package javax.inject :

@Inject @Named, Provider, @Qualifier, @Scope, @Singleton

• Supporto JSR-330 Supportée à 100% par Spring 3.xo L’annotation @Inject peut se subsTituer à @Autowiredo Les annotations @Qualifier fonctionnent différemmento Le changement de portée par défaut des beans spring peut être

résolu par la classe Jsr330ScopeMetadataResolver

• Préconisationso A utiliser pour le développement de frameworks / OSSo Intéressant dans une application lorsque Spring est uniquement

utilisé comme simple moteur d’injection de dépendances12

JSR 330 - Dependency Injection for java

Support des JSR

Page 13: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Caractéristiqueso Appelé pendant le processus de création de tout bean Springo Implémente 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 bean6. Appel de la méthode postProcessAfterInitialization() de chaque post-

processor7. Le bean est prêt à être utilisé

13

Fonctionnement

Usage des post-processeurs de bean

Page 14: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Par spring

14

Utilisation au quotidien

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

ApplicationContextAwareProcessor Permet de passer le contexte applicatif Spring aux beansimplémentant l’interface ApplicationContextAware

ScriptFactoryPostProcessor Permet d’accéder au résultat de scripts (Groovy, JRuby )

Post-Processeur Description

BusExtensionPostProcessor Active la détection automatique d’extension de bus CXF

Jsr181BeanPostProcessor Support de l’annotation @WebService par XFire

Usage des post-processeurs de bean

• Par des frameworks tiers

Page 15: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Problématique : enregistrer son propre plugin auprès du conteneur Spring pour traiter les instances de

beans avant leur utilisation

• Cas d’usage : Injecter un logger dans un bean

15

Créer sa propre annotation spring (1/2)

@Service

public class BankingService {

private static final Logger LOGGER =

LoggerFactory.getLogger(BankingService.class);

}

@Service

public class BankingService {

@Logger

private static Logger logger;

}

Usage des post-processeurs de bean

Page 16: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Solution : créer une annotation et le post-processeur de bean chargé de l’interprêter

16

@Retention(RetentionPolicy.RUNTIME) @Target( { ElementType.FIELD })

public @interface Logger {}

public class LoggerBeanPostProcessor implements BeanPostProcessor {

public Object postProcessBeforeInitialization(Object bean,

String beanName) {

ReflectionUtils.doWithFields(bean.getClass(),

new ReflectionUtils.FieldCallback() {

public void doWith(Field field) {

if (field.getAnnotation(Logger.class) != null) {

ReflectionUtils.makeAccessible(field);

Logger logger =getLogger(bean.getClass().getName());

ReflectionUtils.setField(field, bean, logger);

} } });

return bean;

} … }

Créer sa propre annotation spring (2/2)

Usage des post-processeurs de bean

Page 17: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Problématiqueo Le paramétrage d’une application web dépend de son environnemento Exemples : URL d’un web service, login / mot de passe, host d’un serveur

mailo Comment externaliser leur paramétrage ?

• Contrainteso L’EAR Validé en recette est celui déployé en production

o Les paramètres doivent être externalisés en dehors de l’EAR

o L’externalisation ne doit pas dépendre du serveur d’applicationo L’utilisation des URLResource de Websphere sont par exemple écartées

• Solution envisagéeo Utiliser un fichier de configuration qui sera accessible via le classpatho Activer le post-processeur de fabrique de beans

PropertyPlaceholderConfigurer

17

Etude de Cas dans une application web

Externalisation de la configuration

Page 18: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Fichier de configuration

18

Injection de valeur

<context:property-placeholder

location="classpath:com/javaetmoi/config.properties" />

• Chargement par Spring

• Code Java

Contenu du fichier config.properties:

banking.iban.valid = true

@Service

public class BankingService implements IBankingService{

@Value("${banking.valid.iban}")

private Boolean validIban;

}

Externalisation de la configuration

Page 19: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Constato Java n’offre pas de mécanisme unifié pour récupérer le contenu

d’une ressource• Exemples de ressources :

– Fichier texte, fichier de configuration XML, fichier properties, images …

• Exemples de localisation :– Sur le système de fichier, dans un JAR inclu dans le classpath, via une URL …

• Méthodes possibleso getClass().getResourceAsStream("path")o new URL("url").openConnection(). getInputStream()o new FileInputStream(new File("path"))

• Problématiqueo Comment s’abstraire de la localisation d’une ressource ?

19

Méthodes hétérogènes

Chargement des ressources externes

Page 20: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Solution– Utiliser l’abstraction de ressources proposées par Spring via

l’interface Resource• Une implémentation par type de localisation

– FileSystemResource, ClassPathResource, UrlResource

• Offre plusieurs fonctionnalités– Ouverture d’un flux, existante physique, description …

• Localisation d’une ressource– La syntaxe des chemins d’une ressource est spécifique à Spring– Spring se base sur le préfixe pour déterminer quelle

implémentation utiliser

20

Un mécanisme Unifié avec Spring

Localisation de la ressource Exemple de chemins

Classpath de la JVM classpath:com/javaetmoi/applicationContext-service.xml

Accessible par une URL http://javaetmoi.com/feed.rss

Sur le système de fichiers file:/d:/appli/demo/config.properties

Chargement des ressources externes

Page 21: Workshop Spring  3 - Tests et techniques avancées du conteneur Spring

• Utilisation par Spring– En interne, de nombreuses classes du framework utilisent l’interface Resource

– Le contexte applicatif Spring dispose d’un chargeur de ressources

– Exemple : new ClassPathXmlApplicationContext ( tableau de fichiers de conf )

• Injection de Ressources

21

Exemples d’utilisation

public class MailAutomatiqueService {

private Resource mailTemplate;

// Getter

}

<bean id="emailService"

class="com.javaetmoi.EmailService">

<property name="mailTemplate">

<value>classpath:com/javaetmoi/bfa/mail.vl</value>

</property>

</bean>

Chargement des ressources externes