定义
代理模式: 为其他对象提供一种代理以控制对这个对象的访问.
事例
小明是一个程序员, 在公司负责项目的研发工作. 有一天, 客户打电话进来, 沟通之后, 原来客户是有个模块需求要变动一下. 小明却没有应允, 而是让客户去找产品经理老王沟通.
是小明偷懒不想干活吗? 显然不是. 我们把这个事例对应到上面的定义上, 程序员小明可以映射为其他对象, 产品经理老王是小明的代理. 它来控制小明这个对象的访问.
我们看上面的类图, 可以简单归纳以下角色.
Subject 某一类主题. 比如工程师
Proxy 代理对象. 比如产品经理老王
RealSubject 真实对象. 比如程序员小明
静态代理
我们通过代码来重现上面的场景.
首先定义一个 工程师的接口, 它有一个编码的方法.
- public interface Engineer {
- void coding();
- }
小明是个 Java 码农, 啊呸. 是个 Java 工程师, 要实现工程师这个接口.
- public class JavaEngineer implements Engineer{
- private String name;
- public JavaEngineer(String name){
- this.name = name;
- }
- public void coding() {
- System.out.println(name+": 正在努力的 coding...");
- System.out.println(name+": 终于改完了!");
- }
- }
产品经理老王也让他实现工程师的接口. 他代理了公司里的 Java 工程师对象, 当客户有需求提出来, 他要整理评估一下, 当需要 coding 的时候, 他直接转交给具体的工程师去处理.
- public class ProductManager implements Engineer{
- private String name;
- private JavaEngineer engineer;
- public ProductManager(JavaEngineer engineer,String name){
- this.engineer = engineer;
- this.name = name;
- }
- public void coding() {
- arrange();
- engineer.coding();
- appease();
- }
- private void arrange(){
- System.out.println(this.name+": 整理客户需求中...");
- System.out.println(this.name+": 输出需求文档, 交给码农去完成!");
- }
- private void appease(){
- System.out.println(this.name+": 哎, 需求变好多次了. 得安抚一下这个码农才好!");
- }
- }
我们来重现一下这个场景.
- // 有一个美丽的 Java 工程师, 他的名字叫小明.
- JavaEngineer engineer = new JavaEngineer("XiaoMing");
- // 同样, 还有一个猥琐的老王.
- ProductManager manager = new ProductManager(engineer,"老王");
- // 有新需求的时候, 老王负责去沟通搞定.
- manager.coding();
- System.out.println("------------------ 输出结果 -----------------------");
- // 老王: 整理客户需求中...
- // 老王: 输出需求文档, 交给码农去完成!
- //XiaoMing : 正在努力的 coding...
- //XiaoMing : 终于改完了!
- // 老王: 哎, 需求变好多次了. 得安抚一下这个码农才好!
动态代理
JDK 通过反射机制给我们提供了动态代理的实现, 允许开发人员在运行时刻动态的创建出代理类及其对象. 当使用者调用了代理对象所代理的接口中的方法的时候, 这个调用的信息会被传递给 InvocationHandler 的 invoke 方法. 在 invoke 方法的参数中可以获取到代理对象, 方法对应的 Method 对象和调用的实际参数. invoke 方法的返回值被返回给使用者. 这种做法实际上相当于对方法调用进行了拦截.
关键有两个类, Proxy 和 InvocationHandler .
Proxy 用于生成代理类
InvocationHandler 用于调用目标类的方法, 并且允许在调用前后插入其他的逻辑
上面的事例我们改成动态代理方式来看一下. 先定义一个调用处理程序
- public class ProxyHandler implements InvocationHandler{
- private Object target;
- private String name;
- public ProxyHandler(Object target,String name){
- this.target = target;
- this.name = name;
- }
- public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
- arrange();
- method.invoke(target, args);
- appease();
- return null;
- }
- private void arrange(){
- System.out.println(this.name+": 整理客户需求中...");
- System.out.println(this.name+": 输出需求文档, 交给工程师去完成!");
- }
- private void appease(){
- System.out.println(this.name+": 哎, 需求变好多次了. 得安抚一下这个码农才好!");
- }
- }
然后我们来测试一下
- // 有一个美丽的 Java 工程师, 他的名字叫小明.
- Engineer engineer = new JavaEngineer("XiaoMing");
- // 代理实例的调用处理程序
- ProxyHandler handler = new ProxyHandler(engineer, "老王");
- // 返回指定接口的代理类实例, 该接口可以将方法指派到指定的 handler
- Engineer proxy = (Engineer) Proxy.newProxyInstance(Engineer.class.getClassLoader(),
- new Class[]{Engineer.class}, handler);
- proxy.coding();
- System.out.println("------------------ 输出结果 -----------------------");
- // 老王: 整理客户需求中...
- // 老王: 输出需求文档, 交给码农去完成!
- //XiaoMing : 正在努力的 coding...
- //XiaoMing : 终于改完了!
- // 老王: 哎, 需求变好多次了. 得安抚一下这个码农才好!
内存中的代理类实例长啥样?
我们看到, Proxy 类通过静态方法 newProxyInstance 就生成了一个代理类的实例. 先不管它是怎么样生成的, 但是我想关心它到底长什么样子呢? 把它拿出来看看. JDK 生成的代理类以 $Porxy 开头, 后面跟一个从 0 开始的自增长数字. 比如,$Proxy0, 通过下面这段代码, 可以将代理类实例输出到 $Proxy0.class 文件中.
- byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",new Class[] {
- Engineer.class
- });
- FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");
- fileOutputStream.write(data);
- fileOutputStream.close();
通过反编译 class 文件, 得到代理类删减整理如下:
- public class $Proxy0 extends Proxy implements Engineer {
- private static final long serialVersionUID = 1L;
- private static Method m3;
- protected $Proxy0(InvocationHandler h) {
- // 通过构造方法 把代理实例的调用处理程序传进来
- super(h);
- }
- public final void coding() {
- try {
- // 此处的 h 是父类 Proxy 的属性, 对应的就是 ProxyHandler
- //invoke 就相当于 ProxyHandler.invoke(this, m3, null);
- this.h.invoke(this, m3, null);
- return;
- } catch (RuntimeException localRuntimeException) {
- throw localRuntimeException;
- } catch (Throwable localThrowable) {
- throw new UndeclaredThrowableException(localThrowable);
- }
- }
- static {
- try {
- // 通过反射拿到指定接口的方法
- m3 = Class.forName("proxy.proxy2.Engineer").getMethod("coding",
- new Class[0]);
- } catch (NoSuchMethodException localNoSuchMethodException) {
- throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
- } catch (ClassNotFoundException localClassNotFoundException) {
- throw new NoClassDefFoundError(
- localClassNotFoundException.getMessage());
- }
- }
- }
如果我们把通过反编译得到的 class 文件写成一个 Java 类, 调用它同样可以实现代理功能.
- Engineer engineer = new JavaEngineer("XiaoMing");
- ProxyHandler handler = new ProxyHandler(engineer, "老王");
- $Proxy0 p0 = new $Proxy0(handler);
- p0.coding();
- System.out.println("------------------ 输出结果 -----------------------");
- // 老王: 整理客户需求中...
- // 老王: 输出需求文档, 交给码农去完成!
- //XiaoMing : 正在努力的 coding...
- //XiaoMing : 终于改完了!
- // 老王: 哎, 需求变好多次了. 得安抚一下这个码农才好!
总结
关于动态代理创建对象的过程, 我们大概可以这样总结一下.
1, 通过实现 InvocationHandler 接口创建自己的调用处理器
2, 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 创建动态代理类
3, 通过反射机制获取动态代理类的构造函数, 其参数类型是调用处理器接口类型
4, 通过构造函数创建代理类实例, 此时需将调用处理器对象作为参数被传入
Proxy 类的 newProxyInstance 方法封装了 2-4, 只需 2 步就完成了代理对象的创建. 生成的代理对象集成 Proxy, 实现被代理对象接口. 被代理对象接口的方法实际调用处理器的 invoke 方法, 而处理器的 invoke 方法利用反射调用的是被代理对象的的方法 method.invoke(target,args).
来源: https://juejin.im/post/5c7ba97351882555a82232b6