动态代理是指在运行时,动态生成代理类。代理类的字节码将在运行时生成并载入当前的ClassLoader.
生成动态代理类的方法很多,如JDK自带的动态代理、CGLIB、Javassist或者ASM库。
JDK动态代理使用简单,它内置在JDK中,因此不需要引入第三方Jar包,但相对功能比较弱。CGLIB和Javassist都是高级的字节码生成库,总体性能比JDK自带的动态代理好,而且功能十分强大。ASM是低级的字节码生成工具,使用ASM已经近乎在于使用Javabytecode编程,对开发人员要求较高,也是性能最好的一种动态代理生辰工具。但ASM的使用是在过于繁琐,而且性能也没有数量级的提升,与CGLIB等高级字节码生成工具相比,ASM程序的可维护性也较差。
JDK实现
1、步骤
1)通过实现InvocationHandler接口创建自己的调用处理器
2)通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类
3)通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
4)通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
2、创建代理
//InvocationHandlerImpl 实现了InvocationHandler接口,并能实现方法调用从代理类到委托类的分派转发 //其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用 InvocationHandler handler = new InvocaitonHandlerImpl(..); //通过Proxy为包括Interface接口在内的一组接口动态创建代理类的对象 Class clazz = Proxy.getProxyClass(classLoader,new Class[]{Interface.class,...}); //通过反射从生成的类对象获得构造函数对象 Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class}); //通过构造函数对象创建动态代理类实例 Interface Proxy = (Interface)constructor.newInstance(new Object[]{handler}); //Proxy类的静态方法newProxyInstance对上面具体步骤的后三步做了封装,简化了动态代理对象的获取过程。 //InvocationHandlerImpl实现了InvocaitonHandler接口,并能实现方法调用从代理类到委托类的分派转发 InvocaitonHandler handler = new InvocationHandlerImpl(..); //通过Proxy直接创建动态代理类实例 Interface proxy = (Interface)Proxy.newProxyInstance(classLoader,new Class[]{Interface.class},handler);
3、代码
/** * 接口 * @author Emily * */ public interface IDBQuery { String request(); } /** * 真实的实现类,具体的目标对象 * @author Emily * */ public class DBQuery implements IDBQuery { public DBQuery(){ try { Thread.sleep(1000); //可能包含数据库连接等耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String request() { return "request string"; } } /** * JDK动态代理的实现类 * @author Emily * */ public class JdkDbQueryHandler implements InvocationHandler{ IDBQuery real = null; //主题接口 /** * 生成Handler */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(real == null) real = new DBQuery(); //如果是第一次调用,则生成真实对象 return real.request(); //使用真实主题完成实际的操作 } /** * 利用Handler生成动态代理对象 * @return */ public static IDBQuery createJdkProxy(){ //根据指定的类加载器和接口以及截获器,返回代理类的一个实例对象 //ClassLoader loader :指定被代理对象的类加载器 //Class[] Interfaces :指定被代理对象所以事项的接口 //InvocationHandler h :指定需要调用的InvocationHandler对象 IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{IDBQuery.class}, new JdkDbQueryHandler()); return jdkProxy; } }
CGLIB实现
package com.ltt.dynamic; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * CGLIB动态代理 * @author Emily * */ public class CglibDbQueryInterceptor implements MethodInterceptor{ IDBQuery real = null; /** * 处理代理逻辑的切入类 */ @Override public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable { if (real == null) { //代理类 的内部逻辑 real = new DBQuery(); return real.request(); } return null; } /** * 生成动态代理 * @return */ public static IDBQuery createCglibProxy(){ Enhancer enhancer = new Enhancer(); //指定切入器,定义代理类逻辑 enhancer.setCallback(new CglibDbQueryInterceptor()); //指定实现的接口 enhancer.setInterfaces(new Class[]{IDBQuery.class}); IDBQuery cglibProxy = (IDBQuery) enhancer.create(); return cglibProxy; } }
Javassist实现
一种是使用代理工厂创建,另一种通过使用动态代码创建。使用代理工厂创建时,方法与CGLIB类似,也需要实现一个用于代理逻辑处理的Handler:例如createJavassistDynProxy();使用动态代码创建,生成字节码,这种方式可以非常灵活,甚至可以在运行时生成业务逻辑,如createJavassistBytecodeDynamicProxy()方法。
/** * Javassist动态代理 * @author Emily * */ public class JavassistDynDbQueryHandler implements MethodHandler{ IDBQuery real = null; /* * (non-Javadoc) 实现用于代理逻辑处理的Handler * @see javassist.util.proxy.MethodHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.reflect.Method, java.lang.Object[]) */ @Override public Object invoke(Object arg0, Method arg1, Method arg2, Object[] arg3) throws Throwable { if (real == null) real = new DBQuery(); return real.request(); } /** * 创建动态代理 * @return * @throws Exception */ public static IDBQuery createJavassistDynProxy() throws Exception{ ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setInterfaces(new Class[]{IDBQuery.class}); //指定接口 Class proxyClass = proxyFactory.createClass(); IDBQuery javassistProxy = (IDBQuery) proxyClass.newInstance(); //设置Handler处理器 ((ProxyObject) javassistProxy).setHandler(new JavassistDynDbQueryHandler()); return javassistProxy; } /** * 运行时生成业务逻辑 * @return * @throws Exception */ public static IDBQuery createJavassistBytecodeDynamicProxy() throws Exception{ ClassPool mPool = new ClassPool(true); //定义类名 CtClass mCtc = mPool.makeClass(IDBQuery.class.getName() + "JavaassistBytecodeProxy"); //需要实现接口 mCtc.addInterface(mPool.get(IDBQuery.class.getName())); //添加构造函数 mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc)); //添加类的字段信息,使用动态Java代码 mCtc.addField(CtField.make("public" + IDBQuery.class.getName() + "real;", mCtc)); String dbQueryname = DBQuery.class.getName(); //添加方法,这里使用动态Java代码指定内部逻辑 mCtc.addMethod(CtNewMethod.make("public String request() { if(real==null) real = new " +dbQueryname+"(); return real.request();}", mCtc)); //基于以上信息,生成动态类 Class pc = mCtc.toClass(); //生成动态类的实例 IDBQuery bytecodeProxy = (IDBQuery) pc.newInstance(); return bytecodeProxy; } }
三种动态代理:JDK动态代理创建速度优于CGLIB动态代理,但是在函数的调用性能上远不如CGLIB和Javassist。故CGLIB和Javassist整体性能上比JDK动态代理好。
动态代理 VS 静态代理
静态代理
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
动态代理
1)动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定的。
2)与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
3)动态代理可以很大幅度的减少代码行数,并提升系统的灵活性。