web-dev-qa-db-fra.com

Java Programmation orientée aspect avec annotations

Dans un article intitulé "AOP Fundamentals" , j'ai demandé une explication King's English de ce qu'est l'AOP et de ce qu'il fait. J'ai reçu des réponses très utiles et des liens vers des articles qui m'ont aidé à me renseigner sur toute la théorie.

Mais maintenant, les AOP ont toute mon attention, et tous ces articles et extraits de chapitre sont fantastiques, mais dans chaque cas ils consistent en une théorie noble, de vagues modèles UML et un ordre d'abstraction bien trop élevé à mon goût.

Voici ma compréhension de la théorie AOP, juste pour clarifier, donc si vous voyez quelque chose qui ne va pas, faites le moi savoir!:

  1. Les préoccupations transversales telles que la journalisation, l'authentification, la synchronisation, la validation, la gestion des exceptions, etc. deviennent fortement couplées dans les systèmes non AOP car elles sont utilisées universellement par presque tous les composants/modules de la base de code.

  2. AOP définit les aspects (classes/méthodes) qui résument ces préoccupations transversales avec l'utilisation de points de jonction , conseils et coupes de points .

    une. Conseil - Le code réel (méthode d'un aspect, peut-être?) Mettant en œuvre le souci transversal (c'est-à-dire faire la journalisation proprement dite, valider, authentifier, etc.)

    b. Join Point - Un événement qui est déclenché dans le code non-AOP qui provoque l'exécution des conseils d'un aspect particulier ("tissé" dans le code non-AOP)

    c. Pointcut - Essentiellement, un mappage des points de jonction (événements déclencheurs) à l'exécution des conseils

  3. Tous les aspects sont modularisés (LoggingAspect, AuthenticationAspect, ValidationAspect, etc.) en composants et enregistrés avec un AspectWeaver . Lorsque le code non AOP/POJO rencontre un point de jointure, AspectWeaver "tisse" (intègre) les conseils mappés autour du code non AOP:

 classe publique LoggingAspect 
 {
 // ... 
 
 public void log (String msg) {...} 
} 
 
 classe publique ExceptionHandlingAspect 
 {
 // .. 
 
 public void handle (Exception exc) {.. .} 
} 
 
 classe publique NonAOPCode 
 {
 // ... 
 
 @LoggingAspect @ ExceptionHandlingAspect 
 Public void foo () 
 {
 // faire des choses ... 
} 
} 
 
 // Maintenant dans le pilote 
 Public static int main void (String [] args) 
 {
 NonAOPCode nonAOP = new NonAOPCode (); 
 NonAOP. foo (); 
} 
 
 // L'AspectWeaver * comme par magie * pourrait tisser dans les appels de méthode alors main devient: 
 {
 NonAOPCode nonAOP = nouveau NonAOPCode (); 
 
 log (someMsg); 
 nonAOP.foo (); 
 handle (someExc); 
} 
 

La question à 64 000 $: Ma compréhension de l'AOP basé sur Java est-elle sur la cible, ou bien loin, et pourquoi? Comment pourrait-on correctement utiliser des annotations pour implémenter des aspects, des conseils, des points de jonction, des coupes de points et ce soi-disant tisserand d'aspect?

62
Eugie

Imaginons que vous souhaitiez enregistrer le temps pris par certaines méthodes annotées à l'aide d'un @LogExecTime annotation.

Je crée d'abord une annotation LogExecTime:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecTime {

}

Puis je définis un aspect:

@Component  // For Spring AOP
@Aspect
public class LogTimeAspect {
    @Around(value = "@annotation(annotation)")
    public Object LogExecutionTime(final ProceedingJoinPoint joinPoint, final LogExecTime annotation) throws Throwable {
        final long startMillis = System.currentTimeMillis();
        try {
            System.out.println("Starting timed operation");
            final Object retVal = joinPoint.proceed();
            return retVal;
        } finally {
            final long duration = System.currentTimeMillis() - startMillis;
            System.out.println("Call to " + joinPoint.getSignature() + " took " + duration + " ms");
        }

    }
}

