Comment récupérer les propriétés d’une annotation avec Spring AOP

Pour les besoins d’une application offrant la possibilité de spécifier des formules pour le calcul de rubrique de paye, je souhaitais enrichir l’entité JPA représentant les formules choisies par l’utilisateur de l’application avec une référence sur l’algorithme « Programme » à exécuter. Vous l’aurez noté l’algorithme est une classe qui implémente un calcul, il varie d’une formule à l’autre et par conséquent il ne peut pas être représenté sous la forme d’une entité JPA. Par contre la formule possède une référence (un identifiant) sur un programme (algorithme). Il fallait donc trouver un moyen pour associer la formule et son algorithme après le chargement de l’entité. Bien entendu je me suis penché immédiatement sur la spécification JPA qui offre la possibilité de déclarer un listener sur les entités et ainsi gérer des événements liés au cycle de vie de ces objets.

Très rapidement j’ai mis de côté cette possibilité qui me conduisait à lier la couche métier avec des services utilisés pour charger certains types de programme. Je me suis donc orienté vers une solution basée sur l’AOP (Aspect Oriented Programming) afin de mettre en oeuvre ce chargement de données sans compromettre le principe d’isolation des couches.
L’idée était de créer une annotation et d’utiliser un aspect pour intercepter tous les appels de méthodes annotées avec cette annotation spécifique. De plus je souhaitais pour plus de souplesse que le traitement réalisé sur les méthodes annotées puisse être modifié si ce mécanisme était utilisé dans d’autres situations ou sur d’autres entités. Spring était utilisé dans ce projet et, tout naturellement, je souhaitais que l’on puisse spécifier dans l’annotation le nom du bean implémentant la logique de l’intercepteur à exécuter.

J’ai commencé par créer l’annotation qui porte nom du bean Spring devant être exécuté sur la méthode annotée.


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PostLoadInterceptor {
    String interceptor();
}

Puis le code de l’aspect

@Aspect
@Component
public class PostLoadInterceptorAspect {
    private static Logger logger = LoggerFactory.getLogger(PostLoadInterceptorAspect.class);
    @AfterReturning(pointcut = "@annotation(PostLoadInterceptor)", returning = "result")
    public Object intercept(JoinPoint jp, Object result) {
        Optional interceptor = getHandlerClassFrom(jp);
        if (interceptor.isPresent()) {
            Object handler = beanUtils.getBean(interceptor.get().interceptor());
            if (LifeCycleInterceptor.class.isAssignableFrom(handler.getClass())) {
                result = ((LifeCycleInterceptor) handler).handle(result);
            }
        }
        return result;
     }
    private Optional getHandlerClassFrom(JoinPoint jp) {
        PostLoadInterceptor interceptor = null;
        final MethodSignature methodSignature = (MethodSignature) jp.getSignature();
methodSignature.getMethod();
        try {
            Method method = jp.getTarget().getClass().getMethod(methodSignature.getMethod().getName(), methodSignature.getMethod().getParameterTypes());
            interceptor = method.getAnnotation(PostLoadInterceptor.class);
        } catch (SecurityException | NoSuchMethodException e) {
            logger.warn("Erreur lors de la recherche de l'annotation PostLoadInterceptor dans la classe {}  message {}", jp.getTarget().getClass().getName(), e.getMessage());
        }
        return Optional.ofNullable(interceptor);
    }
}

Et enfin l’illustration de l’utilisation de l’annotation sur une méthode d’un service.


public class FormuleServiceImpl implements FormuleService {

    @Autowired
    private FormuleDao formuleDao;

    @Override
    @PostLoadInterceptor(interceptor = "formulePostLoadInterceptor")
    public Formule findOne(Integer id) {
        return formuleDao.findOne(id);
    }
    ...

Dans cet exemple de code, l’objet beanUtils est un composant dont la responsabilité est de s’interfacer avec le contexte de Spring pour charger un bean Spring.
J’ai fait le choix que les beans implémentant les intercepteurs, devaient respecter une interface particulière (LifeCycleInterceptor) qui définit une méthode handle(Object value). celle ci prend en paramètre l’objet renvoyé par la méthode sur laquelle l’annotation a été positionnée. L’aspect est également exécuté uniquement si la méthode annotée s’est correctement exécutée (@AfterReturning).
Mais la plus grande difficulté a été de récupérer la propriété de l’annotation (ici le nom du bean devant être exécuté) car si l’annotation est positionnée sur une classe concrète (versus une interface) JoinPoint.getSignature() renvoie la signature de la méthode définie dans l’interface implémentée par le bean et cette dernière ne porte pas l’annotation. L’idée est donc de récupérer via l’objet JointPoint la classe et la méthode portant l’annotation (ici un bean Spring).
J’espère que cet article un peu technique permettra à certains lecteurs de mettre en place ce type de fonctionnalité sans difficulté… Pour moi ce sera un mémo 😉

http:///www.jperf.com

 

Publicités

A propos jlerbsc

founder of JavaPerf Consulting Http://www.jperf.com
Cet article, publié dans tech notes, est tagué . Ajoutez ce permalien à vos favoris.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion /  Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion /  Changer )

w

Connexion à %s