[关闭]
@Catyee 2021-07-23T10:21:35.000000Z 字数 9777 阅读 481

代理模式

设计模式


一、什么是代理模式

代理模式就是为其他对象提供一种代理以控制对这个对象的访问。代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

二、静态代理

所谓静态代理就是在程序运行前就已经存在代理类的字节码文件。

  1. /**
  2. * 1、抽象对象接口
  3. */
  4. public interface Subject {
  5. // 业务A抽象接口
  6. void businessA();
  7. // 业务B抽象接口
  8. void businessB();
  9. }
  10. /**
  11. * 2、被代理类(也是接口实现类,实现具体的业务逻辑)
  12. */
  13. public class RealSubject implements Subject {
  14. // 业务A的具体实现逻辑
  15. @Override
  16. public void businessA() {
  17. System.out.println("this is a business.");
  18. }
  19. // 业务B的具体实现逻辑
  20. @Override
  21. public void businessB() {
  22. System.out.println("this is b business");
  23. }
  24. }
  25. /**
  26. * 3、代理类 (代理类一般会实现抽象接口,或者继承被代理类)
  27. */
  28. public class SubjectProxy implements Subject {
  29. private RealSubject realSubject;
  30. public SubjectProxy() {
  31. this.realSubject = new RealSubject();
  32. }
  33. // 代理并扩展业务A
  34. @Override
  35. public void businessA() {
  36. // 调用前扩展
  37. ...
  38. try {
  39. // 调用业务A方法
  40. this.realSubject.businessA();
  41. } catch (Exception e) {
  42. // 捕获异常扩展
  43. ...
  44. }
  45. // 调用后扩展
  46. ...
  47. }
  48. // 代理并扩展业务B
  49. @Override
  50. public void businessB() {
  51. // 调用前扩展
  52. ...
  53. try {
  54. // 调用业务B方法
  55. this.realSubject.businessB();
  56. } catch (Exception e) {
  57. // 捕获异常扩展
  58. ...
  59. }
  60. // 调用后扩展
  61. ...
  62. }
  63. }
  64. /**
  65. * 4、使用类
  66. */
  67. public class Client {
  68. public static void main(String[] args) {
  69. Subject subject = new SubjectProxy();
  70. subject.businessA();
  71. subject.businessB();
  72. }
  73. }

以上就是一个完整的静态代理的代码示例,可以看到代理类是由程序员直接编写的,就是一个普通的类,在程序运行之前需要编译成具体的字节码文件。
静态代理模式的重用性并不强,主要体现在两方面,一是如果代理对象过多,程序员得手写1:1比例的代理类,繁琐、重复且难以维护。二是假如增强动作都比较类似,那代理类中不同的代理方法中还会有很多类似的代码。
静态代理的另一个缺点是一旦被代理类增加或者删除一个方法,那每个代理类都要手动修改,增加了维护的难度。

三、动态代理

为了克服静态代理的缺点,就用了动态代理,所谓动态代理就是不需要再由程序员手动编写代理类,而是将代理类的生成延迟到JVM运行过程中,在需要用到代理类的时候才会按照代理设置自动生成代理类,然后实例化生成代理对象。

现在最为主流的动态代理是JDK的动态代理和CgLib动态代理,他们在实现上略有差异,JDK动态代理产生的代理类和被代理类实现了相同的接口,而cglib动态代理生成的代理类则是继承了被代理类,也就是说代理类是被代理类的子类。

3.1 JDK动态代理