Je crée une classe annotée avec LogExecTime:

@Component
public class Operator {

    @LogExecTime
    public void operate() throws InterruptedException {
        System.out.println("Performing operation");
        Thread.sleep(1000);
    }
}

Et un principal utilisant Spring AOP:

public class SpringMain {

    public static void main(String[] args) throws InterruptedException {
        ApplicationContext context = new GenericXmlApplicationContext("applicationContext.xml");
        final Operator bean = context.getBean(Operator.class);
        bean.operate();
    }
}

Si j'exécute cette classe, j'obtiens la sortie suivante sur stdout:

Starting timed operation
Performing operation
Call to void testaop.Operator.Operate() took 1044 ms

Maintenant avec le magie. Comme j'ai utilisé Spring AOP plutôt que AspectJ weaver, la magie se produit au moment de l'exécution en utilisant des mécanismes proxy-ish. Alors le .class les fichiers ne sont pas modifiés. Par exemple, si je débogue ce programme et que je mets un point d'arrêt dans operate, vous verrez comment Spring a effectué la magie:

Debug screen shot

Comme l'implémentation Spring AOP est non intrusive et utilise les mécanismes Spring dont vous avez besoin pour ajouter le @Component annotation et créer l'objet en utilisant le contexte Spring plutôt que plain new.

AspectJ de l'autre côté changera le .class des dossiers. J'ai essayé ce projet avec AspectJ et décompilé la classe Operator avec jad. Ce qui conduit à:

public void operate()
    throws InterruptedException
{
    JoinPoint joinpoint = Factory.makeJP(ajc$tjp_0, this, this);
    operate_aroundBody1$advice(this, joinpoint, LogTimeAspect.aspectOf(), (ProceedingJoinPoint)joinpoint, (LogExecTime)(ajc$anno$0 == null && (ajc$anno$0 = testaop/Operator.getDeclaredMethod("operate", new Class[0]).getAnnotation(testaop/LogExecTime)) == null ? ajc$anno$0 : ajc$anno$0));
}

private static final void operate_aroundBody0(Operator ajc$this, JoinPoint joinpoint)
{
    System.out.println("Performing operation");
    Thread.sleep(1000L);
}

private static final Object operate_aroundBody1$advice(Operator ajc$this, JoinPoint thisJoinPoint, LogTimeAspect ajc$aspectInstance, ProceedingJoinPoint joinPoint, LogExecTime annotation)
{
    long startMillis = System.currentTimeMillis();
    Object obj;
    System.out.println("Starting timed operation");
    ProceedingJoinPoint proceedingjoinpoint = joinPoint;
    operate_aroundBody0(ajc$this, proceedingjoinpoint);
    Object retVal = null;
    obj = retVal;
    long duration = System.currentTimeMillis() - startMillis;
    System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString());
    return obj;
    Exception exception;
    exception;
    long duration = System.currentTimeMillis() - startMillis;
    System.out.println((new StringBuilder("Call to ")).append(joinPoint.getSignature()).append(" took ").append(duration).append(" ms").toString());
    throw exception;
}

private static void ajc$preClinit()
{
    Factory factory = new Factory("Operator.Java", testaop/Operator);
    ajc$tjp_0 = factory.makeSJP("method-execution", factory.makeMethodSig("1", "operate", "testaop.Operator", "", "", "Java.lang.InterruptedException", "void"), 5);
}

private static final org.aspectj.lang.JoinPoint.StaticPart ajc$tjp_0; /* synthetic field */
private static Annotation ajc$anno$0; /* synthetic field */

static 
{
    ajc$preClinit();
}
63
gabuzo

Il y a quelques mois, j'ai écrit un article avec un exemple sur la façon dont j'ai implémenté un cas pratique de combinaison d'aspects/J avec des annotations Java Java, que vous pourriez trouver utiles:

http://technomilk.wordpress.com/2010/11/06/combining-annotations-and-aspects-part-1/

Je pense que les aspects appliqués aux annotations font une bonne combinaison car ils rendent l'aspect plus explicite dans votre code, mais de manière claire, et vous pouvez utiliser des paramètres dans vos annotations pour plus de flexibilité.

