代理模式(Proxy Pattern)的定义也非常简单,是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,代理模式属于结构型设计模式。使用代理模式主要有两个目的:一保护目标对象,二增强目标对象。下面我们来看一下代理模式的类结构图,如下图所示。

一个典型的代理模式通常有三个角色,这里称之为代理三要素:

  • 共同接口
  • 真实对象
  • 代理对象

image.png

Subject 是顶层接口,RealSubject 是真实对象(被代理对象),Proxy 是代理对象,代理对象持有被代理对象的引用,客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增加一些处理代码。在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。代理模式属于结构型模式,分为静态代理和动态代理。

代理模式分为静态代理和动态代理。动态代理的实现方式有 JDK 动态代理、CGLIB 动态代理、Javassist 动态代理。

静态代理

由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的 .class 文件就已经生成。

举个例子,有些人到了适婚年龄,父母为自己的子女相亲,这个相亲的过程就是一种代理模式。下面来看代码实现。

顶层接口 Person 的代码如下:

  1. /**
  2. * 顶层接口
  3. */
  4. public interface Person {
  5. public void findLove();
  6. }

儿子要找对象,实现 Son 类:

  1. public class Son implements Person {
  2. @Override
  3. public void findLove() {
  4. System.out.println("儿子要求:肤白貌美大长腿");
  5. }
  6. }

父亲要帮儿子相亲,实现 Father 类:

  1. public class Father implements Person {
  2. private Person person;
  3. public Father(Person person){
  4. this.person = person;
  5. }
  6. @Override
  7. public void findLove() {
  8. System.out.println("父亲物色对象");
  9. person.findLove();
  10. System.out.println("双方同意交往,确立关系");
  11. }
  12. }

来看测试代码:

  1. public static void main(String[] args) {
  2. Father father = new Father(new Son());
  3. father.findLove();
  4. }

运行结果如下图所示。

image.png

静态代理的优缺点:

  • 优点:扩展原功能,不侵入原代码;
  • 缺点:不同的代理类、代理方法,需要提供不同的代理对象;

动态代理

动态代理的目的就是为了解决静态代理的缺点。通过使用动态代理,我们可以通过在运行时,动态生成一个持有真实对象并实现代理接口的代理对象,同时注入扩展逻辑。

如果还以找对象为例,那么使用动态代理相当于能够适应复杂的业务场景,不仅包括父亲给儿子找对象,如果找对象这项业务发展成了一个产业,出现了媒婆、婚介所等,那么用静态代理成本太高了,需要一个更加通用的解决方案,满足任何单身人士找对象的需求,下面我们升级一下代码。

JDK 实现方式

创建媒婆类 JDKMeipo:

  1. public class JDKMeipo implements InvocationHandler {
  2. private Person person;
  3. public JDKMeipo(Person person) {
  4. this.person = person;
  5. }
  6. public Person getInstance() {
  7. return (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
  8. person.getClass().getInterfaces(), this);
  9. }
  10. @Override
  11. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  12. before();
  13. Object object = method.invoke(person, args);
  14. after();
  15. return object;
  16. }
  17. private void before() {
  18. System.out.println("我是媒婆:我要给你找对象,现在已经确认你的需求");
  19. System.out.println("开始物色");
  20. }
  21. private void after() {
  22. System.out.println("如果合适的话,就准备办事");
  23. }
  24. }

创建单身客户类:

  1. public class Customer implements Person {
  2. @Override
  3. public void findLove() {
  4. System.out.println("高富帅,身高180,有6块腹肌");
  5. }
  6. }


测试代码如下:

  1. public static void main(String[] args) {
  2. Person person = new JDKMeipo(new Customer()).getInstance();
  3. person.findLove();
  4. }

运行效果如下图所示。

image.png

手写实现 JDK 动态代理

我们来探究一下 JDK 动态代理的原理,并模仿 JDK 动态代理手动实现一个动态代理。

JDK 动态代理采用字节码重组,重新生成对象来替代原始对象,以达到动态代理的目的。JDK 动态代理生成对象的步骤如下:

  1. 获取被代理对象的引用,并且获取它的所有接口,反射获取;
  2. JDK 动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口;
  3. 动态生成 Java 代码,新加的业务逻辑方法由一定的逻辑代码调用(在代码中体现);
  4. 编译新生成的 Java 代码.class 文件;
  5. 重新加载到 JVM 中运行。

