Skip to main content

Implement Your Own Proxy-Based AOP Framework

November 1, 2005

{cs.r.title}









Contents
Creating a Proxy Factory
Adding an Interceptor
AOP Framework in Action
Alternative Implementation Using CGLIB
Conclusion
Resources

Aspect-oriented programming (AOP) is well-suited to managing
application crosscutting concerns, such as logging, security, and
transaction management. AOP provides a complement to object-oriented programming (OOP), which is still the most common and
powerful methodology to address core business concerns. AOP can
reduce code scattering, tangling, and duplication in applications.
Based on their implementation approaches, AOP frameworks can be
classified into two categories:

  1. Class-weaving-based, such as "http://eclipse.org/aspectj/">AspectJ and "http://www.jboss.org/products/aop">JBoss AOP. Core and
    crosscutting concerns are implemented independently. Class weaving
    is the process of integrating the concern implementations to form
    the final system. Weaving can be performed at compile, load, and
    run time. Both AspectJ and JBoss AOP are very powerful AOP
    implementations. They provide field interception, caller side
    interception, and constructor interception.
  2. Proxy-based, such as "http://www.springframework.org/">Spring AOP, "http://nanning.codehaus.org/">Nanning, and "https://dynaop.dev.java.net/">dynaop. With proxies, method
    invocations on an object can be intercepted to inject custom code.
    The aforementioned AOP frameworks use "http://java.sun.com/j2se/1.5.0/docs/guide/reflection/proxy.html">JDK
    dynamic proxy
    , "http://cglib.sourceforge.net/">CGLIB proxy, or both. Unlike
    the class-weaving-based ones, proxy-based AOP frameworks are simpler and
    often focus on method interception. Most of the time, Java
    developers use method interception only. Some proxy-based AOP
    implementations, such as Spring AOP, provide close integration
    with AspectJ to take advantage of its capabilities.

JDK dynamic proxy has been available since JDK 1.3. The proxy
class, which implements a list of interfaces specified at runtime,
is dynamically created by the JVM. Method invocations on the proxy
class are delegated to the underlying proxied object. JDK dynamic
proxy is simple to use, but, like all reflective code, it is
somewhat slower. For most situations, the overhead is not critical.
Another limitation is that it can only implement interfaces.

What if you want to proxy legacy classes that do not have
interfaces? You can use CGLIB. CGLIB is a powerful, high-performance code generation library. Under the cover, it uses
ASM, a small but fast
bytecode manipulation framework, to transform existing byte code to
generate new classes. CGLIB is faster than the JDK dynamic proxy
approach. Essentially, it dynamically generates a subclass to
override the non-final methods of the proxied class
and wires up hooks that call back to the user-defined interceptors.

To help you understand and demystify AOP, this article shows you
how to create a simple AOP framework using both JDK dynamic proxy
and CGLIB. This framework supports declarative transaction
management. This article uses Java 5 features, including
annotations and generics. Since JDK dynamic proxy is simpler, this
article starts with dynamic proxy.

Creating a Proxy Factory

A proxy factory is the central place to create proxies for the
requested target classes. Clients of proxies do not know how the
proxies are created.

[prettify]public interface DynamicProxyFactory{
    <T> T createProxy(Class<T> clazz,
        T target,
        AOPInterceptor interceptor);
}
[/prettify]

To create a dynamic proxy, you need a list of proxy interfaces
and a target object. There is a set of rules about the proxy
interfaces. You can look at the

"http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/Proxy.html">
java.lang.reflect.Proxy
documentation for details. For
simplicity, this article uses a single interface only. You can
ignore the interceptor argument for now; it will be discussed in
the next section.

[prettify]public <T> T createProxy(Class<T> clazz,
    T target, AOPInterceptor interceptor) {
    InvocationHandler handler =
        new DynamicProxyInvocationHandler(target,
            interceptor);

    return (T)Proxy.newProxyInstance(
        Thread.currentThread().getContextClassLoader(),
        new Class<?>[] {clazz},
        handler);
}
[/prettify]

The implementation of the proxy factory is simple. First, it
creates an instance of InvocationHandler, which is one
of the two key dynamic proxy APIs. Then, it uses the static method
Proxy.newProxyInstance to create a proxy that
implements the interface passed in as its second parameter. Note
that the second argument of the newProxyInstance method is an array of
Class<?> instead of Class<T>.
Arrays cannot be created if the element type is generic, but an
unbounded wildcard can be used.

All method invocations on the generated proxy class are
forwarded to InvocationHandler's single method:

[prettify]    public Object invoke(Object proxy,
        Method method, Object[] args)
[/prettify]

Let's see how this method is implemented in
DynamicProxyInvocationHandler.java:

[prettify]public class DynamicProxyInvocationHandler
        implements InvocationHandler {
    private Object target;
    private AOPInterceptor interceptor;

    public DynamicProxyInvocationHandler(Object target,
        AOPInterceptor interceptor)  {
        this.target = target;
        this.interceptor = interceptor;
    }

    public Object invoke(Object proxy, Method method,
        Object[] args) throws Throwable{
        try {
            interceptor.before(method, args);
            Object returnValue = method.invoke(target, args);
            interceptor.after(method, args);
            return returnValue;
        } catch(Throwable t) {
            interceptor.afterThrowing(method, args, t);
            throw t;
        } finally {
            interceptor.afterFinally(method, args);
        }
    }
}
[/prettify]

If you ignore the interceptor-related code, the implementation
of the invoke method is straightforward. It uses reflective invocation
on the Method object to delegate to the target
object.

Adding an Interceptor

As discussed in the preceding section, all method invocations on the
proxy class are forwarded to the invoke method of
InvocationHandler. The invoke method
delegates calls to the target object. Since all method
calls have to go through the single invoke method, you
can apply the decorator pattern on that method, or even immediately
return without further delegating to the target object. If you
decorate that method before delegating to the target object, you
are essentially applying AOP before advice. If you add custom code
after delegating to the target object, you are essentially applying
AOP after advice. If, instead of delegating to the target object,
you route method calls to a different path, you are applying
"around" advice. Now it should be easy for you to figure out what
afterThrowing and afterReturn advices
mean. For details on each kind of advice, you can refer to the book AspectJ in
Action
.

In this article, some method advices are grouped into the
AOPInterceptor. As its name implies, it intercepts
method invocations on the target object through decorating the
invoke method of InvocationHandler, as shown in the
DynamicProxyInvocationHandler class. The method
advices should be decoupled in the real world. Advices for
finally blocks are not common, but are added here for
demonstration purposes.

[prettify]public interface AOPInterceptor {
    void before(Method method, Object[] args);
    void after(Method method, Object[] args);
    void afterThrowing(Method method, Object[] args, Throwable throwable);
    void afterFinally(Method method, Object[] args);
}
[/prettify]

AOP Framework in Action

Typically, you do not want to intercept all of the method calls.
That is, advices are applied only to the methods or classes you are
interested in. You can set the classes and methods through regular
expressions in XML files, annotations, or other mechanisms. At
run time, your framework should be able to decide whether or not
to apply any advices by matching the current class and method with
those specified in your configuration files or annotations. Even
the runtime argument values for a method can be used to determine
which advice should be applied.

The TransactionAnnotation is a simple annotation,
as shown below:

[prettify]@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TransactionAnnotation {
    String value();
}
[/prettify]

The @Retention meta-annotation is set to
RetentionPolicy.RUNTIME so that it can be accessed via
reflection. You can attach annotations to methods only since the
@Target meta annotation is set to
ElementType.METHOD.

Assume you have a business service, called
PersistenceService. It requires a new transaction in
its save method, but transactions are not supported in
its load method.

[prettify]public interface PersistenceService {
    @TransactionAnnotation("REQUIRES_NEW")
    void save(long id, String data);

    @TransactionAnnotation("NOT_SUPPORTED")
    String load(long id);
}
[/prettify]

Now you need a transaction. Assume you have a transaction API
like this:

[prettify]public interface Transaction{
    void open();
    void rollBack();
    void commit();
    void closeIfStillOpen();
}
[/prettify]

The transaction starts by calling the open method
and must be closed after use. Here is the transaction interceptor
that performs declarative transaction management for
PersistenceService:

[prettify]public class TransactionInterceptor
        implements AOPInterceptor {
    private Transaction transaction;

    public void before(Method method, Object[] args) {
        if (isRequiresNew(method)) {
            transaction = new TransactionAdapter();
            transaction.open();
        }
    }

    public void after(Method method, Object[] args) {
        if (transaction != null) {
            transaction.commit();
        }
    }

    public void afterThrowing(Method method,
        Object[] args, Throwable t) {
        if (transaction != null) {
            transaction.rollBack();
        }
    }

    public void afterFinally(Method method, Object[] args) {
        if (transaction != null) {
            transaction.closeIfStillOpen();
        transaction = null;
        }
    }

    protected boolean isRequiresNew(Method method) {
        TransactionAnnotation transactionAnnotation =
            method.getAnnotation(TransactionAnnotation.class);

        if (transactionAnnotation != null) {
            if ("REQUIRES_NEW".equals(
                transactionAnnotation.value())){
            return true;
        }
        }

        return false;
    }
}
[/prettify]

Now you can plug in the transaction interceptor into a proxy
when the proxy is created.

[prettify]DynamicProxyFactory proxyFactory = new DynamicProxyFactoryImpl();
AOPInterceptor interceptor = new TransactionInterceptor();
PersistenceService proxy =
    proxyFactory.createProxy(PersistenceService.class,
        new PersistenceServiceImpl(),
        interceptor);
