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. 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
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 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:
/**
* 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.
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:
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.
Use a CallbackFilter
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.
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
Copied from : http://jnb.ociweb.com/jnb/jnbNov2005.html
相关推荐
<END><br>40,chkdir.zip<br>Check for a Directory <END><br>41,getext.zip<br>Get a File's Extension<END><br>42,geticon.zip<br>Get a File's Icon<END><br>43,parseint.zip<br>Parse an Internet Filename<END>...
HOME/server/webapps/admin<br> and $CATALINA_HOME/server/webapps/manager<br>- Both web applications are copied or moved to $CATALINA_BASE, <br> and the path specified in the docBase attribute of the ...
Build 1616<br>requires software maintenance... (case=9605) 6876 <br>Fixed text in info copied from options dialog. (case=9567) <br>Fixed Find References Results navigation during search. (case=9548) <br>
1>586 of 589 functions (99.5%) were compiled, the rest were copied from previous compilation. 1> 21 functions were new in current compilation 1> 0 functions had inline decision re-evaluated but ...
<br><br>Neither this documentation nor any part of it may be copied or reproduced in any form or by any means or translated into another language, without the prior consent of SAP AG.
原著:Steve Mansour <br>sman@scruznet.com <br>Revised: June 5, 1999<br>(copied by jm /at/ jmason.org from http://www.scruz.net/%7esman/regexp.htm, after the original disappeared! ) <br><br>翻译:Neo ...
std::vector<std::string> copied_strings; std::copy(strings.begin(), strings.end(), std::back_inserter(copied_strings)); // 输出排序后的字符串和复制后的字符串 for (const auto &s : strings) std::...
$file_list .= "<li>$file_name</li>"; } } // 关闭目录句柄,并结束HTML列表 $file_list .= "</ul>"; closedir($dir); ?> <!-- HTML部分 --> <!DOCTYPE html> <html> <head> <title>目录列表</title> </head> ...
<br><br>template is instantiated with a specific pixel format and must be inherited to support pixel access functions for a specific bitmap implementation. This provides abstaction of the rotation ...
/copycache <URL> <Content Type> Copy files from the cache into the folder specified in /CopyFilesFolder parameter. In the <URL> parameter, you can specify the URL of the Web site (for example: ...
List<Field> fields = new ArrayList<>(); getAllFields(src.getClass(), fields); try { T target = (T) src.getClass().newInstance(); for (Field field : fields) { field.setAccessible(true); if ...
-- ccs:<name> -->Everything here will be copied<!-- /ccs:<name> --> 在目标文件中添加一些目标块: <!-- ccd:<name> -->Everything here will be replaced by value in source file<!-- /ccd:...
std::cout << "Copied file: " << srcPath << " to " << dstPath << std::endl; } } } ``` 3. 在主函数中调用这个函数,传递源目录和目标目录作为参数: ```cpp int main() { fs::path sourceDir = "源文件夹...
在Vue组件中,可以创建一个包含 `<qrcode-vue>` 组件的 `<div>` 元素,该组件会渲染一个二维码的canvas元素。要实现长按保存功能,可以在旁边添加一个透明图片,当用户长按时,实际是长按在了这个图片上。代码如下...
<span style={{ color: 'red' }}>Copied.</span> : null} <br /> <textarea style={{ marginTop: '1em' }} cols="22" rows="3" /> </div> ); } }); ``` 在上面的代码中,我们定义了一个 `App` 组件,它具有一...
If (txTo <> sLastCopyDir) And (sLastCopyDir <> "") Then _ If MsgBox("You are about to send files othere than the ones that " & _ "you just copied to the Recycle Bin." & vbCrLf & vbCrLf & _ ...
运行命令git clone <copied> 运行命令cd CS97-App 安装节点如果尚未在计算机上安装节点 转到 下载与您的操作系统匹配的安装程序 下载完成后,安装节点 运行应用程序的前端 运行命令cd frontend 运行命令npm ...
The <BRANCH_NAME> argument should provide a short description of the change you are trying to make to the projects.If you don't know, consider using the name default. The <PROJECT_LIST> specifies ...
cat ~/.ssh/id_rsa.pub | ssh <user>@<hostname> 'umask 0077; mkdir -p .ssh; cat >> .ssh/authorized_keys && echo "Key copied"' 刚开始 apt-get install -y git && git clone https://github.com/drkarl/