Chapter 6. Instrumentation

In this chapter, we explain how to instrument (or "aspectize") the POJOs via JBoss Aop. There are two steps needed by JBoss Aop: 1) POJO declaration, 2) instrumentation. But depends on the instrumentation mode that you are using, you may not need to pre-process your POJO at all. That is, if you use JDK5.0 (required) and load-time mode, then all you need to do is annotating 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.

As for the second step, either we can ask JBoss Aop to do load-time (through a special class loader, so-called load-time mode) or compile-time instrumentation (use of an aopc pre-compiler, so-called precompiled mode). Reader can read the JBoss Aop introduction chapter for more details.

6.1. XML descriptor

To declare a POJO via XML configuration file, you will need a META-INF/jboss-aop.xml (or in the PojoCache case, it is the equivalent pojocache-service.xml file located under the class path or listed in the jboss.aop.path system property. JBoss AOP 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. JBoss Aop provides a so-called pointcut language where it consists of a regular expression set to specify the interception points (or jointpoint in aop parlance). 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 JBoss Aop.

The standalone JBoss Cache 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 pojocache-aop.xml :

<aop>
  <prepare expr="field(* $instanceof{@org.jboss.cache.pojo.annotation.Replicable}->*)" />
</aop>
and then notice the annotation @Replicable used in the Person and Address POJOs. Also note that @Replicable is now inheritant. For example, sub-class of Person such as Student will also be aspectized by JBoss Aop as well. If you want to stop this inheritance behavior, you can simply remove the $instanceof declaration in the prepare statement, e.g.,
            <aop>
              <prepare expr="field(* @org.jboss.cache.pojo.annotation.Replicable->*)" />
            </aop>
            

Detailed semantics of pojocache-aop.xml (or equivalently pojocache-aop.xml) can again be found in JBoss Aop. 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 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.

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

6.2.1. POJO annotation for instrumentation

To support annotation (in order to simplify user's development effort), the JBoss Cache distribution ships with a pojocache-aop.xml under the resources directory. For reference, here is annotation definition from pojocache-aop.xml again :

<aop>
  <prepare expr="field(* @org.jboss.cache.pojo.annotation.Replicable->*)" />
</aop>

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

Here is a code snippet that illustrate the declaration:

@org.jboss.cache.pojo.annotation.Replicable
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.

6.2.2. JDK5.0 field level annotations

In Release 2.0, we have added two additional field level annotations for customized behavior. The first one is @org.jboss.cache.pojo.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.pojo.annotation.Serializable, when applied to a field variable, PojoCache will treat this variable as Serializable, even when it is Replicable. 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 Resource();
   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 because 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.

6.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 pojocache-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/>

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

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

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

6.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}/resources/pojocache-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 6.1. Classes generated after aopc