Support for Aspect-Oriented middleware is a key innovation in JBoss application server 4.0. It drastically simplifies the development of enterprise middleware applications. In this chapter, we first introduce the basic concepts of Aspect Oriented Programming (AOP). Then, we discuss how to configure AOP support inside the JBoss application server and how to deploy AOP-enabled applications in various scenarios.
AOP extends the traditional Object Oriented Programming (OOP) model to improve code reuse across different object hierarchies. The basic concept in AOP is an aspect, which is a common behavior that's typically scattered across methods, classes, object hierarchies, or even entire object models. For example, metrics is one common aspect. To generate useful logs from your application, you have to sprinkle informative messages throughout your code. However, metrics is something that your class or object model really shouldn't be concerned about. After all, metrics is irrelevant to your actual application: it doesn't represent a customer or an account, and it doesn't realize a business rule. It's an orthogonal behavior that requires duplicated code in traditional OOP systems.
In AOP, a feature like metrics is called a crosscutting concern, as it's a behavior that "cuts" across multiple points in your object models, yet is distinctly different. As a development methodology, AOP recommends that you abstract and encapsulate crosscutting concerns into aspects.
In the context of enterprise middleware, container managed services are crosscutting concerns. Once deployed, each J2EE component (e.g., a EJB or a servlet) automatically gets services, such as logging, security and transaction, from the container. Those services are orthogonal to the core business logic. The application developers could reuse those services without writing any code. The J2EE services have the basic characteristics of "aspects" as we discussed before. However, compared with a true AOP solution, the J2EE services model has a number of limitations.
The J2EE component classes must implement certain base classes or interfaces in the J2EE framework. It is impossible for components to inherit from each other. That makes it difficult to build a complex OOP system based on those components.
The J2EE infrastructure code and deployment descriptors are complex. They are designed for fully distributed services and prove to be an overkill for many smaller scale middleware applications.
You cannot extend the J2EE container for more services that are not shipped with the application server.
You cannot use J2EE services in standalone applications without the J2EE application server.
JBoss AOP helps you solve all the above problems. It works with plain old Java objects (POJOs) as opposed to pre-defined "components". JBoss AOP allows you to apply EJB-style services to POJOs without the complex EJB infrastructure code and deployment descriptors. You can develop new aspects and deploy them into the application server for all applications to use. That essentially extends the existing container services. JBoss AOP can also be used in standalone Java applications. A detailed introduction to aspect-oriented programming and the JBoss AOP framework can be found on JBoss web site.
In this section, let's first introduce some basic AOP concepts via some examples. These concepts and terms will be used frequently throughout the rest of this chapter.
A joinpoint is simply any point in your java program. The call of a method. The execution of a constructor, the access of a field are all joinpoints. The joinpoints are places where the aspects provide services to the object. Let's see an example POJO Foo.
public class Foo { public int fooField; public Foo () { fooField = 0; } public String fooMethod (int i) { return Integer.toString (fooField + i); } }
All the following actions on the Foo class and its instance objects are joinpoints.
Foo foo = new Foo (); int k = foo.fooField; String s = foo.fooMethod (0);
Inside the JBoss AOP, a joinpiont is encapsulated by an Invocation object at runtime. This object could contain information like which method is being called, the arguments of the method, etc. It is available to developers via the JBoss AOP framework API.
The AOP system dynamically modifies an object's behavior at joinpoints. The injected behavior, such as logging, security check, transaction etc., is specified in regular Java methods known as "advices". In the following example, the trace() method is an advice. When the execution flow reaches a joinpoint that is bound to the trace() advice (we will see how to specify the binding later), the JVM executes the trace() method instead of the joinpoint. Here, the trace() method prints out a message, instructs the AOP runtime to execute the joinpoint and then prints another message. The invocation object contains information about the joinpoint where this advice is applied. We can apply the advice to any joinpoint to log an event.
public class FooAspect { public Object trace (Invocation invocation) throws Throwable { try { System.out.println("Enter the joinpoint"); return invocation.invokeNext (); } finally { System.out.println("Leave the joinpoint"); } } }
In JBoss AOP, an Aspect class is a regular Java class that holds one or many advice methods. For example, the FooAspect class is an aspect. A special kind of aspect class in JBoss AOP is "interceptor". It must implement the Interceptor interface, which defines only one advice method invoke(). This interface helps developers to enforce compile time type check for the interceptor type of aspect classes.
The advice is bound to a specific set of joinpoints known as a pointcut. As a developer, you specify the mapping rules to group joinpoints into pointcuts using an expression language supported by JBoss AOP. There are three ways to specify the pointcut.
The following listing from the jboss-aop.xml descriptor specifies that the trace() advice is bound to the Foo.fooMethod() method call joinpoint.
<aop> <aspect class="FooAspect" scope="PER_VM"/> <bind pointcut="execution(public String Foo->fooMethod(..))"> <advice name="trace" aspect="FooAspect"/> </bind> </aop>
You can find a complete reference of the elements in the jboss-aop.xml file in the JBoss AOP manual.
If you do not wish to manage a separate jboss-aop.xml configuration file, you can declare the aspect and specify its bindings in the aspect class's source code using annotations. In JDK 5.0, annotation is an officially supported Java language feature. You can just use JBoss AOP defined annotations to tag your aspect class and advice methods.
@Aspect (scope = Scope.PER_VM) public class FooAspect { @Bind (pointcut="execution("* Foo->fooMethod())") public Object trace (Invocation invocation) throws Throwable { try { System.out.println("Enter the joinpoint"); return invocation.invokeNext (); } finally { System.out.println("Leave the joinpoint"); } } }
In JDK 1.4, however, annotation is not supported by the Java compiler. JBoss AOP allows you to embed the annotations in JavaDoc comments. You can use the JBoss annotation compiler to extract the annotation from the source code and then add them to the compiled bytecode files or store them in a separate XML file for further processing.
/** * @@Aspect (scope = Scope.PER_VM) */ public class FooAspect { /** * @@org.jboss.aop.Bind (pointcut="execution("* Foo->fooMethod())") */ public Object trace (Invocation invocation) throws Throwable { try { System.out.println("Enter the joinpoint"); return invocation.invokeNext (); } finally { System.out.println("Leave the joinpoint"); } } }
Annotation is easier to use than the jboss-aop.xml configuration file since it is closer to the source code it is supposed to control.
So far, you have seen how to bind advices to pointcuts using the signature pattern of the joinpoints (e.g., the method signature). A more general way to specify pointcuts is to directly tag the joinpoints in the application code using annotations. Then, in the jboss-aop.xml file, you can map the annotations to the advices. In the following jboss-aop.xml file, the trace() advice is mapped to the @FooTrace annotation tag.
<aop> <aspect class="FooAspect" scope="PER_VM"/> <bind pointcut="execution(* *->@org.jboss.FooTrace(..))"> <advice name="trace" aspect="FooAspect"/> </bind> </aop>
Here is the application code that makes use of the @FooTrace annotation in JDK 5.0.
public class Foo { public int fooField; public Foo () { fooField = 0; } @FooTrace public String fooMethod (int i) { return Integer.toString (fooField + i); } }
The version in JDK 1.4 with the JBoss annotation compiler is as follows.
public class Foo { public int fooField; public Foo () { fooField = 0; } /** * @@org.jboss.FooTrace */ public String fooMethod (int i) { return Integer.toString (fooField + i); } }
Notice that you do not need to annotate the aspect class anymore in this setup. The ability to specify pointcuts via annotations in the application code allows us to develop pre-packaged aspects and then publish the annotation API for all to use. That is exactly what JBoss did to support pre-packaged EJB-style AOP services inside the JBoss application server.
The aspects you have seen so far are "interceptor" type aspects. Another key AOP feature is "introduction" or "mixin" of classes from independent inheritance trees. An introduction modifies the type and structure of a Java class. It can be used to force an existing class to implement an interface using methods from another class. It essentially allows developers to create C++ style multiple inheritance object systems in Java. The following example shows that the methods in FooMixin class is used to make Foo class to implement the FooMixinInt interface at runtime. Here is the FooMixinInt interface.
public interface FooMixinInt { public String fooMethod2 (int i); }
The FooMixin class implements the FooMixinInt interface.
public class FooMixin implements FooMixinInt { public String fooMethod2 (int i) { return Integer.toString (fooField - i); } }
However, the Foo class does not implement the FooMixinInt interface. The following jboss-aop.xml file forces the Foo class to implement the FooMixinInt using the method from the FooMixin class.
<introduction class="Foo"> <mixin> <interfaces>FooMixinInt</interfaces> <class>FooMixin</class> <construction>new FooMixin(this)</construction> </mixin> </introduction>
Then, in the application code, you can cast a Foo instance to the FooMixinInt type at runtime.
Foo foo = new Foo (); FooMixinInt fooint = (FooMixinInt) foo; String s = foo.fooMethod2 (-2);
The fooMethod2() method, which is defined in FooMixin class but not in the Foo class, is now available in the Foo instance at the AOP runtime.
Building JBoss AOP applications is slightly different from building plain Java applications since the aspects and advices need to be instrumented into the compiled Java bytecode. For example, if an advice is bound to a method invocation joinpoint, the AOP instrumentation process would modify the joinpoint bytecode to call out to the advice method with the properly composed invocation object as an argument.
The first step is to compile the all classes, including the aspect classes, to bytecode using the regular javac utility. If you use JDK 5.0 and the J2SE 5.0 style annotation, the annotation is automatically compiled into the bytecode class files.
You can skip this step if you use Java annotations with the JDK 5.0. However, if you want to use JBoss AOP annotation with JDK 1.4, you have to embed the annotations in the JavaDoc comments. They are not processed by the javac compiler. You have to use a annotation compiler provided by JBoss AOP to process the source code. The annotation compiler can directly add annotation to the bytecode class files or generate the annotation data in a separate XML file called metadata.xml. The following example compiles the annotation in Foo.java file into the Foo.class file.
annotationc <classpath of the Foo.class file> -bytecode Foo.java
The following example compiles the annotation into a metadata.xml file in the current directory and does not alter the Foo.class file. The metadata.xml file can be used later in the AOP bytecode instrumentation process.
annotationc <classpath> -xml Foo.java
You can also run the annotation compiler within an ANT build script. The following example modifies the Java bytecode class files directly to add annotations.
<taskdef name="annotationc" classname="org.jboss.aop.ant.AnnotationC" classpathref="jboss.aop.classpath" /> <target name="annotate"> <annotationc compilerclasspathref="classpath" classpath="path/to/classfile" bytecode="true"> <src path="."/> </annotationc> </target>
In this book, we build applications using ANT. So, the ANT version of the annotation compiler is used.
The AOP instrumentation process modifies the Java bytecode to add runtime hooks around pointcuts. Those hooks collect reflection information and invoke advices. The aopc utility in JBoss AOP instruments the bytecode offline for JDK 1.4. If you use JDK 5.0, you have to replace all the aopc below to aopc15. It takes pointcuts definition from the jboss-aop.xml file or the metadata.xml file or the annotation tags already embedded in the bytecode by the annotation compiler. The following example shows how to invoke aopc from the command line with an associated jboss-aop.xml file.
aopc <classpath> -aoppath jboss-aop.xml Foo.class
If both jboss-aop.xml and metadata.xml files are present, you can put them in one directory and pass the directory name to aopc. In fact, all the *-aop.xml files in this directory will be treated as jboss-aop.xml by the aopc compiler.
aopc <classpath> -aoppath <directory to XML files> Foo.class
aopc <classpath> -aopclasspath <classpath to annotated aspect classes> Foo.class
The following example shows how to invoke aopc within an ANT build script.
<taskdef name="aopc" classname="org.jboss.aop.ant.AopC" classpathref="jboss.aop.classpath" /> <targer name="aopc"> <aopc compilerclasspathref="classpath" verbose="true"> <classpath path="${classes.dir}"/> <src path="${classes.dir}"/> <aoppath path="jboss-aop.xml"/> <aopclasspath path="aspects.jar"/> </aopc> </target>
The aopc instrumented bytecode can run directly in any JVM. Another option is to instrument the bytecode at the class load time. This way, you do not need to run the separate aopc program. In fact, if you use JDK 5.0 style annotations or do not use annotations, you do not even need to run the annotatec program. The JBoss application server can be configured to instrument AOP bytecode at class load time (see Section 12.4.2, “Configure the AOP service”).
JBoss AOP applications are supported in the standard and all configurations in JBoss AS 4.0.0 (the default and all configurations in JBoss AS 4.0.1). The jboss-aop.deployer service in the deploy directory deploys AOP applications inside the JBoss application server. The structure of the jboss-aop.deployer directory is shown in Figure 12.1, “The directory structure of the JBoss AOP deployer in JBoss AS.”. Please notice that the jboss-aop.deployer service archive in JBoss AS 4.0.0 is a zip file instead of a directory. It includes the following components:
Java libraries for the JBoss AOP framework including utilities to instrument AOP classes at load time.
A pre-packaged set of aspects, which are available in all applications deployed on the server. Those pre-packaged aspects handle crosscutting concerns, such as security, transaction and spawning asynchronous threads, for applications.
A default aspect binding configuration file base-aop.xml. It binds pre-packaged aspects to certain annotations and method signature patterns. So, in your applications, you can just use those pre-defined annotations and method signatures to take advantage of the pre-packaged aspects.
Since JBoss AOP is a fast evolving technology, it is probably a good idea to keep your jboss-aop.deployer service up-to-date with the latest JBoss AOP release. After you unzip the JBoss AOP release file, the jboss-40-install directory contains the jboss-aop.deployer archive for the JDK 1.4 and JDK 5.0 environment respectively. You can just copy those files to your JBoss server's deploy directory to replace the JBoss AOP service shipped with the application server.
By default, the jboss-aop.deployer service disables load-time bytecode instrumentation. You have to instrument your code offline with the aopc utility. We recommend you to enable load-time instrumentation by editing the META-INF/jboss-service.xml file in the jboss-aop.deployer directory. You can just change the EnableTransformer attribute value to true as follows.
<mbean code="org.jboss.aop.deployment.AspectManagerService" name="jboss.aop:service=AspectManager"> <attribute name="EnableTransformer">false</attribute> <!-- only relevant when EnableTransformer is true --> <attribute name="SuppressTransformationErrors">true</attribute> <!-- only relevant when Enabletransformer is true. Optimization is optional only just in case there is a bug in it --> <attribute name="Optimized">true</attribute> <attribute name="Verbose">false</attribute> </mbean> <mbean code="org.jboss.aop.deployment.AspectDeployer" name="jboss.aop:service=AspectDeployer"> </mbean>
Other attributes in the above mbean controls the behavior of the JBoss AOP deployer. Those attributes are manageable via the JBoss JMX console when the server is running.
The key value proposition of AOP is to promote code reuse. For example, we can reuse common aspects across different object trees in different applications.
Transaction: JBoss AOP allows you to declare transaction requirements and properties on any POJO method. You can inject the current transaction manager anywhere in the code via annotation and save the trouble to write lookup code.
Security: You can declare EJB-style security constraints on any POJO method to limit the access to them. We cover an security aspect example later in this chapter.
Observer: You can transparently add observers to any POJO via annotation. For example, the observer could log all the call invocations against methods in the object. The previously mentioned trace() example shows the observer in action. This aspect can be used outside of the JBoss AS in standalone applications.
Asynchronous method invocation: You can annotate a method to specify that it should be executed in a separate background thread. This aspect can be used outside of the JBoss AS in standalone applications. It is especially useful in Swing or SWT UI applications.
ThreadBased: Any field value tagged with the annotation defined by this aspect would behave as though its value were stored in a java.lang.ThreadLocal. This aspect can be used outside of the JBoss AS in standalone applications.
Read/Write Lock: It allows you to define at the method level a read/write lock using annotation The implementation is based on the concurrent package from Doug Lea. This aspect can be used outside of the JBoss AS in standalone applications.
The pre-packaged aspect library in JBoss AOP is still under development. For the most up-to-date documentation on the currently available aspects, please refer to the docs/aspect-library/index.html document.
In this section, we use an example application to cover the common scenarios of packaging and deploying AOP applications inside JBoss. The example is an online mortgage payment calculator application (see Figure 12.2, “The Mortgage Calculator sample application.”).
The servlet uses the Calculator class to calculate the monthly payment based on the loan principal, term and interest rate.
public class CalculatorServlet extends HttpServlet { private Calculator cal = null; public void init () throws ServletException { cal = new Calculator (); } public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); ServletOutputStream out = response.getOutputStream(); String para = null; int principal = 200000; double rate = 8.0; int term = 360; double payment = 0.; para = request.getParameter("principal"); if (para != null && para.length() > 0) { principal = Integer.parseInt(para); } para = request.getParameter("rate"); if (para != null && para.length() > 0) { rate = Double.parseDouble(para); } para = request.getParameter("term"); if (para != null && para.length() > 0) { term = Integer.parseInt(para); } try { payment = cal.getPayment(principal, rate, term); // Write out the response in HTML } catch (Throwable t) { // Write out the error message in HTML } } }
The Calculator class represents a plain old Java object (POJO).
public class Calculator { public Calculator () { } public double getPayment (int principal, double rate, int term) { rate = rate / 100.; rate = rate / 12.; double tmp = Math.pow(1.+rate, term); return (principal * tmp * rate) / (tmp - 1.); } }
In the world of J2EE containers, the Calculator POJO cannot make use of container services since it does not follow the pre-defined component models (e.g., it is not an EJB). But with JBoss AOP, we can apply EJB-style services as well as custom developed container services to the POJO at runtime. In the next two sections, we will show you how.
As we have discussed in Section 12.4.3, “Pre-packaged aspects library”, JBoss application server 4 comes with several pre-packaged aspects. One of the most useful aspects is the security aspect. It supports access control to any POJO methods, just as the EJB security service constrains access to EJB methods. In this example, we demonstrate how to apply the pre-packaged security aspect to the Calculator.getPayment() method in the above mortgage calculator example.
To develop applications using the pre-packaged aspects is easy. You can simply use the pre-defined annotation to mark up your source code and then use the annotation compiler to process the bytecode. In this example, the security domain is other indicating that the usernames, passwords and roles are stored in the users.properties and roles.properties files in the classpath. The security domain here has the same meaning as the security domain in EJB configuration file jboss.xml. Before the getPayment() method is invoked, the JBoss AOP security aspect transparently checks the user role based on the username obtained by the servlet in the web context. Only users with role Authorized can invoke the getPayment() method. The following is the annotated POJO code using the JDK 1.4 style annotation.
/** * @@org.jboss.aspects.security.SecurityDomain ("other") */ public class Calculator { /** * @@org.jboss.aspects.security.Unchecked */ public Calculator () { } /** * @@org.jboss.aspects.security.Permissions ({"Authorized"}) */ public double getPayment (int principal, double rate, int term) { rate = rate / 100.; rate = rate / 12.; double tmp = Math.pow(1.+rate, term); return (principal * tmp * rate) / (tmp - 1.); } }
The following is the POJO code with JDK 5.0 style annotation.
@SecurityDomain ("other") public class Calculator { @Unchecked public Calculator () { } @Permissions ({"Authorized"}) public double getPayment (int principal, double rate, int term) { rate = rate / 100.; rate = rate / 12.; double tmp = Math.pow(1.+rate, term); return (principal * tmp * rate) / (tmp - 1.); } }
No special packaging is required for deploying this application. You can just package the annotation-enhanced class files as regular Java classes in your .war or .ear applications. Figure 12.3, “Users with inadequate security roles are detected and rejected by the pre-packaged security aspect.” shows that the server refuses to invoke the Calculator.getPayment() method when the current user does not have the required Authorized role.
Figure 12.3. Users with inadequate security roles are detected and rejected by the pre-packaged security aspect.
Notice that your application does not have a jboss-aop.xml file since the default annotation tags and bindings are already defined in the base-aop.xml file that comes within the jboss-aop.deployer package. The down side is that you cannot easily instrument your bytecode outside the server container. The easiest way to deploy this application is to enable the load-time AOP instrumentation in the server (see section Section 12.4.2, “Configure the AOP service”).
In addition to the pre-packaged aspect services, JBoss AOP allows you to develop your own aspects to extend the AOP container services. In the following example, we show how to develop an aspect to limit the number of times the user can invoke certain POJO methods. When this aspect is applied to the Calculator object, you can make the mortgage calculator stop working after certain number of queries (i.e., put the calculator in the "trial software" mode).
As we have discussed, the JBoss AOP aspect class itself is just a simple Java class. The advice method takes the Invocation object from the joinpoint as argument and checks how many times it has been invoked. If it has been invoked for more than 5 times, the aspect will stop the invocation and throw an exception.
package com.jboss.aspect; import org.jboss.aop.joinpoint.Invocation; public class TrialLimitAspect { private static int count = 0; public Object checkLimit (Invocation invocation) throws Throwable { System.out.println("Check whether the trial limit is reached"); count++; if (count < 5) { return invocation.invokeNext(); } else { throw new Exception("Hit the maximum access count"); } } }
Following the example of pre-packaged aspects, the easiest way to bind the custom aspect to applications is to use annotations. You can define custom annotations for this aspect and then publish them as part of the service API. In JBoss AOP, each annotation is a Java interface. The following TrialLimit interface defines the TrialLimit annotation tag.
package com.jboss.aspect; public interface TrialLimit { }
In the jboss-aop.xml file, you can specify the binding between the annotation tag and the advice method.
<aop> <aspect class="com.jboss.aspect.TrialLimitAspect" scope="PER_VM"/> <bind pointcut="execution(* *->@com.jboss.aspect.TrialLimit(..))"> <advice name="checkLimit" aspect="com.jboss.aspect.TrialLimitAspect"/> </bind> </aop>
Finally, in the application code, you just need to tag your methods, which need invocation limit control, with the annotation as you did with the pre-packaged aspects. Notice the second tag on the getPayment() method.
/** * @@org.jboss.aspects.security.SecurityDomain ("other") */ public class Calculator { /** * @@org.jboss.aspects.security.Unchecked */ public Calculator () { } /** * @@org.jboss.aspects.security.Permissions ({"Authorized"}) * @@com.jboss.aspect.TrialLimit */ public double getPayment (int principal, double rate, int term) { rate = rate / 100.; rate = rate / 12.; double tmp = Math.pow(1.+rate, term); return (principal * tmp * rate) / (tmp - 1.); } }
Figure 12.4, “The custom aspect detects that the user has reached the invocation limit for this POJO method.” shows the servlet displaying an error message once the invocation limit has been reached.
In order to deploy the TrialLimitAspect aspect or any other custom aspects, you have to package them properly so that the JBoss application server can recognize them as aspect libraries. You have the following two options.
First, you can package the aspect classes, annotation interfaces and configuration files together in a JAR archive file with the file name extension .aop. The jboss-aop.xml and metadata.xml files must reside in the META-INF directory in the .aop JAR archive. In this example, you can bundle the .aop file with the .war in the same .jar repository (see Figure 12.5, “Package the aspect library, binding configuration file and web application into one JAR file for easy deployment.”), and then deploy the .jar file as a single application. Or, you can deploy them side-by-side on the server. If you use custom aspects in EJB applications, you can include the .aop file directly inside your .ear file.
Second, you can simply package the aspect classes and annotation interfaces in a .jar file and then specify the binding in a *-aop.xml file. You can copy the .jar file and the *-aop.xml files into the deploy directory (see Figure 12.6, “The aspect library, binding configuration file and web application are deployed separately.”). The aspects and their bindings become available to all applications deployed in this server.
Figure 12.5. Package the aspect library, binding configuration file and web application into one JAR file for easy deployment.
Figure 12.6. The aspect library, binding configuration file and web application are deployed separately.
The example application for this chapter uses the first approach and build a TrialLimitAspect.aop and a MortgageCalculator.war file side-by-side. All other applications deployed in the server can make use of the aspect in the TrialLimitAspect.aop file as well.