静态代理相对来说比较简单,无非就是聚合+多态:
参考:设计模式笔记 – Proxy 代理模式 (Design Pattern)
我们知道,通过使用代理,可以在被代理的类的方法的前后添加一些处理方法,这样就达到了类似AOP的效果。而JDK中提供的动态代理,就是实现AOP的绝好底层技术。
JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
还有一个叫CGLib的动态代理,CGLib全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。
例子:
首先是两个测试类:
interface Animal{ void sound(); } class Dog implements Animal{ public void sound(){ System.out.println("wong~"); } }
接下来演示CGLib的使用:
public class CglibProxy implements MethodInterceptor{ private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ // 设置需要创建子类的类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); // 通过字节码技术动态创建子类实例 return enhancer.create(); } // 拦截父类所有方法的调用 @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("begin..."); // 通过代理类调用父类中的方法 Object result = proxy.invokeSuper(obj, args); System.out.println("end..."); return null; } public static void main(String[] args){ CglibProxy proxy = new CglibProxy(); Animal dog = (Animal)proxy.getProxy(Dog.class); dog.sound(); } }
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。
CGLib所创建的动态代理对象的性能比JDK所创建的代理对象性能高不少,大概10倍,但CGLib在创建代理对象时所花费的时间却比JDK动态代理多大概8倍,所以对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建新的实例,所以比较适合CGLib动态代理技术,反之则适用于JDK动态代理技术。另外,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final,private等方法进行处理。所以,大家需要根据实际的情况选择使用什么样的代理了。
同样的,Spring的AOP编程中相关的ProxyFactory代理工厂内部就是使用JDK动态代理或CGLib动态代理的,通过动态代理,将增强(advice)应用到目标类中。
Spring定义了org.springframework.aop.framework.AopProxy接口,并提供了如下两种final类型的实现类:
如果通过ProxyFactory的setInterfaces(Class[] interfaces)指定针对接口进行代理,ProxyFactory就使用JdkDynamicAopProxy,如果是针对类的代理,则使用Cglib2AopProxy。另外,如果使用ProxyFactory的setOptimize(true)方法,则启动了优化代理方式,这样针对接口的代理也会使用Cglib2AopProxy。
在引介增强中就需要强制指定为Cglib2AopProxy,因为引介增强是一种比较特殊的增强类型,不是在目标周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点事类级别的。而非方法级别的。
下面来探析下JDK动态代理的原理,看看为什么JDK动态代理的性能会比较低。
动态代理的基本原理为反射 + 多态 + 聚合
首先创建一个接口,和一个需要被代理的类:
interface Animal{ void sound(); } class Dog implements Animal{ public void sound(){ System.out.println("wong~"); } }
接下来是创建一个MyInvocationHandler,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起:
public interface MyInvocationHandler { public void invoke(Method m,Object... args) throws Exception; }
接下来是创建一个MyInvocationHandler的实现类:
package com.itzhai.javanote.proxy; import java.lang.reflect.Method; public class AnimalHandler implements MyInvocationHandler { private Object animal; public AnimalHandler (Object animal){ this.animal = animal; } public void invoke(Method m,Object[] args) throws Exception{ System.out.println("before"); m.invoke(animal, args); System.out.println("after"); } }
接下里是创建代理类Proxy,里面包含一个newProxy方法,该方法生成需要代理的接口相关的所有方法,通过MyInvocationHandler的invoke方法,调用实际的对象,通过反射机制执行实际的方法,并且需要嵌入的代码也就在invoke方法里面被执行到了:
下面是使用我们创建的动态代理的代码:
package com.itzhai.javanote.proxy; import java.io.File; import java.io.FileWriter; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class MyProxy { public static Object newProxyInstance(Class interfaces,MyInvocationHandler handler) throws Exception{ String r = "\n"; Method[] methods = interfaces.getMethods(); StringBuffer sb = new StringBuffer(""); // 生成需要代理的接口中的所有方法 的代码,通过调用MyInvocationHandler的invoke方法实现真实对象方法的调用 for(int i =0;i<methods.length;i++){ sb.append(" public void "+methods[i].getName()+"() {"+ r + " try{ "+ r + " Method md = "+interfaces.getName()+".class.getMethod(\""+methods[i].getName()+"\");"+ r + " handler.invoke( md,new Object[]{});"+ r + " }catch(Exception e){e.printStackTrace();}"+ r + " }"+r + r ); } // 生成完整的类代码 String src = "package com.itzhai.javanote.proxy;"+ r + r + "import java.lang.reflect.*;"+ r + r + "public class Proxy$1 implements "+interfaces.getName() +"{"+ r + " private com.itzhai.javanote.proxy.MyInvocationHandler handler;"+ r + " public Proxy$1("+handler.getClass().getName() +" handler){"+ r + " this.handler = handler;"+ r + " }"+ r + r + sb.toString() + "}" +r; // 输出Java文件 String dir = System.getProperty("user.dir")+"/src/com/itzhai/javanote/proxy/"; FileWriter writer = new FileWriter(new File(dir+"Proxy$1.java")); writer.write(src); writer.flush(); writer.close(); // 编译动态代理类 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> units = fileMgr.getJavaFileObjects(dir+"Proxy$1.java"); Iterable<String> options = Arrays.asList("-d", System.getProperty("user.dir") + "/bin/"); CompilationTask t = compiler.getTask(null, fileMgr, null, options, null, units); t.call(); fileMgr.close(); // 加载动态代理类,并返回动态代理类的实例 URL[] urls = new URL[]{new URL("file:/"+dir)}; URLClassLoader loader = new URLClassLoader(urls); Class c = loader.loadClass("com.itzhai.javanote.proxy.Proxy$1"); Constructor ctr = c.getConstructor(handler.getClass()); return ctr.newInstance(handler); } }
分析可以得出,当我们通过MyProxy的newProxyInstance()方法返回代理对象时,实际上是通过传入的接口和MyInvocationHandler生成了一个代理类的字节码,并而代理类中的真实对象的方法是通过反射机制进行调用的(我们在实现invoke方法时都是通过反射的APIMethod的invoke调用具体的方法可知),这也是JDK动态代理在创建代理对象快而运行时却比较慢的原因。
感兴趣的朋友可以去看一下JDK的动态代理实现的源代码(JDK中通过Proxy中的getProxyClass(loader, interfaces)方法生成代理代理对象)。
如果要代理具体的业务类,需要使用CGLib动态代理生成子类了。
补充:
Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。
默认的策略是如果目标类是接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理。
本文链接: http://www.itzhai.com/java-dong-tai-dai-li-zhi-jdk-dong-tai-dai-li-he-cglib-dong-tai-dai-li-mian-xiang-qie-mian-bian-cheng-aop-yuan-li.html