以上过程就叫字节码重组。JDK 中有一个规范,在 ClassPath 下只要是 $ 开头的 .class 文件,一般都是自动生成的。那么我们有没有办法看到代替后的对象的“真容”呢?做一个这样测试,我们将内存中的对象字节码通过文件输出到一个新的 .class 文件,然后利用反编译工具查看其源码。

修改刚才的测试类代码:

  1. package com.yjw.demo.pattern.proxy2.jdk;
  2. import com.yjw.demo.pattern.proxy2.staticed.Person;
  3. import sun.misc.ProxyGenerator;
  4. import java.io.FileOutputStream;
  5. public class JDKProxyTest {
  6. public static void main(String[] args) throws Exception {
  7. Person person = new JDKMeipo(new Customer()).getInstance();
  8. person.findLove();
  9. // 通过反编译工具可以查看源代码
  10. byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
  11. FileOutputStream os = new FileOutputStream("/Users/yinjianwei/Downloads/$Proxy0.class");
  12. os.write(bytes);
  13. os.close();
  14. }
  15. }

运行以上代码,找到目录中的 $Proxy0.class 文件。使用反编译工具打开,看到如下内容:

  1. package com.yjw.demo.pattern.proxy2.jdk;
  2. import com.yjw.demo.pattern.proxy2.staticed.Person;
  3. import java.lang.reflect.InvocationHandler;
  4. import java.lang.reflect.Method;
  5. import java.lang.reflect.Proxy;
  6. import java.lang.reflect.UndeclaredThrowableException;
  7. /* renamed from: $Proxy0 reason: invalid class name and default package */
  8. public final class C$Proxy0 extends Proxy implements Person {
  9. private static Method m0;
  10. private static Method m1;
  11. private static Method m2;
  12. private static Method m3;
  13. static {
  14. try {
  15. m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
  16. m3 = Class.forName("com.yjw.demo.pattern.proxy2.staticed.Person").getMethod("findLove", new Class[0]);
  17. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  18. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
  19. } catch (NoSuchMethodException e) {
  20. throw new NoSuchMethodError(e.getMessage());
  21. } catch (ClassNotFoundException e2) {
  22. throw new NoClassDefFoundError(e2.getMessage());
  23. }
  24. }
  25. /**
  26. * 代理类的构造方法,方法参数为InvocationHandler类型,调用父类的构造方法,父类构造方法代码如下所示
  27. * <p>
  28. * protected Proxy(InvocationHandler h) {
  29. * Objects.requireNonNull(h);
  30. * this.h = h;
  31. * }
  32. * </p>
  33. *
  34. * @param invocationHandler
  35. */
  36. public C$Proxy0(InvocationHandler invocationHandler) {
  37. super(invocationHandler);
  38. }
  39. public final boolean equals(Object obj) {
  40. try {
  41. return ((Boolean) this.h.invoke(this, m1, new Object[]{obj})).booleanValue();
  42. } catch (Error | RuntimeException e) {
  43. throw e;
  44. } catch (Throwable th) {
  45. throw new UndeclaredThrowableException(th);
  46. }
  47. }
  48. /**
  49. * 调用InvocationHandler中的invoke方法,并把m3传进去
  50. */
  51. public final void findLove() {
  52. try {
  53. this.h.invoke(this, m3, (Object[]) null);
  54. } catch (Error | RuntimeException e) {
  55. throw e;
  56. } catch (Throwable th) {
  57. throw new UndeclaredThrowableException(th);
  58. }
  59. }
  60. public final int hashCode() {
  61. try {
  62. return ((Integer) this.h.invoke(this, m0, (Object[]) null)).intValue();
  63. } catch (Error | RuntimeException e) {
  64. throw e;
  65. } catch (Throwable th) {
  66. throw new UndeclaredThrowableException(th);
  67. }
  68. }
  69. public final String toString() {
  70. try {
  71. return (String) this.h.invoke(this, m2, (Object[]) null);
  72. } catch (Error | RuntimeException e) {
  73. throw e;
  74. } catch (Throwable th) {
  75. throw new UndeclaredThrowableException(th);
  76. }
  77. }
  78. }

我们发现,$Proxy0 继承了 Proxy 类,同时还实现了 Person 接口,而且重写了 findLove() 等方法,在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,重写的方法用反射调用目标对象的方法。

注意代理类中的构造方法和 findLove() 方法,执行 $Proxy0 中的 findLove() 方法会调用 JDKMeipo 中的 invoke() 方法。

上面的 $Proxy0 是 JDK 帮我们自动生成的,现在我们不依赖 JDK,自己来动态生成源代码、动态完成编译,然后替代目标对象并执行。

