Java Aspect-Oriented Programming with Annotations

后端 未结 5 1621
無奈伤痛
無奈伤痛 2020-11-30 16:57

In a post entitled \"AOP Fundamentals\", I asked for a King\'s English explanation of what AOP is, and what it does. I received some very helpful answers and links

相关标签:
5条回答
  • 2020-11-30 17:32

    Some months ago I wrote an article with an example on how I implemented a practical case of combining Aspect/J aspects with Java annotations, that you may find useful:

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

    I believe aspects applied to annotations make a good combination because they make the aspect more explicit in your code, but in a clean way, and you can use parameters in your annotations for further flexibility.

    BTW the way Aspect/J works is by modifying your classes at compile time, not at run time. You run your sources and aspects through the Aspect/J compiler and it creates the modified class files.

    Spring AOP, as far as I understand it, does the weaving (manipulating the class files to include aspect processing) in a different way, by creating proxy objects, I believe that at instantiation time (but don't take my word for it).

    0 讨论(0)
  • 2020-11-30 17:42

    Change the comment

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

    to

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

    I like the spring writeup of AOP. Check out Chapter 7

    0 讨论(0)
  • 2020-11-30 17:44

    Found the answer myself after much digging and elbow grease...

    Yes, AOP should be annotation-based in the world of Java, however you can't process aspect-related annotations like regular (metadata) annotations. To intercept a tagged method call and "weave" advice methods before/after it, you need the help of some very nifty AOP-centric engines such as AspectJ. A really nice solution was offered by @Christopher McCann in another annotation-related thread, where he suggested the use of AOP Alliance in conjunction with Google Guice. After reading Guice's documentation on AOP support, this is exactly what I'm looking for: a simple-to-understand framework for weaving in the "advice" (method calls) of cross-cutting concerns, such as logging, validating, caching, etc.

    This one was a douzy.

    0 讨论(0)
  • 2020-11-30 17:45

    Let's imagine you want to log the time taken by some annoted methods using a @LogExecTime annotation.

    I first create an annotation LogExecTime:

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

    Then I define an 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");
            }
    
        }
    }
    

    I create a class annoted with LogExecTime:

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

    And a main using 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();
        }
    }
    

    If I run this class I'm getting the following output on stdout:

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

    Now with the magic. As I did use Spring AOP rather than AspectJ weaver, the magic is occurring at run time using proxy-ish mechanisms. So the .class files are left untouched. For instance if I debug this program and put a breakpoint in operate you'll see how Spring has performed the magic:

    Debug screen shot

    As Spring AOP implementation is non-intrusive and uses the Spring mechanisms you need to add the @Component annotation and create the object using Spring context rather than plain new.

    AspectJ on the other side will change the .class files. I tried this project with AspectJ and decompiled the Operator class with jad. Which lead to:

    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();
    }
    
    0 讨论(0)
  • 2020-11-30 17:49

    Here's my contribution to this very useful post.

    We will take a very simple example: we need to take action on some methods' processing. They are annotated with custom annotations, which contain data to handle. Given this data we want to raise an exception or let the process continue like the method was not annotated.

    The Java code for defining our 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();
               }
            }
        }
    }
    

    The xml configuration :

    <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" />
    

    Hope it helps !

    0 讨论(0)
提交回复
热议问题