代理模式(Proxy Pattern)的定义也非常简单,是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,代理模式属于结构型设计模式。使用代理模式主要有两个目的:一保护目标对象,二增强目标对象。下面我们来看一下代理模式的类结构图,如下图所示。
一个典型的代理模式通常有三个角色,这里称之为代理三要素:
- 共同接口
- 真实对象
- 代理对象
Subject 是顶层接口,RealSubject 是真实对象(被代理对象),Proxy 是代理对象,代理对象持有被代理对象的引用,客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增加一些处理代码。在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。代理模式属于结构型模式,分为静态代理和动态代理。
代理模式分为静态代理和动态代理。动态代理的实现方式有 JDK 动态代理、CGLIB 动态代理、Javassist 动态代理。
静态代理
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的 .class 文件就已经生成。
举个例子,有些人到了适婚年龄,父母为自己的子女相亲,这个相亲的过程就是一种代理模式。下面来看代码实现。
顶层接口 Person 的代码如下:
/**
* 顶层接口
*/
public interface Person {
public void findLove();
}
儿子要找对象,实现 Son 类:
public class Son implements Person {
@Override
public void findLove() {
System.out.println("儿子要求:肤白貌美大长腿");
}
}
父亲要帮儿子相亲,实现 Father 类:
public class Father implements Person {
private Person person;
public Father(Person person){
this.person = person;
}
@Override
public void findLove() {
System.out.println("父亲物色对象");
person.findLove();
System.out.println("双方同意交往,确立关系");
}
}
来看测试代码:
public static void main(String[] args) {
Father father = new Father(new Son());
father.findLove();
}
运行结果如下图所示。
静态代理的优缺点:
- 优点:扩展原功能,不侵入原代码;
- 缺点:不同的代理类、代理方法,需要提供不同的代理对象;
动态代理
动态代理的目的就是为了解决静态代理的缺点。通过使用动态代理,我们可以通过在运行时,动态生成一个持有真实对象并实现代理接口的代理对象,同时注入扩展逻辑。
如果还以找对象为例,那么使用动态代理相当于能够适应复杂的业务场景,不仅包括父亲给儿子找对象,如果找对象这项业务发展成了一个产业,出现了媒婆、婚介所等,那么用静态代理成本太高了,需要一个更加通用的解决方案,满足任何单身人士找对象的需求,下面我们升级一下代码。
JDK 实现方式
创建媒婆类 JDKMeipo:
public class JDKMeipo implements InvocationHandler {
private Person person;
public JDKMeipo(Person person) {
this.person = person;
}
public Person getInstance() {
return (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
person.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object object = method.invoke(person, args);
after();
return object;
}
private void before() {
System.out.println("我是媒婆:我要给你找对象,现在已经确认你的需求");
System.out.println("开始物色");
}
private void after() {
System.out.println("如果合适的话,就准备办事");
}
}
创建单身客户类:
public class Customer implements Person {
@Override
public void findLove() {
System.out.println("高富帅,身高180,有6块腹肌");
}
}
测试代码如下:
public static void main(String[] args) {
Person person = new JDKMeipo(new Customer()).getInstance();
person.findLove();
}
运行效果如下图所示。
手写实现 JDK 动态代理
我们来探究一下 JDK 动态代理的原理,并模仿 JDK 动态代理手动实现一个动态代理。
JDK 动态代理采用字节码重组,重新生成对象来替代原始对象,以达到动态代理的目的。JDK 动态代理生成对象的步骤如下:
- 获取被代理对象的引用,并且获取它的所有接口,反射获取;
- JDK 动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口;
- 动态生成 Java 代码,新加的业务逻辑方法由一定的逻辑代码调用(在代码中体现);
- 编译新生成的 Java 代码.class 文件;
- 重新加载到 JVM 中运行。
以上过程就叫字节码重组。JDK 中有一个规范,在 ClassPath 下只要是 $ 开头的 .class 文件,一般都是自动生成的。那么我们有没有办法看到代替后的对象的“真容”呢?做一个这样测试,我们将内存中的对象字节码通过文件输出到一个新的 .class 文件,然后利用反编译工具查看其源码。
修改刚才的测试类代码:
package com.yjw.demo.pattern.proxy2.jdk;
import com.yjw.demo.pattern.proxy2.staticed.Person;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
public class JDKProxyTest {
public static void main(String[] args) throws Exception {
Person person = new JDKMeipo(new Customer()).getInstance();
person.findLove();
// 通过反编译工具可以查看源代码
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
FileOutputStream os = new FileOutputStream("/Users/yinjianwei/Downloads/$Proxy0.class");
os.write(bytes);
os.close();
}
}
运行以上代码,找到目录中的 $Proxy0.class 文件。使用反编译工具打开,看到如下内容:
package com.yjw.demo.pattern.proxy2.jdk;
import com.yjw.demo.pattern.proxy2.staticed.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/* renamed from: $Proxy0 reason: invalid class name and default package */
public final class C$Proxy0 extends Proxy implements Person {
private static Method m0;
private static Method m1;
private static Method m2;
private static Method m3;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m3 = Class.forName("com.yjw.demo.pattern.proxy2.staticed.Person").getMethod("findLove", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
} catch (ClassNotFoundException e2) {
throw new NoClassDefFoundError(e2.getMessage());
}
}
/**
* 代理类的构造方法,方法参数为InvocationHandler类型,调用父类的构造方法,父类构造方法代码如下所示
* <p>
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
* </p>
*
* @param invocationHandler
*/
public C$Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
public final boolean equals(Object obj) {
try {
return ((Boolean) this.h.invoke(this, m1, new Object[]{obj})).booleanValue();
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable th) {
throw new UndeclaredThrowableException(th);
}
}
/**
* 调用InvocationHandler中的invoke方法,并把m3传进去
*/
public final void findLove() {
try {
this.h.invoke(this, m3, (Object[]) null);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable th) {
throw new UndeclaredThrowableException(th);
}
}
public final int hashCode() {
try {
return ((Integer) this.h.invoke(this, m0, (Object[]) null)).intValue();
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable th) {
throw new UndeclaredThrowableException(th);
}
}
public final String toString() {
try {
return (String) this.h.invoke(this, m2, (Object[]) null);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable th) {
throw new UndeclaredThrowableException(th);
}
}
}
我们发现,$Proxy0 继承了 Proxy 类,同时还实现了 Person 接口,而且重写了 findLove() 等方法,在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,重写的方法用反射调用目标对象的方法。
注意代理类中的构造方法和 findLove() 方法,执行 $Proxy0 中的 findLove() 方法会调用 JDKMeipo 中的 invoke() 方法。
上面的 $Proxy0 是 JDK 帮我们自动生成的,现在我们不依赖 JDK,自己来动态生成源代码、动态完成编译,然后替代目标对象并执行。
创建 GPInvocationHandler 接口:
package com.yjw.demo.pattern.proxy2.manual;
import java.lang.reflect.Method;
public interface GPInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
创建 GPClassLoader 类:
package com.yjw.demo.pattern.proxy2.manual;
import java.io.*;
import java.net.URLDecoder;
public class GPClassLoader extends ClassLoader {
private File classPathFile;
public GPClassLoader() {
String classPath = null;
try {
classPath = URLDecoder.decode(GPClassLoader.class.getResource("").getPath(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
this.classPathFile = new File(classPath);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = GPClassLoader.class.getPackage().getName() + "." + name;
if (classPathFile != null) {
File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
if (classFile.exists()) {
FileInputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1) {
out.write(buff, 0, len);
}
return defineClass(className, out.toByteArray(), 0, out.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
}
创建 GPProxy 类:
package com.yjw.demo.pattern.proxy2.manual;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
public class GPProxy {
public static final String ln = "\r\n";
public static Object newProxyInstance(GPClassLoader classLoader, Class<?>[] interfaces, GPInvocationHandler h) {
try {
//1、动态生成源代码.java文件
String src = generateSrc(interfaces);
//2、Java文件输出磁盘
String filePath = URLDecoder.decode(GPProxy.class.getResource("").getPath(), "UTF-8");
System.out.println(filePath);
File f = new File(filePath + "$Proxy0.java");
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//3、把生成的.java文件编译成.class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manage.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
task.call();
manage.close();
//4、编译生成的.class文件加载到JVM中来
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
f.delete();
//5、返回字节码重组以后的新的代理对象
return c.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String generateSrc(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append("package com.yjw.demo.pattern.proxy2.manual;" + ln);
sb.append("import com.yjw.demo.pattern.proxy2.staticed.Person;" + ln);
sb.append("import java.lang.reflect.*;" + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
sb.append("GPInvocationHandler h;" + ln);
sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
sb.append("this.h = h;");
sb.append("}" + ln);
for (Method m : interfaces[0].getMethods()) {
Class<?>[] params = m.getParameterTypes();
StringBuffer paramNames = new StringBuffer();
StringBuffer paramValues = new StringBuffer();
StringBuffer paramClasses = new StringBuffer();
for (int i = 0; i < params.length; i++) {
Class clazz = params[i];
String type = clazz.getName();
String paramName = toLowerFirstCase(clazz.getSimpleName());
paramNames.append(type + " " + paramName);
paramValues.append(paramName);
paramClasses.append(clazz.getName() + ".class");
if (i > 0 && i < params.length - 1) {
paramNames.append(",");
paramValues.append(",");
paramClasses.append(",");
}
}
sb.append("public " + m.getReturnType().getName() + " " + m.getName()
+ "(" + paramNames.toString() + ") {" + ln);
sb.append("try{" + ln);
sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\""
+ m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
sb.append((hasReturnValue(m.getReturnType()) ? "return " : "")
+ getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType())
+ ";" + ln);
sb.append("}catch(Error _ex) { }");
sb.append("catch(Throwable e){" + ln);
sb.append("throw new UndeclaredThrowableException(e);" + ln);
sb.append("}");
sb.append(getReturnEmptyCode(m.getReturnType()));
sb.append("}");
}
sb.append("}" + ln);
return sb.toString();
}
private static Map<Class, Class> mappings = new HashMap<>();
static {
mappings.put(int.class, Integer.class);
}
private static String getReturnEmptyCode(Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "return 0;";
} else if (returnClass == void.class) {
return "";
} else {
return "return null;";
}
}
private static String getCaseCode(String code, Class<?> returnClass) {
if (mappings.containsKey(returnClass)) {
return "((" + mappings.get(returnClass).getName() + ")" + code + ")."
+ returnClass.getSimpleName() + "Values()";
}
return code;
}
private static boolean hasReturnValue(Class<?> clazz) {
return clazz != void.class;
}
private static String toLowerFirstCase(String src) {
char[] chars = src.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
创建 GPMeipo 类:
package com.yjw.demo.pattern.proxy2.manual;
import com.yjw.demo.pattern.proxy2.staticed.Person;
import java.lang.reflect.Method;
public class GPMeipo implements GPInvocationHandler {
// 被代理的对象,把引用保存下来
private Person person;
public GPMeipo(Person person) {
this.person = person;
}
public Person getInstance() {
Class<?> clazz = person.getClass();
return (Person) GPProxy.newProxyInstance(new GPClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
method.invoke(person, args);
after();
return null;
}
private void before() {
System.out.println("我是媒婆:我要给你找对象,现在已经确认你的需求");
System.out.println("开始物色");
}
private void after() {
System.out.println("如果合适的话,就准备办事");
}
}
客户端测试代码如下:
public static void main(String[] args) {
Person person = new GPMeipo(new Customer()).getInstance();
person.findLove();
}
运行效果如下图所示。
静态代理和动态代理的本质区别
- 静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则;
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则;
- 若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无须修改代理类的代码。
代理模式的优缺点
代理模式具有以下优点:
- 代理模式能将代理对象与真实被调用目标对象分离;
- 在一定程度上降低了系统的耦合度,扩展性好;
- 可以起到保护目标对象的作用;
- 可以增强目标对象的功能。
代理模式的缺点:
- 代理模式会造成系统设计中类的数量增加;
- 在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢;
- 增加了系统的复杂度。
摘录:《Spring 5 核心原理与30个类手写实战》来自文艺界的Tom老师的书籍。
推荐文章
代理模式详解(包含原理详解):另外一种方式解读
java动态代理实现与原理详细分析:动态代理原理分析
深入理解[代理模式]原理与技术:手写JDK动态代理源码
作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/dtdhxw 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。