A proxy provides a surrogate or place holder for the target object to control access to it. It introduces a level of indirection when accessing an object. The JDK dynamic proxy, which has been available since JDK 1.3, is often used to create proxies dynamically. The JDK dynamic proxy is simple to use, but the JDK dynamic proxy approach requires the target objects implement one or more interfaces. What if you want to proxy legacy classes that do not have interfaces? You can use the CGLIB library.
CGLIB is a powerful, high performance code generation library. It is widely used behind the scenes in proxy-based Aspect Oriented Programming (AOP) frameworks, such as Spring AOP and dynaop, to provide method interceptions. Hibernate, the most popular object-relational mapping tool, also uses the CGLIB library to proxy single-ended (many-to-one and one-to-one) associations (not lazy fetching for collections, which is implemented using a different mechanism). EasyMock and jMock are libraries for testing Java code using mock objects. Both of them use the CGLIB library to create mock objects for classes that do not have interfaces.
Under the covers, the CGLIB library uses ASM, a small but fast bytecode manipulation framework, to transform existing bytecode and generates new classes. In addition to the CGLIB library, scripting languages, such as Groovy and BeanShell, also use ASM to generate Java bytecode. ASM uses a SAX parser like mechanism to achieve high performance. Using ASM directly is not encouraged because it requires good knowledge of the JVM, including class file format and the instruction set.
Figure 1: CGLIB Library and ASM Bytecode Framework
Figure 1 shows the relationship among the CGLIB library related frameworks and languages. Note that some frameworks, such as Spring AOP and Hibernate, often use both the CGLIB library and the JDK dynamic proxy to meet their needs. Hibernate uses the JDK dynamic proxy to implement a transaction manager adapter for the WebShere application server; Spring AOP, by default, uses the JDK dynamic proxy to proxy interfaces unless you force the use of the CGLIB proxy.
The CGLIB library code base is small, but it is difficult to learn due to lack of documentation. The current version (2.1.2) of the CGLIB library is organized as follows:
To create proxies dynamically, most of the time, you only need to deal with a few APIs in the proxy package.
As discussed in preceding section, the CGLIB library is a high-level layer on top of ASM. It is very useful for proxying classes that do not implement interfaces. Essentially, it dynamically generates a subclass to override the non-final methods of the proxied class and wires up hooks that call back to user-defined interceptors. It is faster than the JDK dynamic proxy approach.
Figure 2: CGLIB library APIs commonly used for proxying classes
CGLIB library APIs commonly used for proxying concrete classes are illustrated in Figure 2. The net.sf.cglib.proxy.Callback
interface is a marker interface. All callback interfaces used by the net.sf.cglib.proxy.Enhancer
class
extend this interface.
The net.sf.cglib.proxy.MethodInterceptor
is the most general callback type. It is often used in
proxy-based AOP implementations to intercept method invocations. This interface has a single method:
public Object intercept(Object object, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable;
When net.sf.cglib.proxy.MethodInterceptor
is the callback for all methods of a proxy, method
invocations on the proxy are routed to this method before invoking the methods on the original object. It is
illustrated in Figure 3. The first argument is the proxy object. The second and third arguments are the method being
intercepted and the method arguments, respectively. The original method may either be invoked by normal reflection
using the java.lang.reflect.Method
object or by using the net.sf.cglib.proxy.MethodProxy
object. net.sf.cglib.proxy.MethodProxy
is usually preferred because it is faster. In this method,
custom code can be injected before or after invoking the original methods.
Figure 3: CGLIB MethodInterceptor
net.sf.cglib.proxy.MethodInterceptor
meets any interception needs, but it may be overkill for some
situations. For simplicity and performance, additional specialized callback types are offered out of the box. For
examples,
net.sf.cglib.proxy.FixedValue
net.sf.cglib.proxy.NoOp
net.sf.cglib.proxy.LazyLoader
net.sf.cglib.proxy.Dispatcher
net.sf.cglib.proxy.LazyLoader
, but the loadObject
method
is always called when a proxy method is invoked.
net.sf.cglib.proxy.ProxyRefDispatcher
Dispatcher
, but it allows the proxy object to be passed in as an argument of the
loadObject
method.
A callback is often used for all methods in the proxy class, as shown in Figure 3, but you can use net.sf.cglib.proxy.CallbackFilter
to selectively apply callbacks on the methods. This fine-grained control feature is not available in the JDK dynamic
proxy, where the invoke
method of java.lang.reflect.InvocationHandler
applies to all the
methods of the proxied object.
In addition to proxying classes, CGLIB can proxy interfaces by providing a drop-in replacement for java.lang.reflect.Proxy to support Java proxying prior to JDK 1.3. Since this replacement proxy capability is rarely used, the related proxy APIs are not covered here.
The proxy package also provides support for net.sf.cglib.proxy.Mixin
. Basically, it allows multiple
objects to be combined into a single larger object. The method invocations on the proxy are delegated to the
underlying objects.
Let's see how to create proxies using CGLIB proxy APIs.
The core of the CGLIB proxying is the net.sf.cglib.proxy.Enhancer
class. To create a CGLIB proxy, at
the minimum, you need a class. Let's use the built-in NoOp
callback first:
/**
* Create a proxy using NoOp
callback. The target class
* must have a default zero-argument constructor.
*
* @param targetClass the super class of the proxy
* @return a new proxy for a target class instance
*/
public Object createProxy(Class targetClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(NoOp.INSTANCE);
return enhancer.create();
}
The return value is a proxy for an instance of the target class. In this example, a single net.sf.cglib.proxy.Callback
is configured for the net.sf.cglib.proxy.Enhancer
class. It can be seen it is fairly straightforward to
create a simple proxy. Instead of creating a new instance of net.sf.cglib.proxy.Enhancer
, you can
simply use the static helper methods in the net.sf.cglib.proxy.Enhancer
class to create proxies. It is
preferred to use the approach shown in the above example because it allows you to configure the net.sf.cglib.proxy.Enhancer
instance to fine control the generated proxies.
Note that the target class is passed in as the super class of the generated proxy. Unlike the JDK dynamic proxy, you
cannot pass in the target object during the proxy creation. The target object must be created by the CGLIB library.
In this example, the default zero-argument constructor is used to create the target instance. If you want the CGLIB
to create an instance with some arguments, instead of net.sf.cglib.proxy.Enhancer.create(),
the net.sf.cglib.proxy.Enhancer.create(Class[],
Object[])
method should be used. The first argument specifies argument types and second argument values.
Primitive types are wrapped in the arguments.
To make the proxy more useful, you can replace the net.sf.cglib.proxy.NoOp
callback with a custom
net.sf.cglib.proxy.MethodInterceptor
. All the method invocations on the proxy are dispatched to the
single intercept
method of net.sf.cglib.proxy.MethodInterceptor
. The
intercept
method then delegates the invocations to the underlying object.
Assume you want to apply authorization check for all the method calls of the target object. If authorization fails,
a runtime exception, AuthorizationException
, will be thrown. The Authorization.java
interface is listed below:
package com.lizjason.cglibproxy;
import java.lang.reflect.Method;
/**
* A simple authorization service for illustration purpose.
*
* @author Jason Zhicheng Li (jason@lizjason.com)
*/
public interface AuthorizationService {
/**
* Authorization check for a method call. An AuthorizationException
* will be thrown if the check fails.
*/
void authorize(Method method);
}
The implementation of net.sf.cglib.proxy.MethodInterceptor
is as follows:
package com.lizjason.cglibproxy.impl; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import com.lizjason.cglibproxy.AuthorizationService; /** * A simpleMethodInterceptor
implementation to * apply authorization checks for proxy method calls. * * @author Jason Zhicheng Li (jason@lizjason.com) * */ public class AuthorizationInterceptor implements MethodInterceptor { private AuthorizationService authorizationService; /** * Create aAuthorizationInterceptor
with the given *AuthorizationService
*/ public AuthorizationInterceptor (AuthorizationService authorizationService) { this.authorizationService = authorizationService; } /** * Intercept the proxy method invocations to inject authorization check. * The original method is invoked throughMethodProxy
. * @param object the proxy object * @param method intercepted Method * @param args arguments of the method * @param proxy the proxy used to invoke the original method * @throws Throwable any exception may be thrown; if so, super method will not be invoked * @return any value compatible with the signature of the proxied method. */ public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy ) throws Throwable { if (authorizationService != null) { //may throw an AuthorizationException if authorization failed authorizationService.authorize(method); } return methodProxy.invokeSuper(object, args); } }
In the intercept
method, the authorization is checked first. If authorization passes, then the intercept
method invokes the original method on the target object. For performance reasons, the original method is invoked by
using the CGLIB net.sf.cglib.proxy.MethodProxy
object instead of normal reflection using the java.lang.reflect.Method
object.
A net.sf.cglib.proxy.CallbackFilter
allows you to set callbacks at the method level. Assume you have a
PersistenceServiceImpl
class, which has two methods: save
and load
. The
save
method requires an authorization check, but the load
method does not.
package com.lizjason.cglibproxy.impl;
import com.lizjason.cglibproxy.PersistenceService;
/**
* A simple implementation of PersistenceService
interface
*
* @author Jason Zhicheng Li (jason@lizjason.com)
*/
public class PersistenceServiceImpl implements PersistenceService {
public void save(long id, String data) {
System.out.println(data + " has been saved successfully.");
}
public String load(long id) {
return "Jason Zhicheng Li";
}
}
Note that PersistenceServiceImpl
class implements PersistenceService
interface, but this
is not required to generate proxies using CGLIB. The net.sf.cglib.proxy.CallbackFilter
implementation
for PersistenceServiceImpl
is as follows:
package com.lizjason.cglibproxy.impl; import java.lang.reflect.Method; import net.sf.cglib.proxy.CallbackFilter; /** * An implementation ofCallbackFilter
forPersistenceServiceImpl
* * @author Jason Zhicheng Li (jason@lizjason.com) */ public class PersistenceServiceCallbackFilter implements CallbackFilter { //callback index for save method private static final int SAVE = 0; //callback index for load method private static final int LOAD = 1; /** * Specify which callback to use for the method being invoked. * @method the method being invoked. * @return the callback index in the callback array for this method */ public int accept(Method method) { String name = method.getName(); if ("save".equals(name)) { return SAVE; } // for other methods, including the load method, use the // second callback return LOAD; } }
The accept
method maps proxy methods to callbacks. The return value is the index in the callback array
for the particular method. Here is the proxy creation implementation for the PersistenceServiceImpl
class:
... Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(PersistenceServiceImpl.class); CallbackFilter callbackFilter = new PersistenceServiceCallbackFilter(); enhancer.setCallbackFilter(callbackFilter); AuthorizationService authorizationService = ... Callback saveCallback = new AuthorizationInterceptor(authorizationService); Callback loadCallback = NoOp.INSTANCE; Callback[] callbacks = new Callback[]{saveCallback, loadCallback }; enhancer.setCallbacks(callbacks); ... return (PersistenceServiceImpl)enhancer.create();
In this example, the AuthorizationInterceptor
instance applies to the save
method and the
NoOp.INSTANCE
to the load
method. Optionally, you can specify the interfaces that the
proxy object is to implement through the net.sf.cglib.proxy.Enhancer.setInterfaces(Class[])
method.
In addition to setting an array of callbacks to the net.sf.cglib.proxy.Enhancer
, you can specify an
array of callback types through net.sf.cglib.proxy.Enhancer.setCallbackTypes(Class[])
method. The
callback types are useful when you do not have
an array of actual callback instances during proxy creation. Like callbacks, you need to use net.sf.cglib.proxy.CallbackFilter
to specify the callback type index for each method. You can download complete source code from http://www.lizjason.com/downloads/ for exmaples of
setting callback types and interfaces.
CGLIB is a powerful, high performance code generation library. It is complementary to the JDK dynamic proxy in that it provides proxying classes that do not implement interfaces. Under the covers, it uses ASM bytecode manipulation framework. Essentially, CGLIB dynamically generates a subclass to override the non-final methods of the proxied class. It is faster than the JDK dynamic proxy approach, which uses Java reflection. CGLIB cannot proxy a final class or a class with any final methods. For general cases, you use the JDK dynamic proxy approach to create proxies. When the interfaces are not available or the performance is an issue, CGLIB is a good alternative.
Jason Zhicheng Li would like to thank Paul Jensen and Tom Wheeler for reviewing this article.