Les secrets du conteneur Spring vous attendent pour ce 3ième workshop sur le framework Spring : module spring-test, injection de beans de portées différentes, support des JSR 250 et 330, post-processeurs de beans, fichiers de configuration et abstraction de l’accès aux ressources.
Workshop Spring 3 - Tests et techniques avancées du conteneur Spring
1. Workshop Spring - Session 3
Tests & Techniques avancées
du conteneur Spring
Conçu en décembre 2011
Réactualisé en novembre 2014
# 1
2. 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
3. Spring et les Tests
Conteneur léger accessible aux tests unitaires et
d’intégration
Support de JUnit 4 et TestNG
Chargement du contexte Spring
Injection de dépendances
Mise en cache du contexte
Extensions JUnit par
Runner : SpringJUnit4ClassRunner
Annotations : @ContextConfiguration, @Rollback, @Repeat …
Listeners : DependencyInjectionTestExecutionListener …
Bouchons prêts à l’emploi
MockHttpSession, MockHttpServletRequest,
MockPortletSession ...
3
Les apports du module Spring Test
4. 4
Spring et les Tests
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());
}
}
5. Retour d’Expérience projet
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, JSF
Dans 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
6. 6
Retour d’Expérience projet
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());
}
}
7. 7
Retour d’Expérience projet
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 );
}
}
8. Injection de beans de portées différentes
• 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;
9. 9
Injection de beans de portées différentes
Injecter un bean de portée session dans un singleton (2/2)
Service
singleton
Proxy
Informations
Utilisateur
Conteneur Spring
Contrôleur
singleton
James
session
John
session
+MISE EN OEUVRE
<bean id="authenticatedUserDetails"
scope="session"
class="com.javaetmoi.core.security.UserDetailsFactory"
factory-method="getUserDetails">
<aop:scoped-proxy />
</bean>
getName() getName()
+ILLUSTRATION
Implémentation basée sur le
SecurityContextHolder de Spring
Security
10. Injection de beans de portées différentes
• 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();
}
11. Support des JSR
• 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
12. Support des JSR
• API Légère dédiée à l’injection de dépendances
o Standardisation des annotations utilisées dans Spring et Google
Guice
o 5 annotations et 1 interface disponibles dans le package javax.inject :
@Inject @Named, Provider, @Qualifier, @Scope, @Singleton
• Support
o JSR-330 Supportée à 100% par Spring 3.x
o L’annotation @Inject peut se subsTituer à @Autowired
o Les annotations @Qualifier fonctionnent différemment
o Le changement de portée par défaut des beans spring peut être
résolu par la classe Jsr330ScopeMetadataResolver
• Préconisations
o A utiliser pour le développement de frameworks / OSS
o Intéressant dans une application lorsque Spring est uniquement
utilisé comme simple moteur d’injection de dépendances
12
JSR 330 - Dependency Injection for java
13. Usage des post-processeurs de bean
• Caractéristiques
o Appelé pendant le processus de création de tout bean Spring
o Implémente 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
6. Appel de la méthode postProcessAfterInitialization() de chaque post-processor
7. Le bean est prêt à être utilisé
13
Fonctionnement
14. Usage des post-processeurs de bean
• 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 beans
implémentant l’interface ApplicationContextAware
ScriptFactoryPostProcessor Permet d’accéder au résultat de scripts (Groovy, JRuby )
• Par des frameworks tiers
Post-Processeur Description
BusExtensionPostProcessor Active la détection automatique d’extension de bus CXF
Jsr181BeanPostProcessor Support de l’annotation @WebService par XFire
15. Usage des post-processeurs de bean
• 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;
…
}
16. Usage des post-processeurs de bean
Créer sa propre annotation spring (2/2)
• Solution : créer une annotation et le post-processeur de
bean chargé de l’interprêter
@Retention(RetentionPolicy.RUNTIME) @Target( { ElementType.FIELD })
public @interface Logger {}
public class LoggerBeanPostProcessor implements BeanPostProcessor {
16
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;
} … }
17. Externalisation de la configuration
• Problématique
o Le paramétrage d’une application web dépend de son environnement
o Exemples : URL d’un web service, login / mot de passe, host d’un serveur
mail
o Comment externaliser leur paramétrage ?
• Contraintes
o 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’application
o L’utilisation des URLResource de Websphere sont par exemple écartées
• Solution envisagée
o Utiliser un fichier de configuration qui sera accessible via le classpath
o Activer le post-processeur de fabrique de beans
PropertyPlaceholderConfigurer
17
Etude de Cas dans une application web
18. Externalisation de la configuration
• Fichier de configuration
18
Injection de valeur
Contenu du fichier config.properties:
banking.iban.valid = true
• Chargement par Spring
<context:property-placeholder
location="classpath:com/javaetmoi/config.properties" />
• Code Java
@Service
public class BankingService implements IBankingService{
@Value("${banking.valid.iban}")
private Boolean validIban;
…
}
19. Chargement des ressources externes
• Constat
o 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 possibles
o getClass().getResourceAsStream("path")
o new URL("url").openConnection(). getInputStream()
o new FileInputStream(new File("path"))
• Problématique
o Comment s’abstraire de la localisation d’une ressource ?
19
Méthodes hétérogènes
20. Chargement des ressources externes
• 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
21. Chargement des ressources externes
• 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>