JDK动态代理

代理是一种结构型设计模式, 让你能提供真实服务对象的替代品给客户端使用。 代理接收客户端的请求并进行一些处理 (访问控制和缓存等), 然后再将请求传递给服务对象。

代理对象拥有和服务对象相同的接口, 这使得当其被传递给客户端时可与真实对象互换。

——Copy From refactoring.guru

一、静态代理

静态代理代码很简单,但其设计思想很重要。示例如下:

点击查看代码
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
//UserService.java
public interface UserService {
User getUser(int userId);
}

//UserServiceImpl.java UserService的实现类
public class UserServiceImpl implements UserService {
@Override
public User getUser(int userId) {
return new User(userId, xxx, xxx);
}
}

//UserServiceProxy.java
public class UserServiceProxy implements UserService {
private UserService delegate;

public UserServiceProxy(UserService target) {
this.delegate = target; //被代理对象
}

@Override
public User getUser(int userId) {
//前置增强
System.out.println("before get user");
User user = this.delegate.getUser(userId);
//后置增强
System.out.println("after get user");
return user;
}
}

//Test.java
public class Test {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = new UserServiceProxy(target);
User user = proxy.getUser(1001);
}
}

二、JDK动态代理

JDK动态代理可以看成一个“万能中介”,它最终实现方法粒度的增强,它的InvocationHandler是“万能的”,根据我们传入的target以及调用不同的方法实现不同的增强效果。需要注意的是JDK动态代理只能代理接口,这与它背后的实现逻辑有关系。

2.1、为什么需要动态代理

静态代理虽然能够实现大多数功能,但每个目标对象都需要生成一个代理类,当被代理类太多时,开发和维护代理类将是一件很可怕的事情,同时静态代理还不具备处理动态生成的class的能力。基于上诉痛点,各种动态代理技术应运而生,JDK动态代理便是其中之一。

2.2、JDK动态代理示例

点击查看代码
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
//接口&实现类参考上面静态代理的例子

//DynamicProxy.java
public class DynamicProxy implements InvocationHandler {

//真正的实现类,或者说被代理对象(不是必须的)
private Object target;

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

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置增强
System.out.println("before invoke");
Object res = method.invoke(target, args);
//后置增强
System.out.println("after invoke");
return res;
}
}

//Test.java
public class Test {
public static void main(String[] args) {
//代理类对象,注意它不是我们的目标代理对象,可以理解为它只是个中介
InvocationHandler handler = new DynamicProxy(new UserServiceImpl());

//动态生成最终目标代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
UserServiceImpl.class.getClassLoader(),
UserServiceImpl.class.getInterfaces(),
handler
);
proxy.getUser(123);
}
}

我们可以看到,JDK动态代理核心就在于实现InvocationHandler,重写invoke方法。

2.3、InvocationHandler接口

  1. InvocationHandler可以理解为一个”执行器“,每个被代理的对象中的所有方法都要经过它,由它决定下一步怎么处理,所以实现InvocationHandler是整个JDK动态代理的核心,不同的功能可以使用不同的InvocationHandler来实现。

  2. InvocationHandler中的核心方法invoke

    2.1、Object proxy: 该参数是 最终的目标代理对象,一般代理用不到它,在具有链式操作的代理对象中可以直接返回该对象,使得最终代理对象也可以使用链式操作。

    2.2 、Method method:该参数是我们要调用的目标方法,一般会使用method.invoke(target, args)继续调用被代理对象的具体实现。

    2.3、Object[] args: 该参数是目标方法参数。

    2.4、返回值:由于该方法是“万能的”,因此我们可以根据传入的参数、调用的方法、目标对象不同而返回不同的结果。

  3. 注意事项

    3.1、在调用目标方法时,传入的实例对象不可以是第一个参数proxy,该参数是最终生成的代理对象,如果继续在它上面调用方法的话就又会回到该方法,从而形成死循环。

    3.2、JDK动态代理只能代理java接口(interface)。

2.4、JDK动态代理在MyBatis中的运用

我们都知道Mybatis只要一个接口和一个XML Mapper(或注解),就能完成增删改查操作,其实Mybatis也是利用JDK动态代理生成的Mapper实例进行调用,下面直接看源码:

这就是生成Mapper实例的代码,其中的mapperInterface就是我们写的Mapper接口,但有一个mapperProxy比较重要,它就是我们说的“万能中介”,看看它是怎么实现的:

这个MapperProxy也实现了nvocationHandler接口,但与上面介绍的些许不同,之前的动态代理需要一个接口的实现类,但这里并没有实现类。其实JDK动态代理并不要求实现类,在invoke方法内部我们完全可以根据三个参数去“创造”结果,然后返回即可。

可以看到,Mybatis根据传入的方法“创造”了另一个“方法”MapperMethod,然后去执行这个新“方法”,最终返回结果。这个创造方法的过程就是根据我们之前的Mapper.xml或注解获得SQL语句,然后根据传入的参数构造statement,最后就直接执行了,具体参考Mybatis源码。

