Chapter 7. Instrumentation

In this chapter, we explain how to instrument (or "aspectize") the POJOs via JBossAop. There are two steps needed by JBossAop: 1) POJO declaration, 2) instrumentation. But depends on the JDK and instrumentation mode that you are using, you may not need to pre-process your POJO at all. That is, if you use JDK5.0 and load-time mode, then all you need to do is annotation your POJO (or declare it in a xml file). This makes your PojoCache programming nearly transparent.

For the first step, since we are using the dynamic Aop feature, a POJO is only required to be declared "prepare". Basically, there are two ways to do this: either via explicit xml or annotation (new since release 1.2.3.)

As for the second step, either we can ask JBossAop to do load-time (through a special class loader, so-called loadtime mode) or compile-time instrumentation (use of an aopc pre-compiler, so-called precompiled mode)

7.1. XML descriptor

To declare a POJO via XML configuration file, you will need a META-INF/jboss-aop.xml file located under the class path. JBossAOP framework will read this file during startup to make necessary byte code manipulation for advice and introduction. Or you can pre-compile it using a pre-compiler called aopc such that you won't need the XML file during load time. JBossAop provides a so-called pointcut language where it consists of a regular expression set to specify the interception points (or jointpoint in aop language). The jointpoint can be constructor, method call, or field. You will need to declare any of your POJO to be "prepared" so that AOP framework knows to start intercepting either method, field, or constructor invocations using the dynamic Aop.

For PojoCache, we only allow all the fields (both read and write) to be intercepted. That is, we don't care for the method level interception since it is the state that we are interested in. So you should only need to change your POJO class name. For details of the pointcut language, please refer to JBossAop.

The standalone JBossCache distribution package provides an example declaration for the tutorial classes, namely, Person and Address . Detailed class declaration for Person and Address are provided in the Appendix section. But here is the snippet for META-INF/jboss-aop.xml :

<aop>
  <prepare expr="field(* org.jboss.test.cache.test.standAloneAop.Address->*)" />
  <prepare expr="field(* $instanceof{org.jboss.test.cache.test.standAloneAop.Person}->*)" />
</aop>

Detailed semantics of jboss-aop.xml can again be found in JBossAop. But above statements basically declare all field read and write operations in classes Address and Person will be "prepared" (or "aspectized"). Note that:

  • The wildcard at the end of the expression signifies all fields in the POJO
  • You can potentially replace specific class name with wildcard that includes all the POJOs inside the same package space
  • The instanceof operator declares any sub-type or sub-class of the specific POJO will also be "aspectized". For example, if a Student class is a subclass of Person , JBossAop will automatically instrument it as well!
  • We intercept the field of all access levels (i.e., private , protected , public , etc.) The main reason being that we consider all fields as stateful data. However, we can relax this requirement in the future if there is a use case for it.
  • We don't intercept field modifiers of static , final , and transient though. That is, field with these modifiers are not stored in cache and is not replicated either. If you don't want your field to be managed by the cache, you can declare them with these modifiers, e.g., transient.

7.2. Annotation

Annotation is a new feature in Java 5.0 that when declared can contain metadata at compile and run time. It is well suited for aop declaration since there will be no need for external metadata xml descriptor. In order to support Java 1.4 as well, JBossAop has also used a xdoclet type annotation syntax, and as a result will need an annotation pre-compiler.

Note that PojoCache starts to support the use of annotation since release 1.2.3 with Java 1.4, and release 1.3 with Java 5.0.

7.2.1. JDK1.4

In order to provide maximum transparency, we have created two aop marker interfaces: @@org.jboss.cache.aop.AopMarker and @@org.jboss.cache.aop.InstanceOfAopMarker. The first interface is used to declare a regular POJO, while the latter one is used to declare the POJO and its sub-classes or sub-types.