proxy.save(1, "Jason Zhicheng Li");
String data = proxy.load(1);
[/prettify]

You can run the manual test from the attached source code to see
the results. As annotated in the PersistenceService
interface, the save method is executed in a new
transaction context, but there is no transaction for the
load method.

Without much coding, you can externalize the proxy creation
through dependency injection. If you have experience in dependency
injection frameworks, like "http://www.springframework.org/">Spring, it should be familiar
to you. All you need to do is to configure interface type, target,
and interceptor in your proxy factory. You can even add a layer of
abstraction for the target by passing a target holder instance into
the proxy factory. The target holder has a reference to the real
target and it can be instantiated without a real target. Then you
can implement advanced features such as hot swapping or pooling of
real targets and virtual proxies.

Alternative Implementation Using CGLIB

Similar to InvocationHandler and Proxy
in dynamic proxy, there are two key APIs in CGLIB proxy,
MethodInterceptor and Enhancer. The
MethodInterceptor is the general callback interface
used by Enhancer, which dynamically generates
subclasses to override the non-final methods of the superclass.
MethodInterceptor is responsible for intercepting all
method calls in the generated proxy. You can invoke custom code
before and after the invocation of the super methods, and even skip
invocation of the super methods. Typically, a single callback is
used per enhanced class, but you can use
CallbackFilter to control which callback to use for a
method.

Let's first create a CGLIB MethodInterceptor.

[prettify]public class CGLIBMethodInterceptor
        implements MethodInterceptor {
    private AOPInterceptor interceptor;

    public CGLIBMethodInterceptor(AOPInterceptor interceptor) {
        this.interceptor = interceptor;
    }

    public Object intercept(Object object, Method method,
        Object[] args, MethodProxy methodProxy )
            throws Throwable {
        try {
            interceptor.before(method, args);
            Object returnValue =
                methodProxy.invokeSuper(object, args);
            interceptor.after(method, args);
            return returnValue;
        } catch(Throwable t) {
            interceptor.afterThrowing(method, args, t);
            throw t;
       } finally {
            interceptor.afterFinally(method, args);
       }
}
[/prettify]

The implementation is very similar to
DynamicProxyInvocationHandler in dynamic proxy, but
note that there is no target object and the type
T is the concrete class type, not the interface type
as in DynamicProxyFactory. The real method is invoked
by using MethodProxy, which is faster, instead of the
Method object. Now let's create the proxy factory:

[prettify]public class CGLIBProxyFactoryImpl
        implements CGLIBProxyFactory {

    public &lt;T&gt; T createProxy(Class&lt;T&gt; clazz,
            AOPInterceptor interceptor) {
        MethodInterceptor methodInterceptor =
            new CGLIBMethodInterceptor(interceptor);

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(methodInterceptor);
        return (T)enhancer.create();
    }
}
[/prettify]

After you set the superclass type and method interceptor, you
simply call the create() method on the
Enhancer object to create a proxy. Optionally, you can
configure CallbackFilter to map a method to a callback
by calling the setCallbackFilter(CallbackFilter) method.
In addition, you can specify the proxy class to implement a set of
interfaces. In this CGLIB implementation, since no interface is
specified, the transaction attributes must be declared in the
PersistenceService implementation instead of the
interface.

Similarly, you can implement interceptors to address logging,
validation, auditing, caching, and security, which are orthogonal to
core business concerns. As shown above, both dynamic proxy and
CGLIB implementation are simple to implement, but you must be aware
that important issues such as performance, exception handling, and
threading are not covered here.

Conclusion

The AOP implementation in this article is simplified for
clarity, but it shows you the essentials of proxy-based AOP
frameworks. AOP decouples crosscutting concerns, such as
the transaction management demonstrated in this article, from
application core concerns. With aspect-oriented design and
programming, you can significantly simplify your design and
implementation. In some cases, however, third-party AOP frameworks
cannot be used due to non-technical reasons, such as corporate
policies and license issues. As shown in this article, you can
implement your own AOP framework that is tailored to meet your
needs. JDK dynamic-proxy-based implementation is simpler, since it
uses standard Java. That means there are no third-party libraries
or build-time bytecode instrumentation. Alternatively, you can
choose CGLIB to proxy legacy classes and have better performance,
but you need to introduce multiple third-party libraries into your
system. At that moment, you should ask yourself if you need to pick
an available AOP framework, which is often more complete and
sophisticated than your roll-your-own AOP implementation.

Resources

Jason Zhicheng Li would like to thank Brian Paulsmeyer
and Mark Volkmann for their help in reviewing this
article.

width="1" height="1" border="0" alt=" " />
Jason Zhicheng Li is a senior software engineer with Object Computing, Inc. in St. Louis, MO.
Related Topics >> Programming   |