2.5、JDK动态代理底层原理

  1. 先看生成的代理类

    1.1、保存代理类

    1
    2
    3
    4
    5
    6
    //方法1
    byte[] bytes = ProxyGenerator.generateProxyClass("stuMapperProxy", stuMapper.getClass().getInterfaces());//获取代理类bytes数组
    com.google.common.io.Files.write(bytes, new File("proxy$0.class"));//保存bytes数组

    //方法2:代码中加入下面一条属性,默认保存的class文件在项目根目录下com/sun/proxy下面
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

    1.2、反编译代理类(以下代码精简掉无关逻辑及异常处理部分)

    点击查看代码
    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
    public final class stuMapperProxy extends Proxy implements StuMapper {
    private static Method m0; //hashCode
    private static Method m1; //equals
    private static Method m2; //toString
    private static Method m3; //业务方法selectAll
    private static Method m4; //业务方法addStudent

    // 代理类继承自Proxy,Proxy中有一个InvocationHandler成员,它就是我们需要实现的那一部分。
    public stuMapperProxy(InvocationHandler var1) throws {
    super(var1);
    }
    //equals方法,通过InvocationHandler调用我们的目标对像的equals范方法
    //下面的其他方法同理。
    public final boolean equals(Object var1) throws {
    //super.h就是InvocationHandler实例,其中的invoke方法被我们增强过
    //this: 当前代理对象
    //m1: 当前方法
    //m2: 方法参数
    return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
    }

    public final String toString() throws {
    return (String) super.h.invoke(this, m2, (Object[]) null);
    }
    //代理的业务方法
    public final List selectAll() throws {
    return (List) super.h.invoke(this, m3, (Object[]) null);
    }
    //代理的业务方法
    public final void addStudent(int var1, String var2, int var3, String var4) throws {
    super.h.invoke(this, m4, new Object[]{var1, var2, var3, var4});
    }

    public final int hashCode() throws {
    return (Integer) super.h.invoke(this, m0, (Object[]) null);
    }

    //前3个方法{m0,m1,m2}固定为hashCode, equals, toString
    //从后面开始就是我们定义的业务方法
    //这些方法都是通过反射的方式获取到的
    static {
    m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    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.sptest.mapper.StuMapper").getMethod("selectAll");
    m4 = Class.forName("com.sptest.mapper.StuMapper").getMethod("addStudent", Integer.TYPE, Class.forName("java.lang.String"), Integer.TYPE, Class.forName("java.lang.String"));
    }
    }

    这里注意:生成的代理类已经继承了Proxy类,无法再继承其他类,所以只能实现业务接口,这也就是为什么JDK只能代理接口的原因。

    1.3、代理类调用流程

  2. 代理对象生成过程(省略安全检查及异常处理代码)

    2.1、从newProxyInstance入手

    点击查看代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
    final Class<?>[] intfs = interfaces.clone();
    //获取代理类[重要]
    Class<?> cl = getProxyClass0(loader, intfs);
    //获取代理类构造方法
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    //通过构造方法创建代理对象并返回
    return cons.newInstance(new Object[]{h});
    }

    2.2、生成代理类

    点击查看代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
    if (interfaces.length > 65535) {
    throw new IllegalArgumentException("interface limit exceeded");
    }

    // If the proxy class defined by the given loader implementing 如果代理类已经被给定的类加载器加载了
    // the given interfaces exists, this will simply return the cached copy; 并且给定的接口也存在,则直接从缓存中拷贝一份返回即可
    // otherwise, it will create the proxy class via the ProxyClassFactory 否则,就通过ProxyClassFactory创造一个代理类
    return proxyClassCache.get(loader, interfaces);
    }

    //这个proxyClassCache是通过如下方式初始化的,proxyClassCache仅仅是一个k-v缓存,获取值时如果不存在就从给定的factory创造一个
    //给定的factory有KeyFactory,用于获取生成key, ProxyClassFactory用于生成value,也就是代理类
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

    2.3、来到ProxyClassFactory

    点击查看代码
    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
    private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    // 生成的代理类名前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    // 代理类名后缀生成器,其实就是一个递增数字,用AtomicLong实现
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    //需要代理的接口
    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);

    //代理类包名
    String proxyPkg = null;
    int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

    //省略其他包名生成,策略...

    if (proxyPkg == null) {
    //使用默认包名 com.sun.proxy package
    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }

    //生成类名后缀(数字),最终就像$Proxy0这样
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;

    //生成代理类字节码【核心】
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);

    //装载代理类,返回class对象
    return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
    }
    }

    2.4、继续探索ProxyGenerator.generateProxyClass

    点击查看代码
    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
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    //先看看ProxyGenerator都有些什么
    //定义了一堆常量
    private static final int CLASSFILE_MAJOR_VERSION = 49;
    private static final int CLASSFILE_MINOR_VERSION = 0;
    private static final int CONSTANT_UTF8 = 1;
    ...
    //定义了一堆指令
    private static final int opc_iload = 21;
    private static final int opc_lload = 22;
    ...
    private static final String superclassName = "java/lang/reflect/Proxy"; //生成的代理类的父类名称
    private static final String handlerFieldName = "h"; //生成的代理类的父类(Proxy)中InvocationHandler成员的名字(代理类需要使用)
    //是否需要保存生成的文件,系统变量设置了 sun.misc.ProxyGenerator.saveGeneratedFiles 为 true 就保存代理类
    private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
    private static Method hashCodeMethod; //生成的代理类的hashCode方法
    private static Method equalsMethod; //生成的代理类的equals方法
    private static Method toStringMethod; //生成的代理类的toString方法
    private String className; //生成的代理类全类名 如:com.sun.proxy.$Proxy0
    private Class<?>[] interfaces; //生成的代理类需要实现的接口,就是我们代理的接口
    private int accessFlags;//生成的代理类的访问标志(Modifiers),一般是17(1+16),1:public, 16:final
    private ProxyGenerator.ConstantPool cp = new ProxyGenerator.ConstantPool();//代理类常量池,一些符号引用等
    private List<ProxyGenerator.FieldInfo> fields = new ArrayList();//生成的代理类的成员变量
    private List<ProxyGenerator.MethodInfo> methods = new ArrayList();//生成的代理类的方法列表(这是新生成的方法)
    private Map<String, List<ProxyGenerator.ProxyMethod>> proxyMethods = new HashMap();//被代理的方法,方法签名与方法之间的映射(这是目标方法,最终用成员变量表示)
    private int proxyMethodCount = 0;//代理方法数量(一般是3+业务方法数)

    //核心方法:generateProxyClass,注意这个方法是public static 的,我们在其他地方也可以直接使用
    public static byte[] generateProxyClass(final String className, Class<?>[] interfaces, int accessFlags) {
    ProxyGenerator generator = new ProxyGenerator(className, interfaces, accessFlags);
    //生成代理类class文件【核心】
    final byte[] classFile = generator.generateClassFile();
    if (saveGeneratedFiles) {
    //保存文件
    }
    return classFile;
    }

    //核心方法:generateClassFile
    private byte[] generateClassFile() {
    //生成proxy代理类的hashcode,equals,toString方法
    addProxyMethod(hashCodeMethod, Object.class);
    addProxyMethod(equalsMethod, Object.class);
    addProxyMethod(toStringMethod, Object.class);

    //添加各个接口的方法
    for (int i = 0; i < interfaces.length; i++) {
    Method[] methods = interfaces[i].getMethods();
    for (int j = 0; j < methods.length; j++) {
    addProxyMethod(methods[j], interfaces[i]);
    }
    }

    //检查返回类型
    for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
    checkReturnTypes(sigmethods);
    }

    //添加生成的构造方法
    methods.add(generateConstructor());

    for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
    for (ProxyMethod pm : sigmethods) {
    //将目标方法放到成员变量中
    fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
    //新增一个与之对应的方法(签名一致),然后在该方法中调用目标方法
    methods.add(pm.generateMethod());
    }
    }

    //添加静态初始化块,就是生成代理类中的静态块
    methods.add(generateStaticInitializer());

    //构造常量池(cp=constant pool)
    cp.getClass(dotToSlash(className));//dotToSlash点转换为斜线
    cp.getClass(superclassName);
    for (int i = 0; i < interfaces.length; i++) {
    cp.getClass(dotToSlash(interfaces[i].getName()));
    }

    //常量池设置为只读模式
    cp.setReadOnly();

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream dout = new DataOutputStream(bout);

    //以下是class文件的结构,想深入了解的话可以看深入Java虚拟机
    // u4 magic number;
    dout.writeInt(0xCAFEBABE);
    // u2 minor_version;
    dout.writeShort(CLASSFILE_MINOR_VERSION);
    // u2 major_version;
    dout.writeShort(CLASSFILE_MAJOR_VERSION);
    // write constant pool
    cp.write(dout);
    // u2 access_flags;
    dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
    // u2 this_class;
    dout.writeShort(cp.getClass(dotToSlash(className)));
    // u2 super_class;
    dout.writeShort(cp.getClass(superclassName));
    // u2 interfaces_count;
    dout.writeShort(interfaces.length);
    // u2 interfaces[interfaces_count];
    for (int i = 0; i < interfaces.length; i++) {
    dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
    }
    // u2 fields_count;
    dout.writeShort(fields.size());
    // field_info fields[fields_count];
    for (FieldInfo f : fields) {
    f.write(dout);
    }
    // u2 methods_count;
    dout.writeShort(methods.size());
    // method_info methods[methods_count];
    for (MethodInfo m : methods) {
    m.write(dout);
    }
    // u2 attributes_count;
    dout.writeShort(0); // (no ClassFile attributes for proxy classes)
    return bout.toByteArray();
    }
文章作者: Jack.Charles
文章链接: https://blog.zjee.me/2020/01/05/jdk-proxy/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 江影不沉浮