Java基础:Java动态代理

文章发布时间:

最后更新时间:

文章总字数:
2.5k

预计阅读时间:
9 分钟

Java基础:Java动态代理

Java反射提供了一种类动态代理机制,可以通过代理接口实现类来完成程序无侵入式扩展。

动态代理基础

首先看看class和interface有什么不同,非abstract的class可以被实例化,而interface不能实例化,必须由类实现,其变量总是通过某个实例向上转型并赋值给接口类型变量。

静态情况

先定义接口:

1
2
3
public interface Hello {
void morning(String name);
}

编写实现类并创建实例调用方法

1
2
3
4
5
6
7
public class HelloWorld implements Hello {
public void morning(String name) {
System.out.println("Good morning, " + name);
}
}
Hello hello = new HelloWorld();
hello.morning("Bob");

动态代理情况

Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例,也就是说不需要编写实现类,直接在运行期创建interface实例。

我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if(method.getName().equals("morning")){
System.out.println("Good morning,"+args[0]);
}
return null;
}
};//定义调用接口方法时的处理逻辑,需要的参数是代理对象,方法名和参数
Hello hello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(),
new Class[]{Hello.class},
handler); //传入的参数需要ClassLoader,需要实现的接口,以及处理调用方法的InvocationHandler,然后直接实例化了接口,调用方法即可
hello.morning("Tom");

}

}
interface Hello {
void morning(String name);
}

在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    1. 使用的ClassLoader,通常就是接口类的ClassLoader
    2. 需要实现的接口数组,至少需要传入一个接口进去;
    3. 用来处理接口方法调用的InvocationHandler实例。
  3. 将返回的Object强制转型为接口。

动态代理实际上就是JVM在运行期间动态创建class字节码并加载的过程,其实并没有什么神奇的,只是JVM帮你自动写了一个类,简要代码如下(真实代码会在后面分析):

1
2
3
4
5
6
7
8
9
10
11
12
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning", String.class),
new Object[] { name });
}
}

通过反射来调用了InvocationHandler中定义的invoke方法,这个类其实并没有源码,JVM自动生成了字节码并注册。

动态代理API

创建动态代理类会使用到java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。java.lang.reflect.Proxy主要用于生成动态代理类Class、创建代理类实例,该类实现了java.io.Serializable接口。

java.lang.reflect.Proxy类主要方法如下:

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
package java.lang.reflect;

import java.lang.reflect.InvocationHandler;

public class Proxy implements java.io.Serializable {

// 省去成员变量和部分类方法...

/**
* 获取动态代理处理类对象
*
* @param proxy 返回调用处理程序的代理实例
* @return 代理实例的调用处理程序
* @throws IllegalArgumentException 如果参数不是一个代理实例
*/
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException {
...
}

/**
* 创建动态代理类实例
*
* @param loader 指定动态代理类的类加载器
* @param interfaces 指定动态代理类的类需要实现的接口数组
* @param h 动态代理处理类
* @return 返回动态代理生成的代理类实例
* @throws IllegalArgumentException 不正确的参数异常
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException {
...
}

/**
* 创建动态代理类
*
* @param loader 定义代理类的类加载器
* @param interfaces 代理类要实现的接口列表
* @return 用指定的类加载器定义的代理类,它可以实现指定的接口
*/
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) {
...
}

/**
* 检测某个类是否是动态代理类
*
* @param cl 要测试的类
* @return 如该类为代理类,则为 true,否则为 false
*/
public static boolean isProxyClass(Class<?> cl) {
return java.lang.reflect.Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}

/**
* 向指定的类加载器中定义一个类对象
*
* @param loader 类加载器
* @param name 类名
* @param b 类字节码
* @param off 截取开始位置
* @param len 截取长度
* @return JVM创建的类Class对象
*/
private static native Class defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);

}

java.lang.reflect.InvocationHandler接口用于调用Proxy类生成的代理类方法,该类只有一个invoke方法。

java.lang.reflect.InvocationHandler接口代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package java.lang.reflect;

