JDK实现动态代理需要实现类通过接口定义业务方法,被代理的类必须实现接口;对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术(采用ASM技术),其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,由于是继承,因此使用CGLIB代理的类不能是final类。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
cglib的实践
(1)建立一个具体实现类:
package com.my.cglibproxy;
public class HelloWorldImpl {
public void sayHi() {
System.out.println("hello cglib");
}
}
(2)建立CGLIB代理类,代理类需要实现MethodInterceptor接口。
MethodInterceptor接口在API中说明如下:
General-purpose Enhancer
callback which provides for "around advice".
接口中只有一个intercept接口,API描述如下:
/**
* All generated proxied methods call this method instead of the original method.
* The original method may either be invoked by normal reflection using the Method object,
* or by using the MethodProxy (faster).
* @param obj "this", the enhanced object
* @param method intercepted Method
* @param args argument array; primitive types are wrapped
* @param proxy used to invoke super (non-intercepted method); may be called
* as many times as needed
* @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. Method returning void will ignore this value.
* @see MethodProxy
*/
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
(3)创建CGLIB的代理类
package com.my.cglibproxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class HelloWorldCglibProxy implements MethodInterceptor {
private Object target;
//此方法返回的是一个原始类的实例(被代理类)
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
//调用此方法的类就是callback类,也就是代理类HelloWorldCglibProxy自身,通过Enhancer的setCallback方法进行设置
@Override
public Object intercept(Object object, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
//在Spring的AOP中,此处可以配置添加before方法
System.out.println("before cglib");
//打印类名
System.out.println("object name: " + object.getClass().getName());
System.out.println("proxy name: " + proxy.getClass().getCanonicalName());
//调用super也就是被代理类的方法,本实例是sayHi方法
proxy.invokeSuper(object, args);
//打印方法名
System.out.println("method name: " + method.getName());
//在Spring中,此处可以配置添加after方法
System.out.println("after cglib");
return null;
}
}
(4)定义测试类进行代理测试
package com.my.cglibproxy;
public class TestCglibProxy {
public static void main(String [] args) {
HelloWorldCglibProxy cglib = new HelloWorldCglibProxy();
HelloWorldImpl helloWorldImpl = (HelloWorldImpl)cglib.getInstance(new HelloWorldImpl());
helloWorldImpl.sayHi();
}
}
测试过程中可能会报错:
Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
at net.sf.cglib.core.TypeUtils.parseType(TypeUtils.java:180)
at net.sf.cglib.core.KeyFactory.<clinit>(KeyFactory.java:66)
at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:69)
at com.my.cglibproxy.HelloWorldCglibProxy.getInstance(HelloWorldCglibProxy.java:17)
at com.my.cglibproxy.TestCglibProxy.main(TestCglibProxy.java:7)
Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
... 5 more
因为CGLIB的底层字节码实现技术是ASM,因此需要导入ASM的jar包,如果导入ASM的jar版本太高,可能出现如下错误。比如我在此实例中用的是ASM 4.1,导致报错。:
Exception in thread "main" java.lang.VerifyError: class net.sf.cglib.core.DebuggingClassWriter overrides final method visit.(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at net.sf.cglib.core.AbstractClassGenerator.<init>(AbstractClassGenerator.java:38)
at net.sf.cglib.core.KeyFactory$Generator.<init>(KeyFactory.java:127)
at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:112)
at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:108)
at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:104)
at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:69)
at com.my.cglibproxy.HelloWorldCglibProxy.getInstance(HelloWorldCglibProxy.java:17)
at com.my.cglibproxy.TestCglibProxy.main(TestCglibProxy.java:7)
此时需要下载一个3.X版本的ASM的jar包,错误解决,打印如下:
before cglib
object name: com.my.cglibproxy.HelloWorldImpl$$EnhancerByCGLIB$$8ed02279
proxy name: net.sf.cglib.proxy.MethodProxy
hello cglib
method name: sayHi
after cglib
从打印结果可以看出:
(1)CGLIB的代理方法invoke中的第一个参数Object object,所代表的就是原始类HelloWorldImpl的CGLIB的的代理类;
(2)获得代理类之后,实际通过代理类调用的是什么方法,则invoke方法的第二个Method参数就表示什么方法,比如本实例中,通过代理类的getInstance获得对象后,调用的是sayHi方法,则invoke的第二个Method参数就表示sayHi方法。
(3)原始类的方法的调用,虽然代码表现为helloWorldImpl.sayHi();实际是通过代理类来调用的,由Enhancer的setCallback设置回调类,比如本例中参数为setCallback(this),就是代理类自身。
(4)CGLIB能够知道具体调用哪个方法(即代理类invoke中的Method参数),是通过Enhancer的setSuperclass方法设置父类(这个父类就是被代理的原始类),从而根据这个父类拦截获取获取到父类的方法,在实现了MethodInterceptor的代理类中,可以在invoke方法中调用父类的具体方法之前进行一些处理,spring中AOP的around advice就是基于此实现的。