BTW la façon dont Aspect/J fonctionne est en modifiant vos classes au moment de la compilation, pas au moment de l'exécution. Vous exécutez vos sources et vos aspects via le compilateur Aspect/J et il crée les fichiers de classe modifiés.

Spring AOP, pour autant que je le comprenne, fait le tissage (manipulation des fichiers de classe pour inclure le traitement des aspects) d'une manière différente, en créant des objets proxy, je crois qu'au moment de l'instanciation (mais ne prenez pas mon mot pour lui) .

12
Carles Barrobés

J'ai trouvé la réponse moi-même après beaucoup de fouilles et d'huile de coude ...

Oui, AOP doit être basé sur des annotations dans le monde de Java, mais vous ne pouvez pas traiter les annotations liées à l'aspect comme les annotations régulières (métadonnées). Pour intercepter un appel de méthode étiqueté et "tisser" des méthodes de conseil avant/après, vous avez besoin de l'aide de quelques moteurs très orientés AOP tels que AspectJ. Une solution vraiment sympa a été proposée par @Christopher McCann dans un autre fil lié aux annotations, où il a suggéré l'utilisation d'AOP Alliance en collaboration avec Google Guice. Après avoir lu la documentation de Guice sur la prise en charge AOP, voici exactement ce que je recherche: un cadre simple à comprendre pour tisser les "conseils" (appels de méthode) des préoccupations transversales, telles que la journalisation, la validation, la mise en cache, etc.

Celui-ci était un étourdi.

5
Eugie

Changer le commentaire

// The AspectWeaver *magically* might weave in method calls so main now becomes

à

// The AspectWeaver *magically* might weave in method calls so main now
// becomes effectively (the .class file is not changed)

J'aime l'écriture printanière d'AOP. Découvrez Chapitre 7

0
DwB

Voici ma contribution à cet article très utile.

Prenons un exemple très simple: nous devons agir sur le traitement de certaines méthodes. Ils sont annotés avec des annotations personnalisées, qui contiennent des données à gérer. Compte tenu de ces données, nous voulons lever une exception ou laisser le processus se poursuivre comme si la méthode n'était pas annotée.

Le code Java pour définir notre aspect:

package com.example;

public class AccessDeniedForCustomAnnotatedMethodsAspect {

public Object checkAuthorizedAccess(ProceedingJoinPoint proceedingJointPoint)
throws Throwable {

    final MethodSignature methodSignature = (MethodSignature) proceedingJointPoint
                                            .getSignature();

    // how to get the method name
    final String methodName = methodSignature
                                            .getMethod()
                                            .getName();

    // how to get the parameter types
    final Class<?>[] parameterTypes = methodSignature
                                            .getMethod()
                                            .getParameterTypes();

    // how to get the annotations setted on the method
    Annotation[] declaredAnnotations = proceedingJointPoint
                                            .getTarget()
                                            .getClass()
                                            .getMethod(methodName, parameterTypes)
                                            .getDeclaredAnnotations();

    if (declaredAnnotations.length > 0) {

        for (Annotation declaredAnnotation : Arrays.asList(declaredAnnotations)) {

            // I just want to deal with the one that interests me
            if(declaredAnnotation instanceof CustomAnnotation) {

                // how to get the value contained in this annotation 
                (CustomAnnotation) declaredAnnotation).value()

                if(test not OK) {
                    throw new YourException("your exception message");
                }

                // triggers the rest of the method process
                return proceedingJointPoint.proceed();
           }
        }
    }
}

La configuration xml:

<aop:config>
    <aop:aspect id="accessDeniedForCustomAnnotatedMethods"
               ref="accessDeniedForCustomAnnotatedMethodsAspect">
        <aop:around pointcut="execution(@xxx.zzz.CustomAnnotation * *(..))"
               method="checkAuthorizedAccess" />
    </aop:aspect>
</aop:config>

<bean id="accessDeniedForCustomAnnotatedMethodsAspect"
   class="xxx.yyy.AccessDeniedForCustomAnnotatedMethodsAspect" />

J'espère que ça aide !

0
lboix