Create Proxies Dynamically Using CGLIB Library

Create Proxies Dynamically Using CGLIB Library

By Jason Zhicheng Li, OCI Senior Software Engineer

November 2005


Introduction to CGLIB Library

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

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.

CGLIB Proxy APIs

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:

net.sf.cglib.core
Low-level bytecode manipulation classes; Most of them are related to ASM.
net.sf.cglib.transform
Classes for class file transformations at runtime or build time
net.sf.cglib.proxy
Classes for proxy creation and method interceptions
net.sf.cglib.reflect
Classes for a faster reflection and C#-style delegates
net.sf.cglib.util
Collection sorting utilities
net.sf.cglib.beans
JavaBean related utilities

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

CGLIB Method Interceptor

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
It is useful to force a particular method to return a fixed value for performance reasons.
net.sf.cglib.proxy.NoOp
It delegates method invocations directly to the default implementations in the super class.
net.sf.cglib.proxy.LazyLoader
It is useful when the real object needs to be lazily loaded. Once the real object is loaded, it is used for every future method call to the proxy instance.
net.sf.cglib.proxy.Dispatcher
It has the same signatures as net.sf.cglib.proxy.LazyLoader, but the loadObject method is always called when a proxy method is invoked.
net.sf.cglib.proxy.ProxyRefDispatcher
It is the same as 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.

Create a Simple Proxy

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:

  1. /**
  2.  * Create a proxy using NoOp callback. The target class
  3.  * must have a default zero-argument constructor.
  4.  *
  5.  * @param targetClass the super class of the proxy
  6.  * @return a new proxy for a target class instance
  7.  */
  8. public Object createProxy(Class targetClass) {
  9. Enhancer enhancer = new Enhancer();
  10. enhancer.setSuperclass(targetClass);
  11. enhancer.setCallback(NoOp.INSTANCE);
  12. return enhancer.create();
  13. }

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.

Use a MethodInterceptor

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:

  1. package com.lizjason.cglibproxy;
  2.  
  3. import java.lang.reflect.Method;
  4.  
  5. /**
  6.  * A simple authorization service for illustration purpose.
  7.  *
  8.  * @author Jason Zhicheng Li (jason@lizjason.com)
  9.  */
  10. public interface AuthorizationService {
  11. /**
  12.   * Authorization check for a method call. An AuthorizationException
  13.   * will be thrown if the check fails.
  14.   */
  15. void authorize(Method method);
  16. }

The implementation of net.sf.cglib.proxy.MethodInterceptor is as follows:

  1. package com.lizjason.cglibproxy.impl;
  2.  
  3. import java.lang.reflect.Method;
  4. import net.sf.cglib.proxy.MethodInterceptor;
  5. import net.sf.cglib.proxy.MethodProxy;
  6. import com.lizjason.cglibproxy.AuthorizationService;
  7.  
  8. /**
  9.  * A simple MethodInterceptor implementation to
  10.  * apply authorization checks for proxy method calls.
  11.  *
  12.  * @author Jason Zhicheng Li (jason@lizjason.com)
  13.  *
  14.  */
  15. public class AuthorizationInterceptor implements MethodInterceptor {
  16. private AuthorizationService authorizationService;
  17.  
  18. /**
  19.   * Create a AuthorizationInterceptor with the given
  20.   * AuthorizationService
  21.   */
  22. public AuthorizationInterceptor (AuthorizationService authorizationService) {
  23. this.authorizationService = authorizationService;
  24. }
  25.  
  26. /**
  27.   * Intercept the proxy method invocations to inject authorization check.
  28.   * The original method is invoked through MethodProxy.
  29.   * @param object the proxy object
  30.   * @param method intercepted Method
  31.   * @param args arguments of the method
  32.   * @param proxy the proxy used to invoke the original method
  33.   * @throws Throwable any exception may be thrown; if so, super method will not be invoked
  34.   * @return any value compatible with the signature of the proxied method.
  35.   */
  36. public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy ) throws Throwable {
  37. if (authorizationService != null) {
  38. //may throw an AuthorizationException if authorization failed
  39. authorizationService.authorize(method);
  40. }
  41. return methodProxy.invokeSuper(object, args);
  42. }
  43. }

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.

Use a CallbackFilter

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.

  1. package com.lizjason.cglibproxy.impl;
  2.  
  3. import com.lizjason.cglibproxy.PersistenceService;
  4.  
  5. /**
  6.  * A simple implementation of PersistenceService interface
  7.  *
  8.  * @author Jason Zhicheng Li (jason@lizjason.com)
  9.  */
  10. public class PersistenceServiceImpl implements PersistenceService {
  11.  
  12. public void save(long id, String data) {
  13. System.out.println(data + " has been saved successfully.");
  14. }
  15.  
  16. public String load(long id) {
  17. return "Jason Zhicheng Li";
  18. }
  19. }

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:

  1. package com.lizjason.cglibproxy.impl;
  2.  
  3. import java.lang.reflect.Method;
  4. import net.sf.cglib.proxy.CallbackFilter;
  5.  
  6. /**
  7.  * An implementation of CallbackFilter for PersistenceServiceImpl
  8.  *
  9.  * @author Jason Zhicheng Li (jason@lizjason.com)
  10.  */
  11. public class PersistenceServiceCallbackFilter implements CallbackFilter {
  12.  
  13. //callback index for save method
  14. private static final int SAVE = 0;
  15.  
  16. //callback index for load method
  17. private static final int LOAD = 1;
  18.  
  19. /**
  20.   * Specify which callback to use for the method being invoked.
  21.   * @method the method being invoked.
  22.   * @return the callback index in the callback array for this method
  23.   */
  24. public int accept(Method method) {
  25. String name = method.getName();
  26. if ("save".equals(name)) {
  27. return SAVE;
  28. }
  29. // for other methods, including the load method, use the
  30. // second callback
  31. return LOAD;
  32. }
  33. }

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:

  1. ...
  2. Enhancer enhancer = new Enhancer();
  3. enhancer.setSuperclass(PersistenceServiceImpl.class);
  4.  
  5. CallbackFilter callbackFilter = new PersistenceServiceCallbackFilter();
  6. enhancer.setCallbackFilter(callbackFilter);
  7.  
  8. AuthorizationService authorizationService = ...
  9. Callback saveCallback = new AuthorizationInterceptor(authorizationService);
  10. Callback loadCallback = NoOp.INSTANCE;
  11. Callback[] callbacks = new Callback[]{saveCallback, loadCallback };
  12. enhancer.setCallbacks(callbacks);
  13. ...
  14. 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 examples of setting callback types and interfaces.

Summary

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.

References

Jason Zhicheng Li would like to thank Paul Jensen and Tom Wheeler for reviewing this article.

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret