Create Proxies Dynamically Using CGLIB Library
来源:互联网 发布:宋承宪 刘亦菲 知乎 编辑:程序博客网 时间:2024/06/16 04:05
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
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 concrete classes are illustrated in Figure 2.Thenet.sf.cglib.proxy.Callback
interface is a marker interface. All callback interfaces used by thenet.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 thejava.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
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 asnet.sf.cglib.proxy.LazyLoader
, but theloadObject
method is always called when a proxy method is invoked.net.sf.cglib.proxy.ProxyRefDispatcher
It is the same asDispatcher
, but it allows the proxy object to be passed in as an argument of theloadObject
method.
A callback is often used for all methods in the proxy class, as shown in Figure 3, but you can usenet.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 theinvoke
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-inNoOp
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 singlenet.sf.cglib.proxy.Callback
is configured for thenet.sf.cglib.proxy.Enhancer
class. It can be seen it is fairly straightforward to create a simple proxy. Instead of creating a new instance ofnet.sf.cglib.proxy.Enhancer
, you can simply use the static helper methods in thenet.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 thenet.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 ofnet.sf.cglib.proxy.Enhancer.create(),
thenet.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 customnet.sf.cglib.proxy.MethodInterceptor
. All the method invocations on the proxy are dispatched to the singleintercept
method of net.sf.cglib.proxy.MethodInterceptor
. Theintercept
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. TheAuthorization.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 simple MethodInterceptor 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 a AuthorizationInterceptor 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 through MethodProxy.
* @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 theintercept
method invokes the original method on the target object. For performance reasons, the original method is invoked by using the CGLIBnet.sf.cglib.proxy.MethodProxy
object instead of normal reflection using thejava.lang.reflect.Method
object.
Use a CallbackFilter
A net.sf.cglib.proxy.CallbackFilter
allows you to set callbacks at the method level. Assume you have aPersistenceServiceImpl
class, which has two methods:save
andload
. Thesave
method requires an authorization check, but theload
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. Thenet.sf.cglib.proxy.CallbackFilter implementation forPersistenceServiceImpl is as follows:
package com.lizjason.cglibproxy.impl;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.CallbackFilter;
/**
* An implementation of CallbackFilter for PersistenceServiceImpl
*
* @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 thePersistenceServiceImpl 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 thesave
method and theNoOp.INSTANCE
to theload
method. Optionally, you can specify the interfaces that the proxy object is to implement through thenet.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 throughnet.sf.cglib.proxy.Enhancer.setCallbackTypes(Class[])
method. The callback types are useful when you do not havean array of actual callback instances during proxy creation. Like callbacks, you need to usenet.sf.cglib.proxy.CallbackFilter
to specify the callback type index for each method. You can download complete source code fromhttp://www.lizjason.com/downloads/ for exmaples 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
- Complete source code for this article - http://www.lizjason.com/downloads/
- CGLIB library - http://cglib.sourceforge.net
- Spring Framework - http://www.springframework.org
- JDK dynamic proxy - http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/Proxy.html
- EasyMock - http://www.easymock.org
- jMock - http://www.jmock.org
- dynaop - http://dynaop.dev.java.net
- A good introduction to ASM by Eugene Kuleshov - http://www.onjava.com/lpt/a/5250
Jason Zhicheng Li would like to thank Paul Jensen and Tom Wheeler for reviewing this article.
- Create Proxies Dynamically Using CGLIB Library
- CodeSnip: How to Create a BoundField Dynamically Using GridView
- Create class type dynamically
- Dynamically Create Chart Sample
- Create C++ Object Dynamically
- how to create proxies pool
- Create menu by javascript dynamically
- Use javascript create checkbox dynamically
- Dancing Rectangles: Using GAPI to Create a Managed Graphics Library
- Scrapy设置之Using proxies and crawlers
- Spring 创建 JDK- and CGLIB-based proxies的区别
- create enum type dynamically(in runtime)
- How to create short-cut dynamically
- Create UPD socket (dynamically allocated port number)
- Using XML To Dynamically Generate GUI Elements
- How to create an automation project using MFC and a type library
- 转载:How to create an automation project using MFC and a type library
- cglib(Code Generation Library)
- [技术分享 - TMG 篇] TMG URL 过滤功能出现故障
- javascript document.write()覆盖原文档的问题
- ORACLE空值漫谈2
- ural 1010. Discrete Function
- Java设计模式学习心得
- Create Proxies Dynamically Using CGLIB Library
- 防止进程被杀
- 一些软件的环境变量配置(java, ant, tmocat,maven)
- 经典SQL语句大全
- 介绍J2ME编程中的几个重要概念
- mysql-5.5.15 安装
- 细说Singleton模式:创建、多线程与销毁
- J2EE开发应遵循的原则介绍
- linux常用c函数 字符串转换篇