概念

代理模式 Java 当中最常用的设计模式之一。其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。而Java的代理机制分为静态代理和动态代理,而这里我们主要重点学习 java 自带的 jdk 动态代理机制。

静态代理

在讲动态代理之前,我们先了解下什么是静态代理。静态代理在编译使用时,定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。我们用一个出租房子作为实例讲解。

==静态代理的本质就是多态。==

  • 定义一个接口:

    1
    2
    3
    public interface Rental {
    public void sale();
    }
  • 委托类,实现接口的方法

    1
    2
    3
    4
    5
    6
    public class Entrust implements Rental{
    @Override
    public void sale() {
    System.out.println("出租房子");
    }
    }
  • 代理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class AgentRental implements Rental{
    private Rental target; // 被代理对象
    public AgentRental(Rental target) {
    this.target = target;
    }
    @Override
    public void sale() {
    System.out.println("房子出租价位有1k-3k"); // 增加新的操作
    target.sale(); // 调用Entrust委托类的sale方法
    }
    }
  • 测试类,生成委托类实例对象,并将该对象传入代理类构造函数中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Test {
    // 静态代理使用示例
    public static void consumer(Rental subject) {
    subject.sale();
    }
    public static void main(String[] args) {
    Rental test = new Entrust();
    System.out.println("---使用代理之前---");
    consumer(test);
    System.out.println("---使用代理之后---");
    consumer(new AgentRental(test));
    }
    }

通过上面的例子,我们可以看见静态代理的优点:

我们可以在不改变 Entrust 委托类源代码的情况下 ,通过 AgentRental 代理类来修改 Entrust 委托类的功能,从而实现“代理”操作。在进行代理后,自定义说明房子出租价位有1k-3k的操作方法。

但这个是我们通过代理类进行实现更改的方法,如果当我们需要过多的代理类对委托类进行修改的情况下,则可能出现下图情况:

由此可以我们得知此静态代理的缺点:

  1. 当我们的 接口类需要增加和删除方式 的时候,委托类和代理类都需要更改,不容易维护。

  2. 同时如果需要 代理多个类 的时候,每个委托类都要编写一个代理类,会导致代理类繁多,不好管理。

因为 java 静态代理是对类进行操作的,我们需要一个个代理类去实现对委托类的更改操作,针对这个情况,我们可以利用动态代理来解决,通过程序运行时自动生成代理类。

动态代理

简介

Java 动态代理位于 Java.lang.reflect 包下,我们一般就仅涉及 Java.lang.reflect.Proxy 类与 InvocationHandler 接口,使用其配合反射,完成实现动态代理的操作。

  • InvocationHandler 接口:负责提供调用代理操作。

是由代理对象调用处理器实现的接口,定义了一个 invoke() 方法,每个代理对象都有一个关联的接口。当代理对象上调用方法时,该方法会被自动转发到InvocationHandler.invoke() 方法来进行调用。

  • Proxy 类:负责动态构建代理类

提供四个静态方法来为一组接口动态生成的代理类并返回代理类的实例对象。
![](imgs/Pasted image 20230111152239.png)

getProxyClass(ClassLoader,Class<?>...)
获取指定类加载器和动态代理类对象。

newProxyInstance(ClassLoader,Class<?>[],InvocationHandler)
指定类加载器,一组接口,调用处理器;

isProxyClass(Class<?>)
判断获取的类是否为一个动态代理类;

getInvocationHandler(Object)
获取指定代理类实例查找与它相关联的调用处理器实例;

实现过程

  1. 使用 java.lang.InvocationHandler 接口创建自定义调用处理器,由它来实现 invoke 方法,执行代理函数;

  2. 使用 java.lang.reflect.Proxy 类指定一个 ClassLoader,一组 interface 接口和一个 InvocationHandler

  3. 通过反射机制获得动态代理类的构造方法,其唯一参数类型是调用处理器接口类型;

  4. 调用 java.lang.reflect.Proxy.newProxyInstance() 方法,分别传入类加载器,被代理接口,调用处理器;创建动态代理实例对象。

  5. 通过代理对象调用目标方法;

示例代码

委托类和接口与上面的静态代理是一致的。只是修改代理类和测试类。

==关键:==
通过下面代理类的代码可以发现,代理类增强的功能与委托类和接口无关(代码中不存在与委托类相关联的代码),在静态代理的时候,如果我们想对多个不同接口下的委托类做 ==同样== 的功能增强(也就是下面的 System.out.println("房子的出租价位有1k-3k") ),我们只需要写一个代理类,而不用对每个接口下的委托类都写一个代理类。这就是动态代理的好处!我们只需要在使用时(这里是测试中)指定我们要对哪个委托类进行代理即可(代理类会自动判断该委托类是哪个接口的)。

  • 代理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.just.test;

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;

    public class TestAgent implements InvocationHandler {
    private Object target;

    public TestAgent(Object target) {
    this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("房子的出租价位有1k-3k");
    Object result = method.invoke(target, args);
    return result;
    }
    }
  • 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package com.just.test;

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;

    public class Test {
    public static void main(String[] args) {
    // Rental test = new Entrust();
    // test.sale();
    // (new AgentRental(test)).sale();
    // 获取委托类的实例对象
    Entrust testEntrust = new Entrust();
    // 获取CLassLoader
    ClassLoader classLoader = testEntrust.getClass().getClassLoader();
    // 获取所有接口
    Class[] interfaces = testEntrust.getClass().getInterfaces();
    // 获取一个调用处理器
    InvocationHandler invocationHandler = new TestAgent(testEntrust);
    // 查看生成的代理类
    //小于等于jdk1.8的版本
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
    //大于jdk1.8的版本
    System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
    // 创建代理对象
    Rental proxy = (Rental) Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
    // 调用代理对象的sayHello()方法
    proxy.sale();
    }
    }

这里我们可以看见我们生成的动态代理类的字节码文件,放置在程序根目录下的 com.sun.proxy.$Proxy0.class 文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import com.just.test.Rental;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Rental {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final void sale() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.just.test.Rental").getMethod("sale");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

在这里生成的 $Proxy0 代理类中,我们可以清楚知道动态代理的实现过程。实际上我们在创建代理对象时,就是通过通过反射来获取这个类的构造方法,然后来创建的代理实例。

动态代理的原理

  1. 通过委托类的 classLoaderinterfaces 来构建一个新生成的代理类。

  1. 通过反射,获取新代理类的 constructor ,从而得到新代理类的一个实例。
    这里是通过 InvocationHandler 实例化新的代理类,为的是能够使用原来自己写的代理类。
  2. 在新生成的代理类的实例中调用 InvocationHandlerinvoke() 方法。
    这里的 super.h 就是 InvocationHandler ,也就是原来自己写的代理类。

  1. InvocationHandler (或者说是代理类)中的 invoke() 通过反射调用委托类的方法。method.invoke(target, args);

注意这两个 invoke() 方法不一样,一个是 InvocationHandlerinvoke(Object Proxy, Method method, Object[] args) 方法,一个是反射中 Method 类的 invoke(Object object, Object[] args) 方法。

参考文章

1
https://xz.aliyun.com/t/9197#toc-0