Wednesday 5 December 2012

MethodCallSpy – technical notes for curious

Two Types of Weaving in AOP

Build Time Weaving is more of a style of a regular programming. Aspects are typically as an integral part of target application. It is used to achieve a greater Separation of Concerns, where it cannot be easily reached by ordinary design patterns, to keep code simple and readable. Typical usage – unobtrusive logging, declarative transaction management, transparent caching. These things may “pollute” main code from a design point of view or compromise speed when even a few surplus condition matter. You can easily remove all logging commands as a mater of changing a single build configuration option, without a single change in source code.
AspectJ distinguishes Compile-time weaving (source code level) and Post-compile weaving (binary class weving). I dared to put them in one category for purpose of this article.
The Load Time Weaving (LTW) is rather a technique for ad hoc modification of unsuspecting target application in runtime. You “hack in” to the target application. Such Aspects are developed separately from target code. Often without having access to the original source code. It is used only for specific tasks, like method tracing, performance monitoring.
Imagine this situation. You have build application, a JAR or WAR, it passed whole formal releasing process, not a trivial thing in big corporations, it was successfully deployed. But then you suspect an issue. Unfortunately, none put any logging to the suspected part of the application. Time is crucial, rebuild is not an option. LTW is to rescue. Write simple advice, targeted to specific package or class, log internal states. Still, the server needs to be restarted, start up parameters added to include the aspect related, but still better then full rebuild and redeployment.
Or this scenario. You need to trace used classes do do some serious cleaning. Your application is still in development, in environment you control. You could rebuild it using regular build time weaver with ajc compiler. However, lets assume it is a legacy application, mature, with complex build, chain of build tools, mixture of JVM languages, Java and Groovy, not necessary a Maven build, figuring out where to plug ajc compiler and having the confidence all intended classes were really advised, it may be tricky business. Time consuming. Much simpler and safer is to use LTW.
For more read here.

Code based style versus annotation based style

AspectJ supports two styles, the original code based, and from version 1.5 also annotation based style. TraceAgent code uses annotation based style. You can clearly see @Pointcut , @After, @Before, @Around, @Aspect from package org.aspectj.lang.annotation. After in source code of TraceAllAgent class.
Benefit is, you don't need any special compiler or IDE plugin. Just core Java and basic Eclipse JDT. Disadvantage is, it does not syntactically checks the pointcut definitions, there is no indication which classes are affected by the aspects. Everything is evaluated only in runtime. AspectJ has excellent plugin – AJDT or Eclipse plugin for AspectJ. It is actively developed, currently in version 2.2.1. See video demonstrations what it can do. If you plan to deal with aspects more intensively, it would pay off to invest time and effort and set up you IDE and tool chain accordingly and use aspect classes (*.aj files). For our all method tracing agent, a one off tool, it is no brainer to pick annotation based rules definition.
The use of the @AspectJ annotations means that application can be compiled by a regular Java 5 compiler, and subsequently woven by the AspectJ weaver (for example, as an additional build stage, or as late as class load-time).
Unfortunately annotation based style is much less documented. Most available examples on internet use code based style. The official developers guide - The AspectJ Programming Guide (link) – barely mentions annotation style. The best (if not only) documentation to @AspectJ annotations are in the older guide The AspectJ 5 Development Kit Developer's Notebook, see chapter chapter 9 there:
http://www.eclipse.org/aspectj/doc/released/adk15notebook/ataspectj-pcadvice.html
This "developers notebook" is still far more complete documentation then Programming Guide. As a preffered source of information is suggested even by AspectJ authorities.

Code based style versus annotation based style – comparison

Code based style. Note the aspect keyword instead usuall class.
public aspect Foo {
      pointcut listOperation() : execute(* java.util.List.*(..));
      pointcut anyUtilityCall() : execute(* my.home.util..*(..));

      before() : listOperation() {
         System.out.println(...);
      }

      after() : anyUtilityCall() {
         System.out.println(...);
      }
}
Equivalent in annotation based style, using @AspectJ annotations.
@Aspect
public class Foo {
      @Pointcut("execute(* java.util.List.*(..))")
      void listOperation() {}


      @Pointcut("execute(* my.home.util..*(..))")
      void anyUtilityCall() {}

     @Before("listOperation()")
     public void logAllMethodsOnAList() {
       // advice method name is can be anything
       System.out.println(...);
     }

     @After("anyUtilityCall()")
     public void logUtilityMethods() {
       System.out.println(...);
     }
}
In most situations you can simplify this even more:
@Aspect
public class Foo {

     @Before(pointcut = "execute(* java.util.List.*(..))")
     public void logAllMethodsOnAList() {
       // advice method name is can be anything
       System.out.println(...);
     }