import java.lang.reflect.Method;

/**
* 每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
*/
public interface InvocationHandler {

/**
* 在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
* @param proxy 在其上调用方法的代理实例
* @param method 对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
* @param args 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer
或 java.lang.Boolean)的实例中。
* @return 从代理实例的方法调用返回的值**/

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

}

使用Proxy动态加载类

在看Proxy源码的时候,发现了一个令人感兴趣的native方法:

image-20230904113612632

看这个方法的名字和传入的参数也知道这是一个可以通过字节码动态加载类的方法,和我们之前分析过的ClassLoader和Unsafe里面的是一样的,于是我们也可以通过反射调用这个defineClass0方法来动态加载类并创建类对象(在动态代理中,defineClass0是用来向JVM动态注册JVM自动生成的字节码):

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
package com.anbai.sec.proxy;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_BYTES;
import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_NAME;

public class ProxyDefineClassTest {

public static void main(String[] args) {
// 获取系统的类加载器,可以根据具体情况换成一个存在的类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();

try {
// 反射java.lang.reflect.Proxy类获取其中的defineClass0方法
Method method = Proxy.class.getDeclaredMethod("defineClass0", new Class[]{
ClassLoader.class, String.class, byte[].class, int.class, int.class
});

// 修改方法的访问权限
method.setAccessible(true);

// 反射调用java.lang.reflect.Proxy.defineClass0()方法,动态向JVM注册
// com.anbai.sec.classloader.TestHelloWorld类对象
Class helloWorldClass = (Class) method.invoke(null, new Object[]{
classLoader, TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length
});

// 输出TestHelloWorld类对象
System.out.println(helloWorldClass);
} catch (Exception e) {
e.printStackTrace();
}
}

}

其实步骤也是和之前一样的。

$ProxyXXX类代码分析

java.lang.reflect.Proxy类是通过创建一个新的Java类(类名为com.sun.proxy.$ProxyXXX),然后将其字节码注册到JVM的方式来实现无侵入的类方法代理功能。

动态代理生成出来的类有如下技术细节和特性:

  1. 动态代理的必须是接口类,通过动态生成一个接口实现类来代理接口的方法调用(反射机制)。
  2. 动态代理类会由java.lang.reflect.Proxy.ProxyClassFactory创建。
  3. ProxyClassFactory会调用sun.misc.ProxyGenerator类生成该类的字节码,并调用java.lang.reflect.Proxy.defineClass0()方法将该类注册JVM
  4. 该类继承于java.lang.reflect.Proxy并实现了需要被代理的接口类,因为java.lang.reflect.Proxy类实现了java.io.Serializable接口,所以被代理的类支持序列化/反序列化
  5. 该类实现了代理接口类,会通过ProxyGenerator动态生成接口类的所有方法,
  6. 该类因为实现了代理的接口类,所以当前类是代理的接口类的实例(proxyInstance instanceof FileSystemtrue),但不是代理接口类的实现类的实例(proxyInstance instanceof UnixFileSystemfalse)。
  7. 该类方法中包含了被代理的接口类的所有方法,通过调用动态代理处理类(InvocationHandler)的invoke方法获取方法执行结果。
  8. 该类代理的方式重写了java.lang.Object类的toStringhashCodeequals方法。
  9. 如果动过动态代理生成了多个动态代理类,新生成的类名中的0会自增,如com.sun.proxy.$Proxy0/$Proxy1/$Proxy2

总结:动态代理具有比静态代码更优越的特性,对我们的方法进行了无侵入的扩展。

主要使用场景:

  1. 统计方法执行所耗时间。
  2. 在方法执行前后添加日志。
  3. 检测方法的参数或返回值。
  4. 方法访问权限控制。
  5. 方法Mock测试。

其实这些场景都是对原有的方法进行了拓展。

一句话总结动态代理:运行期间JVM自动帮你实现了接口并完成注册,从而可以调用接口的方法而不需要静态运行前自己写类去实现接口,至于具体怎么实现就需要你通过invoke告诉他。