Java代理(静态代理、JDK动态代理、CGLIB动态代理)
Java中代理有静态代理和动态代理。静态代理的代理关系在编译时就确定了,而动态代理的代理关系是在运行期确定的。静态代理实现简单,适合于代理类较少且确定的情况,而动态代理则给我们提供了更大的灵活性。Java中的方法继承及重写本质上可以看做是一种动态代理,实际上CGLIB动态代理的原理就是继承。
Java中动态代理有JDK原生动态代理和CGLIB动态代理两种。前者本质上是根据定好的接口动态生成静态代理类(该接口的实现类);后者则不需要事先定好接口而是可以直接根据类进行动态代理,其本质是根据指定的类动态生成静态代理类(指定的类的子类)。动态代理中,被代理的对象的所有方法都会被代理,除非在代理逻辑中进行方法筛选。
本质:
JDK原生动态代理:生成被代理对象所实现的接口的实现类;实现类中每个方法调用被代理对象的相应方法,只不过在调用前后加上了额外逻辑;要求被代理对象实现接口
CGLIB动态代理:生成被代理对象的子类;子类中的每个方法调用被代理对象(父类对象)中的相应方法,只不过在调用前后加上了额外处理;要求被代理对象不能被final修饰
动态代理是IOC、AOP等技术的基础。
不论是静态代理还是动态代理,都是设计模式里装饰器模式(Decorator Pattern)的典型例子——即在不修改原有类或方法的前提下增强其功能。例如BufferedInputStream等内部持有InputStream对象且通过该对象完成IO(静态代理)、Spring AOP的声明式事务(动态代理)、Python里的函数装饰器等。
静态代理和JDK动态代理
Dynamic proxies allow one single class with one single method to service multiple method calls to arbitrary classes with an arbitrary number of methods. A dynamic proxy can be thought of as a kind of Facade, but one that can pretend to be an implementation of any interface. Under the cover, it routes all method invocations to a single handler – the invoke() method. https://www.baeldung.com/java-dynamic-proxies
示例:
1 package com.marchon.proxytest; 2 3 public interface IUserService { 4 public String getUserName(); 5 public Integer getAge(String userName); 6 }
1 package com.marchon.proxytest; 2 3 /** 4 * 被代理对象 5 * 6 * @author zsm 7 * 8 */ 9 public class UserServiceImpl implements IUserService { 10 11 @Override 12 public String getUserName() { 13 String res = this.getClass() + ":hello"; 14 System.out.println(res); 15 return res; 16 } 17 18 @Override 19 public Integer getAge(String userName) { 20 Integer age = 20; 21 System.out.println(age); 22 return age; 23 } 24 25 }
1 package com.marchon.proxytest; 2 3 /** 4 * 代理对象(静态代理)<br> 5 * 缺点:<br> 6 * 1、代理类和被代理类实现相同的接口,代码重复、得为每个接口都实现相应的实现从而维护成本高 2、代理对象只服务于被代理对象,即每个被代理对象都得实现相应的代理对象 7 * 8 * @author zsm 9 * 10 */ 11 class UserServiceStaticProxy implements IUserService { 12 private IUserService proxiedObj; 13 14 public UserServiceStaticProxy(IUserService proxiedObj) { 15 if (proxiedObj instanceof UserServiceStaticProxy) { 16 throw new RuntimeException("illegal proxiedObj proxied object"); 17 } 18 19 this.proxiedObj = proxiedObj; 20 } 21 22 @Override 23 public String getUserName() { 24 System.out.println("before"); 25 String res = proxiedObj.getUserName(); 26 System.out.println("after"); 27 return res; 28 29 } 30 31 @Override 32 public Integer getAge(String userName) { 33 System.out.println("before"); 34 Integer age = proxiedObj.getAge(userName); 35 System.out.println("after"); 36 return age; 37 } 38 39 } 40 41 public class Main_StaticProxy { 42 public static void main(String[] args) { 43 IUserService proxiedObj = new UserServiceImpl(); 44 45 // UserServiceStaticProxy proxy = new UserServiceStaticProxy(proxiedObj); 46 IUserService proxyObject = new UserServiceStaticProxy(proxiedObj); 47 proxyObject.getUserName(); 48 proxyObject.getAge("zhangsan"); 49 } 50 }
1 package com.marchon.proxytest; 2 3 import java.io.FileOutputStream; 4 import java.io.IOException; 5 import java.lang.reflect.InvocationHandler; 6 import java.lang.reflect.Method; 7 import java.lang.reflect.Proxy; 8 9 import sun.misc.ProxyGenerator; 10 11 /** 12 * 代理对象(动态代理)<br> 13 * 动态代理:在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能。动态代理主要分为JDK动态代理和cglib动态代理两大类<br> 14 * 这里介绍jdk动态代理,其本质上是在运行时动态产生一个实现指定接口的静态代理类,指定接口的所有非final方法(包括继承的非final方法如toString)均会被代理。 15 * 16 * @author zsm 17 * 18 */ 19 20 class JdkDynamicProxyTemplate implements InvocationHandler { 21 private Object proxiedObj; 22 23 public JdkDynamicProxyTemplate(Object proxiedObj) { 24 this.proxiedObj = proxiedObj; 25 } 26 27 @Override 28 public Object invoke(Object proxyObj, Method method, Object[] args) throws Throwable { 29 // if (method.getName().equals("getUserName")) {//为避免所有非final方法都被代理,可作此判断 30 // 31 // } 32 33 // System.out.println(proxyObj);//stack overflow, why? 34 // System.out.println(method);// public abstract java.lang.String com.marchon.proxytest.IUserService.getUserName() 35 36 System.out.println("before"); 37 Object res = method.invoke(proxiedObj, args); 38 System.out.println("after"); 39 40 return res; 41 } 42 43 } 44 45 public class Main_JdkDynamicProxy {// 参阅:https://www.jianshu.com/p/269afd0a52e6 46 public static void main(String[] args) { 47 IUserService proxiedObj = new UserServiceImpl(); 48 49 JdkDynamicProxyTemplate proxyTemplate = new JdkDynamicProxyTemplate(proxiedObj); 50 51 // 第一个参数是指定代理类的类加载器(我们传入当前测试类的类加载器) 52 // 第二个参数是代理类需要实现的接口(我们传入被代理类实现的接口,这样生成的代理类和被代理类就实现了相同的接口) 53 // 第三个参数是invocation handler,用来处理方法的调用。这里传入我们自己实现的handler 54 IUserService proxyObject = (IUserService) Proxy.newProxyInstance(proxiedObj.getClass().getClassLoader(), 55 proxiedObj.getClass().getInterfaces(), proxyTemplate);// 创建包含被代理对象各方法的代理对象,显然可知:该代理对象实现了所传接口中(这里为IUserService)定义的各方法、代理对象的各方法实现为直接调用proxyTemplate相应的各方法实现。可以使用sum.misc下的ProxyGenerator生成动态代理类的字节码文件,再反编译出动态代理类源码 56 57 proxyObject.getUserName(); 58 proxyObject.getAge("zhangsan"); 59 proxyObject.toString();// 所有非final方法都会被代理,包括从Object继承的等 60 61 { 62 // 获取代理类字节码文件 63 String path = "$Proxy0.class"; 64 byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", proxiedObj.getClass().getInterfaces()); 65 FileOutputStream out = null; 66 67 try { 68 out = new FileOutputStream(path); 69 out.write(classFile); 70 out.flush(); 71 } catch (Exception e) { 72 e.printStackTrace(); 73 } finally { 74 try { 75 out.close(); 76 } catch (IOException e) { 77 e.printStackTrace(); 78 } 79 } 80 } 81 82 // jdk动态代理动态产生的代理类类似于如下静态代理 83 System.out.println(); 84 System.out.println("equivalent static proxy test:"); 85 new TmpEquivalentStaticProxy(proxyTemplate).getUserName(); 86 87 } 88 } 89 90 /** 91 * 92 * 上述生成的动态代理生成的代理类实际是类似于本类 93 * 94 * @author zsm 95 * 96 */ 97 class TmpEquivalentStaticProxy implements IUserService {// 真正由jdk动态代理生成的代理类还 extends Proxy 98 private InvocationHandler invokeHandler; 99 100 public TmpEquivalentStaticProxy(InvocationHandler invokeHandler) { 101 this.invokeHandler = invokeHandler; 102 } 103 104 @Override 105 public String getUserName() { 106 Object[] args = null; 107 Method getUserNameMethod = IUserService.class.getMethods()[0];// public abstract java.lang.String 108 // com.marchon.proxytest.IUserService.getUserName() 109 try { 110 return (String) invokeHandler.invoke(this, getUserNameMethod, args); 111 } catch (Throwable e) { 112 e.printStackTrace(); 113 return null; 114 } 115 116 } 117 118 @Override 119 public Integer getAge(String userName) { 120 Object[] args = new Object[] { userName }; 121 try { 122 Method getAgeMethod = IUserService.class.getMethods()[1];// public abstract java.lang.Integer 123 // com.marchon.proxytest.IUserService.getAge(java.lang.String) 124 return (Integer) invokeHandler.invoke(this, getAgeMethod, args); 125 } catch (Throwable e) { 126 e.printStackTrace(); 127 return null; 128 } 129 130 } 131 132 }
springframework中的一个jdk动态代理示例:
由上可见,静态代理的各个方法具有共性:每个方法内的前后都做额外其他处理、中间调用被代理对象的相应方法。既然有共性,那就可以抽取共性以减少重复,也即将静态代理的方法抽象出一个”模板“,这样就不需要在代理对象中针对被代理对象的每个方法写额外逻辑,这其实就变成了动态代理。
JDK动态代理实际上是自动生成一个静态代理类并创建相应实例。代理类默认继承Porxy类,因为Java中只支持单继承,所以JDK动态代理只能去实现接口;代理类的方法都会去调用InvocationHandler的invoke()方法,故此需重写InvocationHandler的invoke()方法。
JDK动态代理为我们提供了非常灵活的代理机制,但也有不足:
被代理对象的所有非final方法(如从Object继承的toString、equals等)都会被代理(即都会在方法前后做与InvocationHandler的invoke()方法前后同样的处理,当然我们可以通过在invoke里对method name加以判断避免此情况),然而有时候我们并不希望这些方法被代理。
JDK动态代理是基于接口的(生成的动态代理类实际上extends Proxy implements IUserService)。如果要被代理的对象没有实现接口,该如何实现代理呢?可用下面要介绍的CGLIB动态代理。
从上面讨论可得知,静态代理和动态代理在运行时是一样的(都是静态代理了),其差异在运行时之前(即编译期)才存在,差异体现在开发者是否需要分别在每个与被代理方法对应的代理方法的前后写额外的处理逻辑:静态代理需要而动态代理不需要(后者不需要是因为该任务从由开发者负责转交由JVM来负责了)。因此,动态代理相比于静态代理的主要优势是为开发者提供便利,不用像静态代理那写重复的代理方法逻辑。
CGLIB动态代理(springframework)
与JDK动态代理不同,CGLIB(Code Generation Library)动态代理不需要事先定义接口,而是可以直接对类进行动态代理。CGLIB动态代理中被代理对象的所有非final方法默认也会被代理。
示例:
1 package com.marchon.proxytest; 2 3 import java.lang.reflect.Method; 4 5 import org.springframework.cglib.proxy.Enhancer; 6 import org.springframework.cglib.proxy.MethodInterceptor; 7 import org.springframework.cglib.proxy.MethodProxy; 8 9 /** 10 * 代理对象(动态代理)<br> 11 * 动态代理:在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能。动态代理主要分为JDK动态代理和cglib动态代理两大类<br> 12 * 这里介绍cglib动态代理,其本质上是在运行时动态产生一个实现继承指定类的的静态代理类,指定类的所有非final方法(包括继承的非final方法如toString)均会被代理。 13 * 14 * @author zsm 15 * 16 */ 17 18 class CglibDynamicProxyTemplate implements MethodInterceptor { 19 20 @Override 21 public Object intercept(Object proxyObj, Method proxiedMethod, Object[] args, MethodProxy proxyMethod) throws Throwable { 22 // if (method.getName().equals("sayHello")) {//为避免所有非final方法都被代理,可作此判断 23 // 24 // } 25 26 // System.out.println(proxyObj);//stack overflow, why? 27 // System.out.println(proxiedMethod);// public java.lang.String 28 // com.marchon.proxytest.HelloConcrete.sayHello(java.lang.String) 29 // System.out.println(proxyMethod);// org.springframework.cglib.proxy.MethodProxy@108c4c35 30 31 System.out.println("before"); 32 Object res = proxyMethod.invokeSuper(proxyObj, args); 33 System.out.println("after"); 34 return res; 35 } 36 37 } 38 39 class HelloConcrete { 40 public String sayHello(String username) { 41 return "hello " + username; 42 } 43 44 public final String getAddress() { 45 return "beijing"; 46 } 47 } 48 49 public class Main_CglibDynamicProxy { 50 public static void main(String[] args) { 51 CglibDynamicProxyTemplate proxyTemplate = new CglibDynamicProxyTemplate(); 52 53 Enhancer enhancer = new Enhancer(); 54 enhancer.setSuperclass(HelloConcrete.class); 55 enhancer.setCallback(proxyTemplate); 56 57 HelloConcrete proxyObject = (HelloConcrete) enhancer.create(); 58 System.out.println(proxyObject.sayHello("zhangsan"));// 会创建包含被代理对象所有非final方法的代理类,实际上代理类是被代理类的子类,故不会代理final方法;代理对象的各方法实现为直接调用proxyTemplate相应的各方法实现 59 proxyObject.hashCode();// 所有非final方法都会被代理,包括从Object继承的等 60 proxyObject.getAddress();// final方法不会被代理 61 62 // jdk动态代理动态产生的代理类类似于如下静态代理 63 System.out.println(); 64 System.out.println("equivalent static proxy test:"); 65 new TmpEquivalentStaticProxyOfCglib(proxyTemplate).sayHello("zhangsan"); 66 } 67 } 68 69 class TmpEquivalentStaticProxyOfCglib extends HelloConcrete {// 真正由cglib动态代理生成的代理类还 implements Factory 70 private MethodInterceptor methodInterceptor; 71 72 public TmpEquivalentStaticProxyOfCglib(MethodInterceptor methodInterceptor) { 73 this.methodInterceptor = methodInterceptor; 74 } 75 76 @Override 77 public String sayHello(String username) { 78 Object[] args = null; 79 Method proxiedMethod = super.getClass().getMethods()[0]; 80 MethodProxy proxyMethod = null;// this.getClass().getMethods()[0];//不造如何获取MethodProxy对象,故此方法实际上跑不了 81 82 try { 83 return (String) methodInterceptor.intercept(this, proxiedMethod, args, proxyMethod); 84 } catch (Throwable e) { 85 e.printStackTrace(); 86 return null; 87 } 88 89 } 90 91 // 无法override getAddress方法 92 }
由上可见,此动态代理本质上是在运行时动态根据指定的类继承实现一个子类(故指定的类不能是final的),在子类中重写方法,方法的实现为调用MethodInterceptor中的intercept方法。
关于Java Spring CBLIB动态代理的原理(生成代理类的过程)可参阅文章:https://fangshixiang.blog.csdn.net/article/details/106205956,关键类:ConfigurationClassEnhancer、Enhancer。底层是借助ASM来实现动态产生代理类的。
综上,JDK动态代理和CGLIB动态代理本质上很像,
从功能角度看:作用都是让开发者可以只实现一个方法(InvocationHandler的invoke方法,MethodInterceptor的intercept方法)来为被代理对象做方法增强。
从内部实现上看,框架要完成两件事:在运行时动态生成一个代理类及其对象、调用用户写的方法增强。具体而言包括:创建代理类(通过实现接口创建,通过继承父类创建)、根据代理类创建代理对象、在代理对象中重写非final方法。重写方法的逻辑是调用invoke 或 intercept方法。
JavaAssist代理
一个编辑Java字节码的库,通过编辑编译产生的类中的字节码来实现方法增强,可以修改方法、增加方法等。
ASM代理
一套Java 字节码生成架构,它可以动态生成二进制格式的子类或其它代理类,或者在类被 Java 虚拟机装入内存之前,动态修改类。
参考资料
https://www.jianshu.com/p/269afd0a52e6
https://www.cnblogs.com/liuyun1995/p/8144628.html
https://www.cnblogs.com/CarpenterLee/p/8241042.html JDK动态代理和CGLIB动态代理
https://blog.csdn.net/difffate/article/details/70552056 CGLIB动态代理
https://juejin.cn/post/6911549491158089742?utm_source=gold_browser_extension 代理总结