开发者

Java Aspect-Oriented Programming with Annotations

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 to articles that helped fill me in on all the theory.

But now AOP's got my full attention, and all these articles and chapter excerpts are fantastic, but in every single case they consist of lofty theory, vague UML models, and order of abstraction that are way too high-up for my liking.

Here is my understanding of AOP theory, just to clarify, so if you see something that looks wrong, let me know!:

  1. Cross-cutting concerns such as Logging, Authenticating, Synchronizing, Validating, Exception Handling, etc. become highly-coupled in non-AOP systems as they are used universally by almost every component/module in the codebase.

  2. AOP defines aspects (classes/methods) that abstract these cross-cutting concerns with the use of join points, advice, and pointcuts.

    a. Advice - The actual code (method of an aspect, perhaps?) implementing the cross-cutting concern (i.e. doing the actual logging, validating, authenticating, etc.)

    b. Join Point - An event that is triggered in non-AOP code that causes a particular aspect's advice to be executed ("woven" into the non-AOP code)

    c. Pointcut - Essentially, a mapping of join points (triggering events) to advice execution

  3. All aspects are modularized (LoggingAspect, AuthenticationAspect, ValidationAspect, etc.) into components and registered with an AspectWeaver. When non-AOP/POJO code comes across a join point, AspectWeaver "weaves" (integrates) the mapped advice around the non-AOP code:

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);
}

The $64,000 Question: Is my understanding of Java-based AOP on target, or way off, and why? How could one correctly use annotations to implement aspects, advi开发者_开发问答ce, join points, pointcuts and this so-called aspect weaver?


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:

Java Aspect-Oriented Programming with Annotations

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();
}


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).


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.


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


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