以下是JDK动态代码的使用示例:

  1. /**
  2. * 1、抽象对象接口
  3. */
  4. public interface Subject {
  5. // 业务A抽象接口
  6. void businessA();
  7. // 业务B抽象接口
  8. void businessB();
  9. }
  10. /**
  11. * 2、被代理类(也是接口实现类,实现具体的业务逻辑)
  12. */
  13. public class RealSubject implements Subject {
  14. // 业务A的具体实现逻辑
  15. @Override
  16. public void businessA() {
  17. System.out.println("this is a business.");
  18. }
  19. // 业务B的具体实现逻辑
  20. @Override
  21. public void businessB() {
  22. System.out.println("this is b business");
  23. }
  24. }
  25. /**
  26. * 3、拦截器用于功能增强
  27. */
  28. public class SubjectInterceptor<T extends Subject> implements InvocationHandler {
  29. //目标类
  30. private final T target;
  31. public SubjectInterceptor(T target) {
  32. this.target = target;
  33. }
  34. // 这里proxy就是代理对象本身,所以千万不要这样调用:method.invoke(proxy, args),会导致无限循环
  35. @Override
  36. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  37. System.out.println("执行前扩展"); // 执行前扩展
  38. Object result = method.invoke(this.target, args);
  39. System.out.println("执行后扩展"); // 执行后扩展
  40. return result;
  41. }
  42. }
  43. /**
  44. * 4、生成代理对象,执行业务
  45. */
  46. public class Client {
  47. public static void main(String[] args) {
  48. Subject subject = new RealSubject();
  49. SubjectInterceptor<Subject> subjectInterceptor = new SubjectInterceptor<>(subject);
  50. Subject subjectProxy = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
  51. subject.getClass().getInterfaces(), subjectInterceptor);
  52. subjectProxy.businessA();
  53. subjectProxy.businessB();
  54. // 将生成的代理类持久化到文件
  55. byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0.class", new Class[] {Subject.class});
  56. try (FileOutputStream fos = new FileOutputStream("/tmp/$Proxy0.class")) {
  57. fos.write(bytes);;
  58. fos.flush();
  59. }
  60. }
  61. }

首先关注InvocationHandler中的invoke方法,这个方法的第一个参数就是代理对象本身,第二个参数是当前调用方法的Method对象,第三个参数是方法的参数,我们要注意不能在代码中这样调用:

  1. @Override
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. // ...
  4. Object result = method.invoke(proxy, args);
  5. // ...
  6. return result;
  7. }

这样调用就会导致方法一直被拦截,然后再调用再拦截,成为死循环。

第56行Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h), 可以看到这个方法有三个参数,第一个参数是被代理对象的classloader,第二个参数是被代理对象实现的接口,第三个参数是拦截器。这里重点关注第二个参数,他是被代理对象所有实现的接口,生成代理对象的时候,代理对象也会实现这些接口,所以JDK的动态代理最终代理类和被代理类都会实现相同的接口,从另一方面来说要想使用JDK动态代理,必须要有至少一个抽象接口,这也是JDK动态代理的使用限制

JDK动态代理采用的是字节重组的方式,重新生成对象来替代原始对象,以达到动态代理的目的。JDK动态代理生成对象的步骤如下:
(1)JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口。
(2)编译新生成的Java代码.class文件。
(3)重新加载到JVM中运行。
以上过程就叫字节码重组。JDK中有一个规范,在ClassPath下只要是$开头的.class文件,一般都是自动生成的,代理类就是自动生成的,可以通过打断点的方式进行调试查看。我们还可以将生成的代理类持久化到磁盘中,然后使用反编译软件进行查看(使用idea即可直接查看字节码文件)方法见上面第61及行之后的代码。

