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"); } }
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; } }
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)
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就是基于此实现的。