Annotations personnalisées résolues au runtime
Problème
Dans certains cas il serait utile de pouvoir décrire des comportements au niveau des interfaces de vos classes. La plupart du temps cette description est ajoutée à la javadoc des interfaces, pour les API publiques du moins. Le problème c'est que cette documentation n'est pas compréhensible par l'application ni à la compilation ni à l’exécution. Il est donc nécessaire d'implémenter un mécanisme qui répond à la documentation.
À ce stade, la difficulté est de garder l’implémentation conforme à la documentation, sans qu'aucun garde fou ne nous y contraigne.
Java 5 à introduit le principe d'annotation, qui permet d'introduire des informations supplémentaires à la signature des méthodes ou la déclaration des properties.
Plusieurs JSR décrivent des "dictionnaires" d'annotations par exemple :
Avant de commencer
Comme pour les autres articles nous utiliserons Maven pour configurer le projet. Donc, créez un projet Maven :)
Ajoutez-y ceci afin que le projet soit compilé en 1.6 :
Faites une interface de service et décrivez-y les restrictions nécessaires grâce aux annotations :
La résolution des annotations se fera par AOP. Nous allons donc utiliser spring et aspectj pour ça. Rajouter ceci à votre pom :
Et configurez Spring en créant un fichier spring-contect.xml dans le même package que votre code (mais dans le répertoire ressource de Maven ;)):
L'aspect
À présent nous allons implémenter l'aspect :
Voilà, le point de jointure se trouve sur l'interface et nous demandons à l'aspect de vérifier si il y a une annotation "Security" sur l'interface de la méthode appelée. Ici l’implémentation consiste seulement à écrire la valeur que prend l'annotation dans l'interface.
Testons
À présent nous allons utiliser Junit pour tester ceci :
Et voilà ce que vous devriez obtenir à l'exécution du test :
Comme vous avez pu le voir, c'est au niveau de l'aspect que votre annotation est prise en compte. C'est donc par là que vous devrez implémenter la mécanique particulière à vos besoins.
Amusez-vous bien :)
Dans certains cas il serait utile de pouvoir décrire des comportements au niveau des interfaces de vos classes. La plupart du temps cette description est ajoutée à la javadoc des interfaces, pour les API publiques du moins. Le problème c'est que cette documentation n'est pas compréhensible par l'application ni à la compilation ni à l’exécution. Il est donc nécessaire d'implémenter un mécanisme qui répond à la documentation.
À ce stade, la difficulté est de garder l’implémentation conforme à la documentation, sans qu'aucun garde fou ne nous y contraigne.
Java 5 à introduit le principe d'annotation, qui permet d'introduire des informations supplémentaires à la signature des méthodes ou la déclaration des properties.
Plusieurs JSR décrivent des "dictionnaires" d'annotations par exemple :
- La JSR 181 qui décrit les annotations pour les webservices,
- la JSR 303 les validations de bean,
- ou bien encore la JSR 317 qui décrit les annotations JPA 2,
- ...
Ceci répond correctement à la problématique, mais vous pouvez vous trouver, comme souvent, devant un problème particulier auquel aucune norme ni implémentation ne répond.
Nous allons voir dans la suite de cet article comment nous pouvons créer nos propres annotations à ajouter au niveau des interfaces, et comment les résoudre à l’exécution.
Avant de commencer
Comme pour les autres articles nous utiliserons Maven pour configurer le projet. Donc, créez un projet Maven :)
Ajoutez-y ceci afin que le projet soit compilé en 1.6 :
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<compilerVersion>1.6</compilerVersion>
</configuration>
</plugin>
</plugins>
</build>
Déclarer ses propres annotations
Pour la suite nous nous métrons dans le cas ou nous voulons décrire des niveaux de sécurité à appliquer à l'appel des méthodes.
Créer la classe Security comme suit :
Créer la classe Security comme suit :
package com.psytech.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)//Annotation pour méthode
@Retention(RetentionPolicy.RUNTIME)//Doit être conservée après la compilation
public @interface Security {
public enum Level {
LOW, STRICT
}
Level[] levels();
}
Faites une interface de service et décrivez-y les restrictions nécessaires grâce aux annotations :
package com.psytech.security;
import com.psytech.security.Security.Level;
public interface MyService {
@Security(levels = { Level.LOW })
void sendMail();
@Security(levels = { Level.LOW })
void sendSMS();
@Security(levels = { Level.STRICT })
void sendLolCat();
}
La résolution des annotations se fera par AOP. Nous allons donc utiliser spring et aspectj pour ça. Rajouter ceci à votre pom :
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.1.1.RELEASE</version> <type>jar</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.1.1.RELEASE</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.5.4</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.5.4</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>3.1.1.RELEASE</version> </dependency> </dependencies>
Et configurez Spring en créant un fichier spring-contect.xml dans le même package que votre code (mais dans le répertoire ressource de Maven ;)):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<aop:aspectj-autoproxy />
<context:component-scan base-package="com.psytech.security" />
</beans>
Comme vous pouvez le voir la configuration est très simple et je vous renvoie à la doc de Spring si vous voulez en savoir plus.
Faites une implémentation rapide de votre service :
Faites une implémentation rapide de votre service :
package com.psytech.security;
import org.springframework.stereotype.Component;
@Component
public class MyServiceImpl implements MyService {
public void sendMail() {
System.out.println("Mail sent");
}
public void sendSMS() {
System.out.println("SMS sent");
}
public void sendLolCat() {
System.out.println("Lol Cat sent");
}
}
L'aspect
À présent nous allons implémenter l'aspect :
package com.psytech.security; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import com.psytech.security.Security.Level; @Aspect @Component public class SecurityImplAOP { @Around("execution(public * com.psytech.security.MyService+.*(..))") public void checkSecurity(final JoinPoint joinpoint) { if (joinpoint.getSignature() instanceof MethodSignature) { final MethodSignature methodSignature = (MethodSignature) joinpoint.getSignature(); System.out.println(methodSignature.getName()); final Security security = AnnotationUtils.findAnnotation(methodSignature.getMethod(), Security.class); if (security != null) for (final Level level : security.levels()) System.out.println(level.toString()); } } }
Voilà, le point de jointure se trouve sur l'interface et nous demandons à l'aspect de vérifier si il y a une annotation "Security" sur l'interface de la méthode appelée. Ici l’implémentation consiste seulement à écrire la valeur que prend l'annotation dans l'interface.
Testons
À présent nous allons utiliser Junit pour tester ceci :
package com.psytech.security; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/com/psytech/security/spring-context.xml" }) public class TestMyServiceImpl { @Autowired MyService myService; @Test public void testSendMail() { myService.sendMail(); } @Test public void testSendSMS() { myService.sendSMS(); } @Test public void testSendLolCat() { myService.sendLolCat(); } }
Et voilà ce que vous devriez obtenir à l'exécution du test :
sendMail LOW sendSMS LOW sendLolCat STRICT
Comme vous avez pu le voir, c'est au niveau de l'aspect que votre annotation est prise en compte. C'est donc par là que vous devrez implémenter la mécanique particulière à vos besoins.
Amusez-vous bien :)
Commentaires
Enregistrer un commentaire