将动态生成的代理类反编译之后会发现:代理类继承了Proxy类,同时实现了所有被代理对象实现的那些接口。生成的代理类中有一个静态代码块,代码块中通过反射获取了被代理类的所有方法,代理对象重写了代理类的所有方法,在重写的方法中用反射调用代理对象的方法

  1. // 反编译代理类的字节码文件的结果
  2. public final class class extends Proxy implements Subject {
  3. private static Method m1;
  4. private static Method m4;
  5. private static Method m3;
  6. private static Method m2;
  7. private static Method m0;
  8. // 以拦截器作为构造参数
  9. public class(InvocationHandler var1) throws {
  10. super(var1);
  11. }
  12. // 重写代理对象的方法
  13. public final void businessA() throws {
  14. try {
  15. // super.h就是拦截器,这里其实就是调用了拦截器的invoke方法
  16. // 在拦截器的invoke方法中会调用增强的代码,以及代理对象的方法
  17. super.h.invoke(this, m3, (Object[])null);
  18. } catch (RuntimeException | Error var2) {
  19. throw var2;
  20. } catch (Throwable var3) {
  21. throw new UndeclaredThrowableException(var3);
  22. }
  23. }
  24. // 省略部分代码
  25. public final void businessB() throws {...}
  26. public final String toString() throws {...}
  27. public final int hashCode() throws {...}
  28. public final boolean equals(Object var1) throws {...}
  29. // 静态代码块:反射获取代理对象的所有方法,并保存引用
  30. static {
  31. try {
  32. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  33. m4 = Class.forName("com.catyee.experiment.design.proxy.Subject").getMethod("businessB");
  34. m3 = Class.forName("com.catyee.experiment.design.proxy.Subject").getMethod("businessA");
  35. m2 = Class.forName("java.lang.Object").getMethod("toString");
  36. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  37. } catch (NoSuchMethodException var2) {
  38. throw new NoSuchMethodError(var2.getMessage());
  39. } catch (ClassNotFoundException var3) {
  40. throw new NoClassDefFoundError(var3.getMessage());
  41. }
  42. }
  43. }

3.2 CGLib动态代理

以下是CGLib动态代理的使用示例:

  1. /**
  2. * 1、被代理类(实现具体的业务逻辑)
  3. */
  4. public class RealSubject {
  5. // 业务A的具体实现逻辑
  6. public void businessA() {
  7. System.out.println("this is a business.");
  8. }
  9. // 业务B的具体实现逻辑
  10. public void businessB() {
  11. System.out.println("this is b business");
  12. }
  13. }
  14. /**
  15. * 2、拦截器用于功能增强
  16. */
  17. public class CGSubjectInterceptor implements MethodInterceptor {
  18. // 生成代理对象
  19. public <T extends Subject> T getProxy(Class<T> clazz) {
  20. Enhancer enhancer = new Enhancer();
  21. enhancer.setSuperclass(clazz);
  22. enhancer.setCallback(this);
  23. return (T) enhancer.create();
  24. }
  25. @Override
  26. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  27. // 执行前扩展
  28. System.out.println("执行前扩展");
  29. Object result = methodProxy.invokeSuper(o, objects);
  30. // 执行后扩展
  31. System.out.println("执行后扩展");
  32. return result;
  33. }
  34. }
  35. /**
  36. * 3、生成代理对象,执行业务
  37. */
  38. public class CGClient {
  39. public static void main(String[] args) {
  40. // 通过cglib提供的接口将动态生成的代理类的字节码持久化到文件
  41. System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/tmp/cglib_proxy_class");
  42. // 生成代理对象,并执行业务
  43. RealSubject subjectProxy = new CGSubjectInterceptor().getProxy(RealSubject.class);
  44. subjectProxy.businessA();
  45. subjectProxy.businessB();
  46. }
  47. }

可以看到CGLib是使用Enhancer来生成代理对象的,在生成之前需要调用setSupperclass方法,作用是设置代理类的父类,这个父类就是被代理类。所以CGLib的被代理类不需要实现任何接口,代理类继承被代理类然后重写被代理类中的所有方法,在方法中调用父类的原方法。

实例化Enhancer对象之后需要设置callback,callback从名字来看含义是"回调",但并不是"原方法执行完了再调用callback",而是"调用原方法的时候会调用到callback",callback全面管理了原方法和增强逻辑的执行,示例中设置的callback是MethodInterceptor,也就是方法拦截器,除了MethodInterceptor,CGLIB还定义了NoOp、LazyLoader等多种callback,具体如下:

