05. 动态代理

a view of a mountain range with a moon in the sky

动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程(AOP)。

实现动态代理的方式很多,比如 JDK 自身提供的动态代理,就是主要利用了反射机制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似 ASM、cglib(基于 ASM)、Javassist 等。

img

静态代理

静态代理就是设计模式中的代理模式:主要为其他对象提供一种代理,并控制对这个对象的访问。

示例:

定义Subject抽象类,包含RealSubject和Proxy的公共接口,这样就在任何使用RealSubject的地方都可以使用Proxy。

1
2
3
public abstract class Subject {
public abstract void request();
}

RealSubject定义Proxy,代理的真正实体:

1
2
3
4
5
6
public class RealSubject extends Subject{
@Override
public void request() {
System.out.println("真实的请求");
}
}

Proxy代理类保存了一个引用,使得代理可以访问真实的实体,并提供一个和Subjcet接口相同的接口,这样代理就可以用来代替实体:

1
2
3
4
5
6
7
8
9
10
public class Proxy extends Subject{
private RealSubject realSubject;
@Override
public void request() {
if (null == realSubject){
realSubject = new RealSubject();
}
realSubject.request();
}
}

最后,实现:

1
2
3
4
5
6
public class Test {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.request();
}
}

结果:

1
2
3
真实的请求

Process finished with exit code 0

静态代理的问题

静态代理模式固然在访问无法访问的资源,能够增强现有的接口业务功能方面有很大有点,但是大量使用这种静态代理,会使我们的系统内的类规模增大,并且不易维护;

从本质上看,示例中Proxy和RealSubject功能一样,Proxy只是中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。

JDK动态代理

为了解决静态代理的问题,动态代理思想提了出来。

以示例为例子,动态代理在运行状态中,需要代理的地方,会根据 Subject 和 RealSubject,动态地创建一个 Proxy,用完之后,就会销毁,这样就可以避免了 Proxy 角色的 class 在系统中冗杂的问题了。

img

动态代理属于经典的代理模式,需要引入一个InvocationHandler,它负责统一管理所有的方法调用。(其实就是一个接口,这个接口就一个invoke方法,参数固定的三个参数,下面会讲)。

动态代理步骤

  1. 获取RealSubject上的所有接口列表(执行invoke()方法的时候要用);
  2. 确定要生成的代理类的类名,默认为:com.sum.proxy.$ProxyXXXX;
  3. 根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码;
  4. 将对应的字节码转换成对应的class对象;
  5. 创建InvocationHandler实例handler,用来处理Proxy的所有方法调用
  6. Proxy的class对象以创建handler对象为参数,实例化一个Proxy对象。

从上面可以看出,JDK 动态代理的实现是基于实现接口的方式,使得 Proxy 和 RealSubject 具有相同的功能。

但其实还有一种思路:通过继承。即:让 Proxy 继承 RealSubject,这样二者同样具有相同的功能,Proxy 还可以通过重写 RealSubject 中的方法,来实现多态。CGLIB 就是基于这种思路设计的。

在上述步骤中,最重要的有两个类,一个是InvocationHandler接口,另外一个是Proxy类。

InvocationHandler接口

1
2
3
4
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

只有这一个方法。每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个Handler,当我们通过代理对象调用一个方法的时候,这个方法调用会被转发有InvocationHandler这个接口的invoke方法来调用。

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

参数说明:

  • proxy - 代理的真实对象。
  • method - 所要调用真实对象的某个方法的 Method 对象
  • args - 所要调用真实对象某个方法时接受的参数

Proxy类

Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

1
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

这个方法的作用就是得到一个动态的代理对象。

参数说明:

  • loader - 一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载。
  • interfaces - 一个 Class<?> 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
  • h - 一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上

示例

首先,定义Subject接口,声明两个方法:

1
2
3
4
public interface Subject {
void hello(String str);
String bey();
}

定义真实对象RealSubject类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RealSubject implements Subject{

@Override
public void hello(String str) {
System.out.println("hello " + str);
}

@Override
public String bey() {
System.out.println("Goodbye!");
return "Over";
}
}

定义动态代理类,实现 InvocationHandler 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class InvocationHandlerDemo implements InvocationHandler {
// 这个就是我们要代理的真实对象
private Object subject;

// 构造方法,给我们要代理的真实对象赋初值
public InvocationHandlerDemo(Object subject) {
this.subject = subject;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在代理真实对象前我们可以添加一些自己的操作
System.out.println("before method");
System.out.println("call method " + method);
// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object obj = method.invoke(subject, args);
// 在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after method");
System.out.println();
return obj;
}
}

最后,实现Client类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Client {
public static void main(String[] args) {
// 代理的真实对象
Subject realSubject = new RealSubject();
// 代理哪个真实对象,将该对象传进去,最后通过该真实对象调用方法
InvocationHandlerDemo invocationHandlerDemo = new InvocationHandlerDemo(realSubject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Subject subject = (Subject) Proxy.newProxyInstance(invocationHandlerDemo.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), invocationHandlerDemo);
System.out.println(subject.getClass().getName());
subject.hello("world");
String bey = subject.bey();
System.out.println("result: " + bey);
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
com.sun.proxy.$Proxy0 
before method
call method public abstract void com.example.basejava.testproxy.dynamicProxy.Subject.hello(java.lang.String)
hello world
after method

before method
call method public abstract java.lang.String com.example.basejava.testproxy.dynamicProxy.Subject.bey()
Goodbye!
after method

result: Over

Process finished with exit code 0

输出说明:

com.sun.proxy.$Proxy0?

为什么会返回代理对象的类名会是这样的?

1
2
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);

可能我以为返回的这个代理对象会是 Subject 类型的对象,或者是 InvocationHandler 的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为 Subject 类型的对象?

原因就是:在 newProxyInstance 这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是 Subject 类型,所以就可以将其转化为 Subject 类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在 jvm 运行时动态生成的一个对象,它并不是我们的 InvocationHandler 类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy 为中,最后一个数字表示对象的标号

接着我们来看看这两句

1
2
subject.hello("world");
String bey = subject.bey();

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的 invoke 方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject 类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的 invoke 方法去执行。

在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作(与AOP切面的环绕通知类似),同时我们看到我们的这个 method 对象是这样的:

1
2
public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()

正好就是我们的 Subject 接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的 invoke 方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

动态代理总结

代理类与委托类实现同一接口,主要是通过代理类实现 InvocationHandler 并重写 invoke 方法来进行动态代理的,在 invoke 方法中将对方法进行处理。

JDK 动态代理特点:

  • 优点:相对于静态代理模式,不需要硬编码接口,代码复用率高。
  • 缺点:强制要求代理类实现 InvocationHandler 接口。

CGLIB动态代理

这里只是简单介绍CGlib的动态代理

CGLIB 提供了与 JDK 动态代理不同的方案。很多框架,例如 Spring AOP 中,就使用了 CGLIB 动态代理。

CGLIB 底层,其实是借助了 ASM 这个强大的 Java 字节码框架去进行字节码增强操作。

CGLIB 动态代理的工作步骤:

  • 生成代理类的二进制字节码文件;
  • 加载二进制字节码,生成 Class 对象( 例如使用 Class.forName() 方法 );
  • 通过反射机制获得实例构造,并创建代理类对象。

CGLIB 动态代理特点:

优点:使用字节码增强,比 JDK 动态代理方式性能高。可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口。

缺点:不能对 final 类以及 final 方法进行代理。

参考

动态代理