创建 GPInvocationHandler 接口:

  1. package com.yjw.demo.pattern.proxy2.manual;
  2. import java.lang.reflect.Method;
  3. public interface GPInvocationHandler {
  4. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  5. }

创建 GPClassLoader 类:

  1. package com.yjw.demo.pattern.proxy2.manual;
  2. import java.io.*;
  3. import java.net.URLDecoder;
  4. public class GPClassLoader extends ClassLoader {
  5. private File classPathFile;
  6. public GPClassLoader() {
  7. String classPath = null;
  8. try {
  9. classPath = URLDecoder.decode(GPClassLoader.class.getResource("").getPath(), "UTF-8");
  10. } catch (UnsupportedEncodingException e) {
  11. e.printStackTrace();
  12. }
  13. this.classPathFile = new File(classPath);
  14. }
  15. @Override
  16. protected Class<?> findClass(String name) throws ClassNotFoundException {
  17. String className = GPClassLoader.class.getPackage().getName() + "." + name;
  18. if (classPathFile != null) {
  19. File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
  20. if (classFile.exists()) {
  21. FileInputStream in = null;
  22. ByteArrayOutputStream out = null;
  23. try {
  24. in = new FileInputStream(classFile);
  25. out = new ByteArrayOutputStream();
  26. byte[] buff = new byte[1024];
  27. int len;
  28. while ((len = in.read(buff)) != -1) {
  29. out.write(buff, 0, len);
  30. }
  31. return defineClass(className, out.toByteArray(), 0, out.size());
  32. } catch (Exception e) {
  33. e.printStackTrace();
  34. } finally {
  35. if (null != in) {
  36. try {
  37. in.close();
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. if (out != null) {
  43. try {
  44. out.close();
  45. } catch (IOException e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. }
  50. }
  51. }
  52. return null;
  53. }
  54. }

创建 GPProxy 类:

  1. package com.yjw.demo.pattern.proxy2.manual;
  2. import javax.tools.JavaCompiler;
  3. import javax.tools.StandardJavaFileManager;
  4. import javax.tools.ToolProvider;
  5. import java.io.File;
  6. import java.io.FileWriter;
  7. import java.lang.reflect.Constructor;
  8. import java.lang.reflect.Method;
  9. import java.net.URLDecoder;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. public class GPProxy {
  13. public static final String ln = "\r\n";
  14. public static Object newProxyInstance(GPClassLoader classLoader, Class<?>[] interfaces, GPInvocationHandler h) {
  15. try {
  16. //1、动态生成源代码.java文件
  17. String src = generateSrc(interfaces);
  18. //2、Java文件输出磁盘
  19. String filePath = URLDecoder.decode(GPProxy.class.getResource("").getPath(), "UTF-8");
  20. System.out.println(filePath);
  21. File f = new File(filePath + "$Proxy0.java");
  22. FileWriter fw = new FileWriter(f);
  23. fw.write(src);
  24. fw.flush();
  25. fw.close();
  26. //3、把生成的.java文件编译成.class文件
  27. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  28. StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
  29. Iterable iterable = manage.getJavaFileObjects(f);
  30. JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
  31. task.call();
  32. manage.close();
  33. //4、编译生成的.class文件加载到JVM中来
  34. Class proxyClass = classLoader.findClass("$Proxy0");
  35. Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
  36. f.delete();
  37. //5、返回字节码重组以后的新的代理对象
  38. return c.newInstance(h);
  39. } catch (Exception e) {
  40. e.printStackTrace();
  41. }
  42. return null;
  43. }
  44. private static String generateSrc(Class<?>[] interfaces) {
  45. StringBuffer sb = new StringBuffer();
  46. sb.append("package com.yjw.demo.pattern.proxy2.manual;" + ln);
  47. sb.append("import com.yjw.demo.pattern.proxy2.staticed.Person;" + ln);
  48. sb.append("import java.lang.reflect.*;" + ln);
  49. sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
  50. sb.append("GPInvocationHandler h;" + ln);
  51. sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
  52. sb.append("this.h = h;");
  53. sb.append("}" + ln);
  54. for (Method m : interfaces[0].getMethods()) {
  55. Class<?>[] params = m.getParameterTypes();
  56. StringBuffer paramNames = new StringBuffer();
  57. StringBuffer paramValues = new StringBuffer();
  58. StringBuffer paramClasses = new StringBuffer();
  59. for (int i = 0; i < params.length; i++) {
  60. Class clazz = params[i];
  61. String type = clazz.getName();
  62. String paramName = toLowerFirstCase(clazz.getSimpleName());
  63. paramNames.append(type + " " + paramName);
  64. paramValues.append(paramName);
  65. paramClasses.append(clazz.getName() + ".class");
  66. if (i > 0 && i < params.length - 1) {
  67. paramNames.append(",");
  68. paramValues.append(",");
  69. paramClasses.append(",");
  70. }
  71. }
  72. sb.append("public " + m.getReturnType().getName() + " " + m.getName()
  73. + "(" + paramNames.toString() + ") {" + ln);
  74. sb.append("try{" + ln);
  75. sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\""
  76. + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
  77. sb.append((hasReturnValue(m.getReturnType()) ? "return " : "")
  78. + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType())
  79. + ";" + ln);
  80. sb.append("}catch(Error _ex) { }");
  81. sb.append("catch(Throwable e){" + ln);
  82. sb.append("throw new UndeclaredThrowableException(e);" + ln);
  83. sb.append("}");
  84. sb.append(getReturnEmptyCode(m.getReturnType()));
  85. sb.append("}");
  86. }
  87. sb.append("}" + ln);
  88. return sb.toString();
  89. }
  90. private static Map<Class, Class> mappings = new HashMap<>();
  91. static {
  92. mappings.put(int.class, Integer.class);
  93. }
  94. private static String getReturnEmptyCode(Class<?> returnClass) {
  95. if (mappings.containsKey(returnClass)) {
  96. return "return 0;";
  97. } else if (returnClass == void.class) {
  98. return "";
  99. } else {
  100. return "return null;";
  101. }
  102. }
  103. private static String getCaseCode(String code, Class<?> returnClass) {
  104. if (mappings.containsKey(returnClass)) {
  105. return "((" + mappings.get(returnClass).getName() + ")" + code + ")."
  106. + returnClass.getSimpleName() + "Values()";
  107. }
  108. return code;
  109. }
  110. private static boolean hasReturnValue(Class<?> clazz) {
  111. return clazz != void.class;
  112. }
  113. private static String toLowerFirstCase(String src) {
  114. char[] chars = src.toCharArray();
  115. chars[0] += 32;
  116. return String.valueOf(chars);
  117. }
  118. }

创建 GPMeipo 类:

  1. package com.yjw.demo.pattern.proxy2.manual;
  2. import com.yjw.demo.pattern.proxy2.staticed.Person;
  3. import java.lang.reflect.Method;
  4. public class GPMeipo implements GPInvocationHandler {
  5. // 被代理的对象,把引用保存下来
  6. private Person person;
  7. public GPMeipo(Person person) {
  8. this.person = person;
  9. }
  10. public Person getInstance() {
  11. Class<?> clazz = person.getClass();
  12. return (Person) GPProxy.newProxyInstance(new GPClassLoader(), clazz.getInterfaces(), this);
  13. }
  14. @Override
  15. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  16. before();
  17. method.invoke(person, args);
  18. after();
  19. return null;
  20. }
  21. private void before() {
  22. System.out.println("我是媒婆:我要给你找对象,现在已经确认你的需求");
  23. System.out.println("开始物色");
  24. }
  25. private void after() {
  26. System.out.println("如果合适的话,就准备办事");
  27. }
  28. }

客户端测试代码如下:

  1. public static void main(String[] args) {
  2. Person person = new GPMeipo(new Customer()).getInstance();
  3. person.findLove();
  4. }

运行效果如下图所示。

image.png

静态代理和动态代理的本质区别

  1. 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则;
  2. 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则;
  3. 若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无须修改代理类的代码。

代理模式的优缺点

代理模式具有以下优点:

  1. 代理模式能将代理对象与真实被调用目标对象分离;
  2. 在一定程度上降低了系统的耦合度,扩展性好;
  3. 可以起到保护目标对象的作用;
  4. 可以增强目标对象的功能。

代理模式的缺点:

  1. 代理模式会造成系统设计中类的数量增加;
  2. 在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢;
  3. 增加了系统的复杂度。

摘录:《Spring 5 核心原理与30个类手写实战》来自文艺界的Tom老师的书籍。

推荐文章

代理模式详解(包含原理详解):另外一种方式解读
java动态代理实现与原理详细分析:动态代理原理分析
深入理解[代理模式]原理与技术:手写JDK动态代理源码

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/dtdhxw 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。