我们同样将动态生成的代理类的字节码持久化到文件(方法见上面第46行代码),会发现生成了三个字节码文件,通过代理类的源码可以看到,代理类会重写所有从父类继承来的方法,并且还会生成一个对应的代理方法。

  1. // 重写被代理对象的原方法
  2. public final void businessA() {
  3. MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; // 拦截器
  4. if (var10000 == null) {
  5. CGLIB$BIND_CALLBACKS(this);
  6. var10000 = this.CGLIB$CALLBACK_0;
  7. }
  8. if (var10000 != null) {
  9. // 调用拦截器的intercept方法
  10. // 在拦截器中会调用增强代码,并调用methodProxy.invokeSuper方法
  11. // invokeSuper方法会调到上面的CGLIB$businessA$0()方法,该方法内部调到被代理对象的原方法
  12. var10000.intercept(this, CGLIB$businessA$0$Method, CGLIB$emptyArgs, CGLIB$businessA$0$Proxy);
  13. } else {
  14. super.businessA();
  15. }
  16. }
  17. // 生成一个新的与原方法对应的代理方法
  18. final void CGLIB$businessA$0() {
  19. // 调用被代理对象的原方法
  20. super.businessA();
  21. }

所以整个调用流程就是subjectProxy.businessA()方法(代理对象重写的方法) -> 拦截器的intercept()方法 -> methodProxy.invokeSuper()方法 -> 与原方法对应的CGLIB$businessA$0()代理方法 -> 被代理对象的businessA()方法(被代理对象的原方法)

这里比较关键的在于MethodProxy的invokeSuper()方法,看一下源码:

  1. public Object invokeSuper(Object obj, Object[] args) throws Throwable {
  2. try {
  3. init();
  4. FastClassInfo fci = fastClassInfo;
  5. return fci.f2.invoke(fci.i2, obj, args);
  6. } catch (InvocationTargetException e) {
  7. throw e.getTargetException();
  8. }
  9. }

可以看到它是先获取FastClassInfo,然后通过FastClassInfo来调用到与原方法对应的代理方法的,这个FastClassInfo是CGLib代理的关键,之前说过CGLIB动态代理生成了三个字节码文件,一个是代理类的字节码文件,另外两个一个是代理类的FastClass,一个是被代理类的FastClass,CGLib代理执行代理方法的效率之所以比JDK的高,是因为CGlib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个FastClass类,这两个类会为代理类和被代理类的方法分配一个index(int类型),这个index当作一个入参,FastClass就可以直接定位要调用的方法并直接进行调用,省去了反射调用,所以调用效率比JDK代理通过反射调用高
以下是反编译的RealSubject的FastClass类:

  1. public class RealSubject$$FastClassByCGLIB$$f5f16011 extends FastClass {
  2. public RealSubject$$FastClassByCGLIB$$f5f16011(Class var1) {
  3. super(var1);
  4. }
  5. // 获取方法的index
  6. public int getIndex(Signature var1) {
  7. String var10000 = var1.toString();
  8. switch(var10000.hashCode()) {
  9. case -673380524:
  10. if (var10000.equals("businessA()V")) {
  11. return 0;
  12. }
  13. break;
  14. case -673350733:
  15. if (var10000.equals("businessB()V")) {
  16. return 1;
  17. }
  18. break;
  19. // 省略部分代码
  20. ...
  21. }
  22. return -1;
  23. }
  24. // 省略部分代码
  25. ...
  26. // var1即为方法的index, var2是被代理对象, var3是参数
  27. // 通过index直接找到对应的方法,然后执行
  28. public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
  29. RealSubject var10000 = (RealSubject)var2;
  30. int var10001 = var1;
  31. try {
  32. switch(var10001) {
  33. case 0:
  34. var10000.businessA();
  35. return null;
  36. case 1:
  37. var10000.businessB();
  38. return null;
  39. // 设略部分代码
  40. ...
  41. }
  42. } catch (Throwable var4) {
  43. throw new InvocationTargetException(var4);
  44. }
  45. throw new IllegalArgumentException("Cannot find matching method/constructor");
  46. }
  47. // 省略部分代码
  48. ...
  49. }

四、JDK动态代理与CGLib动态代理的对比

(1)JDK动态代理实现了被代理对象的接口(JDK动态代理的被代理对象必须要实现至少一个抽象接口),CGLib代理继承了被代理对象
(2)JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,CGlib代理实现更复杂,生成代理类比JDK动态代理效率低
(3)JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注