Finally, to support annotation (to simplify user's development effort), the JBossCache distribution ships with a jboss-aop.xml under the resources directory. When you use the annotation precompiler, this file needs to be in the class path. For example, please refer to the annoc Ant build target. For reference, here is the content of that jboss-aop.xml :

<aop>
  <prepare expr="field(* @@org.jboss.cache.aop.AopMarker->*)" />
  <prepare expr="field(* $instanceof{@@org.jboss.cache.aop.InstanceOfAopMarker}->*)" />
</aop>

Basically, it simply states that any annotation with both marker interfaces will be "aspectized" accordingly.

Here is two code snippets that illustrate the declaration of both types through 1.4 style annotation:

/**
* @@org.jboss.cache.aop.AopMarker
*/
public class Address {...}
The above declaration will instrument the class Address, and
/**
* @@org.jboss.cache.aop.InstanceOfAopMarker
*/
public class Person {...}
The above declaration will instrument the class Person and all of its sub-classes. That is, if Student sub-class from Personal, then it will get instrumented automatically without further annotation declaration.

Note that the simplicity of declaring POJOs through annotation and its marker interfaces (although you will need to use the annotation pre-compiler.) See the build.xml target annoc for example.

7.2.2. JDK5.0

The JDK5.0 annotation is similar to the JDK1.4 counterpart except the annotation names themselves are different, e.g., the two annotations are: @org.jboss.cache.aop.annotation.PojoCacheable and @org.jboss.cache.aop.annotation.InstanceOfPojoCacheable. For example, when using JDK5.0 annotation, instead of using @@org.jboss.cache.aop.AopMarker you will use @org.jboss.cache.aop.annotation.PojoCacheable instead. In the distribution, under examples/PojoCache/annotated50, there is an example of using JDK50 annotation. Note that we have decided to use different annotation naming convention between JDK1.4 and 5.0.

7.2.3. JDK5.0 field level annotations

In Release 1.4, we have added two additional field level annotations for customized behavior. The first one is @org.jboss.cache.aop.annotation.Transient. When applied to a field variable, it has the same effect as the Java language transient keyword. That is, PojoCache won't put this field into cache management (and therefore no replication).

The second one is @org.jboss.cache.aop.annotation.Serializable, when applied to a field varaiable, PojoCache will treat this variable as Serializable, even when it is PojoCacheable. However, the field will need to implement the Serializable interface such that it can be replicated.

Here is a code snippet that illustrates usage of these two annotations. Assuming that you have a Gadget class:

public class Gadget
{
   // resource won't be replicated
   @Transient Resource resource;
   // specialAddress is treated as a Serializable object but still has object relationship
   @Serializable SpecialAddress specialAddress;
   // other state variables
}

Then when we do:

   Gadget gadget = new Gadget();
   Resource resource = new Resouce();
   SepcialAddress specialAddress = new SpecialAddress();

   // setters
   gadget.setResource(resource);
   gadget.setSpecialAddress(specialAddress);

   cache1.putObject("/gadget", gadget); // put into PojoCache management

   Gadget g2 = (Gadget)cache2.getObject("/gadget"); // retrieve it from another cache instance
   g2.getResource(); // This is should be null becuase of @Transient tag so it is not replicated.

   SepcialAddress d2 = g2.getSpecialAddress();
   d2.setName("inet"); // This won't get replicated automatically because of @Serializable tag
   ge.setSpecialAddress(d2); // Now this will.

7.3. Weaving

As already mentioned, a user can use the aop precompiler (aopc) to precompile the POJO classes such that, during runtime, there is no additional system class loader needed. The precompiler will read in jboss-aop.xml and weave the POJO byte code at compile time. This is a convenient feature to make the aop less intrusive.

Below is an Ant snippet that defines the library needed for the various Ant targets that we are listing here. User can refer to the build.xml in the distribution for full details.

   <path id="aop.classpath"/>
       <fileset dir="${lib}"/>
          <include name="**/*.jar" //>
          <exclude name="**/jboss-cache.jar" //>
          <exclude name="**/j*unit.jar" //>
          <exclude name="**/bsh*.jar" //>
       </fileset/>
   </path/>

7.3.1. Ant target for running load-time instrumentation using specialized class loader

Below is the code snippet for the Ant target that is doing loadtime instrumentation through a special classloader. Basically, the Ant target one.test.aop using the bootclass loader generated by the Ant target generateClassLoader to run an individual test. Note that since JBossAop 1.3, a new GenerateInstrumentedClassLoader has been used since the previous SystemClassLoader is error prone.

   <target name="generateClassLoader" description=
           "Generate a new modified class loader so we can perform load time instrumentation">
      <property name="build.bootclasspath" value="${output}/gen-bootclasspath"/>
      <java classname="org.jboss.aop.hook.GenerateInstrumentedClassLoader">
         <classpath>
            <path refid="aop.classpath"/>
         </classpath>
         <arg value="${build.bootclasspath}"/>
      </java>
      <path id="bootclasspath">
         <pathelement location="${build.bootclasspath}"/>
         <path refid="aop.classpath"/>
      </path>
      <property name="bootclasspath" refid="bootclasspath"/>
   </target>

   <!-- eg. ant run.examples -Dtest=org.jboss.test.cache.test.local.NoTxUnitTestCase -->
   <target name="one.test.aop" depends="compile, generateClassLoader" description="run one junit test case.">
      <junit printsummary="yes" timeout="${junit.timeout}" fork="yes">
         <jvmarg value="-Djboss.aop.path=${output}/etc/META-INF/jboss-aop.xml"/>
         <jvmarg value="-Xbootclasspath/p:${bootclasspath}"/>
         <!-- jvmarg value="-Dbind.address=${bind.address}"/ -->
         <classpath path="${output}/etc" />
         <sysproperty key="log4j.configuration" value="file:${output}/etc/log4j.xml" />
         <classpath refid="lib.classpath"/>
         <classpath path="${build}"/>
         <formatter type="xml" usefile="true"/>
         <test name="${test}" todir="${reports}"/>
      </junit>
  </target>   

If you are running JDK5.0, you can also use the javaagent option that does not require a separate Classloader. Here are the ant snippet from one-test-aop50, for example.

   <target name="one.test.aop50" depends="compile, generateClassLoader" description="run one junit test case.">
     <junit printsummary="yes" timeout="${junit.timeout}" fork="yes">
        <jvmarg value="-Djboss.aop.path=${output}/resources/jboss-aop.xml"/>
        <jvmarg value="-javaagent:${lib-50}/jboss-aop-jdk50.jar"/>
        <classpath path="${output}/etc" />
        <sysproperty key="log4j.configuration" value="file:${output}/etc/log4j.xml" />
        <classpath refid="lib.classpath.50"/>
        <classpath refid="build.classpath.50"/>
        <formatter type="xml" usefile="true"/>
        <test name="${test}" todir="${reports}"/>
     </junit>
   </target>

7.3.2. Ant target for aopc

Below is the code snippet for the aopc Ant target. Running this target will do compile-time weaving of the POJO classes specified.

   <taskdef name="aopc" classname="org.jboss.aop.ant.AopC" classpathref="aop.classpath"/>
   <target name="aopc" depends="compile" description="Precompile aop class">
      <aopc compilerclasspathref="aop.classpath" verbose="true">
         <src path="${build}"/>
         <include name="org/jboss/cache/aop/test/**/*.class"/>
         <aoppath path="${output}/etc/META-INF/jboss-aop.xml"/>
         <classpath path="${build}"/>
         <classpath refid="lib.classpath"/>
      </aopc>
   </target>

Below is a snapshot of files that are generated when aopc is applied. Notice that couple extra classes have been generated because of aopc.

Classes generated after aopc

Figure 7.1. Classes generated after aopc

7.3.3. Ant target for annotation compiler

Below is the code snippet for the annoc Ant target. You run this step if you are using the JDK1.4 annotation. After this step is successfully run, you decide either to use compile-time or load-time mode of weaving.

   <!-- pre-compile directory with annotationc using jdk1.4 -->
   <target name="annoc" depends="compile" description="Annotation precompiler for aop class">
      <!-- Define a new ant target. This is the 1.4 annotation pre-compiler. After running this step,
           you still need to run the aopc step again, if you are not using system class loader.
      -->
       <taskdef name="annotationc" classname="org.jboss.aop.ant.AnnotationC" classpathref="aop.classpath"/>
       <annotationc compilerclasspathref="aop.classpath" bytecode="true">
          <classpath refid="lib.classpath"/>
          <classpath path="${build}"/>
          <!--System wide jboss-aop.xml is located here. -->
          <classpath path="${output.resources.dir}"/>
          <src path="${source}"/>
          <include name="org/jboss/cache/aop/test/**/*.java"/>
       </annotationc>
   </target>