65 votes

Programmation orientée aspect en Java avec annotations

Dans un billet intitulé "Principes fondamentaux de la POA" J'ai demandé une L'anglais du roi explication de ce qu'est le POA, 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é à comprendre toute la théorie.

Mais maintenant, l'AOP a retenu toute mon attention, et tous ces articles et extraits de chapitres sont fantastiques, mais en chaque cas ils consistent en une théorie noble, des modèles UML vagues et un niveau d'abstraction beaucoup trop élevé à mon goût.

Voici ma compréhension de la théorie de la POA, juste pour clarifier, donc si vous voyez quelque chose qui semble faux, faites-le moi savoir !

  1. Les préoccupations transversales telles que la journalisation, l'authentification, la synchronisation, la validation, le traitement des exceptions, etc. deviennent fortement couplées dans les systèmes non-AOP car elles sont utilisées de manière universelle par presque tous les composants/modules du code source.

  2. La POA définit aspects (classes/méthodes) qui permettent d'abstraire ces préoccupations transversales à l'aide d'outils de gestion de l'information. points de jonction , conseils y pointscuts .

    a. Conseils - Le code réel (méthode d'un aspect, peut-être ?) qui met en œuvre la préoccupation transversale (c'est-à-dire qui effectue la journalisation réelle, la validation, l'authentification, etc.)

    b. Point de jonction - Un événement déclenché dans le code non-AOP qui entraîne l'exécution de l'avis d'un aspect particulier ("tissé" dans le code non-AOP).

    c. Pointcut - Il s'agit essentiellement d'une mise en correspondance des points de jonction (événements déclencheurs) avec 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 du code non-AOP/POJO rencontre un point de jonction, AspectWeaver " tisse " (intègre) le conseil mappé autour du code non-AOP :

    public class LoggingAspect { // ...

    public void log(String msg) { ... }

    }

    public class ExceptionHandlingAspect { // ..

    public void handle(Exception exc) { ... }

    }

    public class NonAOPCode { // ...

    @LoggingAspect @ExceptionHandlingAspect
    public void foo()
    {
        // do some stuff...
    }

    }

    // Now in the driver public static int main void(String[] args) { NonAOPCode nonAOP = new NonAOPCode(); nonAOP.foo(); }

    // The AspectWeaver *magically* might weave in method calls so main now becomes: { NonAOPCode nonAOP = new NonAOPCode();

    log(someMsg);
    nonAOP.foo();
    handle(someExc);

    }

La question à 64 000 $ : Ma compréhension de la POA basée sur Java est-elle bonne ou mauvaise, et pourquoi ? Comment pourrait-on correctement utiliser les annotations pour mettre en œuvre les aspects, les conseils, les points de jonction, les coupes transversales et ce soi-disant tisseur d'aspects ?

66voto

gabuzo Points 3181

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

Je crée d'abord une annotation LogExecTime :

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

}

Ensuite, 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 de type proxy. Ainsi, le .class sont laissés intacts. Par exemple, si je débogue ce programme et que je place un point d'arrêt en operate vous verrez comment Spring a opéré la magie :

Debug screen shot

Comme l'implémentation de Spring AOP est non intrusif et utilise les mécanismes de Spring, vous devez ajouter l'élément @Component et créer l'objet en utilisant le contexte Spring plutôt que le simple contexte new .

De l'autre côté, AspectJ changera la .class fichiers. J'ai essayé ce projet avec AspectJ et j'ai décompilé la classe Operator avec jad. Ce qui a 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();
}

12voto

Carles Barrobés Points 5283

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

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

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

De plus, Aspect/J fonctionne en modifiant vos classes au moment de la compilation, et non au moment de l'exécution. Vous faites passer vos sources et vos aspects par le compilateur Aspect/J et il crée les fichiers de classe modifiés.

Spring AOP, pour autant que je le comprenne, effectue 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 me croyez pas sur parole).

5voto

Eugie Points 503

J'ai trouvé la réponse moi-même après avoir beaucoup creusé et utilisé de l'huile de coude...

Oui, la POA devrait être basée sur les annotations dans le monde de Java, mais vous ne pouvez pas traiter les annotations liées aux aspects comme des annotations ordinaires (métadonnées). Pour intercepter un appel de méthode balisé et "tisser" des méthodes de conseil avant/après, vous avez besoin de l'aide de certains moteurs très astucieux centrés sur la POA tels qu'AspectJ. Une solution très intéressante a été proposée par @Christopher McCann dans un autre fil de discussion sur les annotations, où il a suggéré l'utilisation de AOP Alliance en conjonction avec Google Guice. Après avoir lu la documentation de Guice sur la prise en charge de la POA, c'est exactement ce que je recherche : un cadre simple à comprendre pour intégrer les "conseils" (appels de méthode) des préoccupations transversales, comme la journalisation, la validation, la mise en cache, etc.

Celui-là était nul.

0voto

DwB Points 14687

Modifier 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'article sur le printemps d'AOP. Regardez Chapitre 7

0voto

lboix Points 88

Voici ma contribution à cet article très utile.

Nous allons prendre un exemple très simple : nous devons agir sur le traitement de certaines méthodes. Elles sont annotées avec des annotations personnalisées, qui contiennent des données à traiter. En donnant ces données, nous voulons lever une exception ou laisser le processus se dérouler comme si la méthode n'était pas annotée.

Le code Java pour définir notre aspect :

    package xxx.yyy;

        public class AccessDeniedForCustomAnnotatedMethodsAspect {

            public Object checkAuthorizedAccess(ProceedingJoinPoint proceedingJointPoint) throws Throwable {

                final MethodSignature methodSignature = (MethodSignature) proceedingJointPoint.getSignature();
                final String methodName = methodSignature.getMethod().getName(); // how to get the method name
                final Class<?>[] parameterTypes = methodSignature.getMethod().getParameterTypes(); // how to get the parameter types

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

                if (declaredAnnotations.length > 0) {

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

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

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

if(test not OK) {

    throw new YourException("your exception message");
}
                return proceedingJointPoint.proceed(); // triggers the rest of the method process
            }

        }
}

}

La configuration xml :

<beans ... xmlns:aop="http://www.springframework.org/schema/aop">

<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 cela vous aidera !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X