     @After(pointcut = "execute(* my.home.util..*(..))")
     public void logUtilityMethods() {
       System.out.println(...);
     }
}
Advice methods - those annotated with @Before, @After etc. - can have arguments. You can handle (selected) arguments in this way, as a proper named arguments, or some other runtime information, like reference to calling instance.
Or you can add JoinPoint as an advice argument as in TraceAllAgent class.
@Before("allMethodsAllClasses()")
public void beforeTracedMethods(JoinPoint joinPoint) {
     String className = joinPoint.getStaticPart()
          .getSignature().getDeclaringTypeName();
     . . .
}

Conditional advice execution

Point cut method is usually empty, unless you use if() pointcut expression:
@Pointcut("call(* *.processEvent(JComponent)) && args(eventSource) && if()")
public static boolean someCallWithIfTest(Widget w) {
     return eventSource instanceof JButton;
}
Note that return value is now boolean and not void. Call the advice only when only when condition is satisfied, that is when processMedhod parameter is JButton and only then.

Maven dependencies

<dependency>
     <groupId>aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>1.5.4</version>
</dependency>
<dependency>
     <groupId>aspectj</groupId>
     <artifactId>aspectjrt</artifactId>
     <version>1.5.4</version>
</dependency>
<dependency>
     <groupId>aspectj</groupId>
     <artifactId>aspectjtools</artifactId>
     <version>1.5.4</version>
</dependency>
Much newer version of AspectJ is available in the time of writing – AspectJ 1.7.1.
However in main Maven repository was 1.5.4 still the latest so I stuck with it. No issues.

Interesting links

AspectJ homepage - http://www.eclipse.org/aspectj/
http://www.eclipse.org/aspectj/doc/released/adk15notebook/ - The AspectJ 5 Development Kit Developer's Notebook
http://www.eclipse.org/aspectj/doc/next/devguide/ltw.html - Load-Time Weaving chapter in Developers Guide. Also explains Compile-time and Post-compile weaving.
AJDT or Eclipse plugin for AspectJ
http://www.eclipse.org/aspectj/doc/next/adk15notebook/annotations-pointcuts-and-advice.html - Join Point Matching based on Annotations. Do NOT confuse this with annotation based style. This chapter is about intercepting only methods with certain annotation and similar rules.
http://www.eclipse.org/aspectj/doc/next/progguide/semantics-pointcuts.html – list of all (?) possible pointcut definitions with examples. Note, everything is in code based style. But you should be now able co convert them to annotation based style. See chapter above.
http://www.eclipse.org/aspectj/doc/released/faq.php#q:comparecallandexecution
What is the difference between call and execution join points?
Summary: Since AspectJ 1.1 you should use the execute() pointcut designator unless you have a good reason to use call().
http://www.eclipse.org/aspectj/doc/next/progguide/starting-aspectj.html#inter-type-declarations – another interesting feature of AspectJ. You can add methods, fields and even modify hierarchy using these inter type declarations. However, it looks like it can be used only with compile time weaving, not LTW.

Appendix – code to locate AspectJ jar on classpath

To detect full path to AspectJ agent library, something suitable for tests, not tied to any file system layout, I wrote this utility method:
public static String getAspectjWeaverJarFullPath() {
     ClassLoader loader = SampleMultithreadAppTest.class.getClassLoader();
     String aspectjTypicalClass = "org/aspectj/weaver/ltw/LTWeaver.class";
     String aspectjUrl = loader.getResource(aspectjTypicalClass)
          .toExternalForm();
     return aspectjUrl.substring(aspectjUrl.indexOf('/') + 1,
          aspectjUrl.indexOf(".jar!") + 4);
}
The method looks for a prominent AspectJ class, containing pre-main method, the entry point of any Java Agent. Classes can be seen as files in a directory, so ClassLoader is able to find them by getResource(), don't forget to use “/”instead “.” and add file suffix of “.class”. Method toExternalForm then return something like:
jar:file:/home/dev/maven_repo/org/aspectj/aspectjweaver/1.5.4/aspectjweaver-1.5.4.jar!/org/aspectj/weaver/ltw/LTWeaver.class. With some careful text stripping we can get path to AspectJ weaver jar. AspectJ weaver jar has to be on the classpath, we can take it as granted here, see the general requirements in the previous article.
This approach is better alternative to search through classpath string. It can deal with non-standard AspectJ names, like having version included in name or not, or non-standard assembly, like bulk all-in-one JAR. Until LTWeaver class in package org.aspectj.weaver.ltw, it will work.


No comments:

Post a Comment