Workshop Spring 3 - Tests et techniques avancées du conteneur Spring
-
Upload
antoine-rey -
Category
Technology
-
view
3.728 -
download
8
Transcript of 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
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
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
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
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
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
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
• 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
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
• 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
• 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
• 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
• 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
• 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
• 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
• 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
• 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
• 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
• 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
• 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
• 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