动态代理

  |  

摘要: 本文主要是关于动态代理的原理和应用。同时也是 Spring 的一个核心功能:面向切面编程(AOP)的原理。

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


问题背景

有一个 Greeting 接口,接口有一个 sayhello 方法,该方法接受用户名参数,向用户显示欢迎信息。

  • proxy/Greeting.java
1
2
3
4
5
package proxy;

public interface Greeting {
void sayHello(String name);
}

有一个实现类 GreegingImpl,实现 Greeting 接口:

  • proxy/GreetingImpl.java
1
2
3
4
5
6
7
package proxy;

public class GreetingImpl implements Greeting {
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
}

此时如果有了新的需求: 在执行 sayHello 方法的前后,记录一些日志信息。

比较直接的方法是修改 GreetingImpl 的实现,添加记录日志的代码。但后续很可能还有别的新需求,例如在调用 sayHello 之前要验证权限等等,又要改代码。

因此修改 GreetingImpl 的实现并不是好办法。


静态代理

可以写一个代理类,同样实现 Greeting 接口。只是把 sayHello 方法的调用委托给真正的 Greeting 对象,即 GreetingImpl 类的对象。这样就可以在 sayHello 方法调用前后增加一些记录动作。

  • proxy/GreetingProxy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package proxy;

import java.util.logging.Logger;
import java.util.logging.Level;

public class GreetingProxy implements Greeting {
private Logger logger = Logger.getLogger(this.getClass().getName());
private Greeting greetingObj;

public GreetingProxy(Greeting greetingObj) {
this.greetingObj = greetingObj;
}

public void sayHello(String name) {
logger.log(Level.INFO, "sayHello method starts...");
greetingObj.sayHello(name);
logger.log(Level.INFO, "sayHello method ends...");
}
}

客户端程序

  • proxy/Client.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package proxy;

public class Client {
public static void welcome(Greeting greeting, String name) {
greeting.sayHello(name);
}

public static void main(String[] args) {
Greeting greeting = new GreetingImpl();
// 不使用代理
welcome(greeting, "张三");
System.out.println("-----------------");
// 使用代理
Greeting greetingProxy = new GreetingProxy(greeting);
welcome(greetingProxy, "李四");
}
}

结果如下:

1
2
3
4
5
6
7
Hello, 张三
-----------------
Jun 15, 2022 6:27:22 PM proxy.GreetingProxy sayHello
INFO: sayHello method starts...
Hello, 李四
Jun 15, 2022 6:27:22 PM proxy.GreetingProxy sayHello
INFO: sayHello method ends...

代理对象 greetingProxy 代理真正的 GreetingImpl 对象来执行 sayHello。这使得 GreetingImpl 不必介入额外的需求中,可以专注于本身的职责,这是静态代理


动态代理

静态代理中,代理对象通过实现与目标对象相同的接口,来控制对目标对象的访问。

但是静态代理有弱点:代理对象的一个接口只服务于一种类型的对象,如果要代理的方法有很多,那么每个方法都进行代理的话,静态代理的代码量过大。

(1) InvocationHandler

Java 的反射 API 中有一个 InvocationHandler 接口,可以用于开发动态代理的类。这样就不必为特定对象与方法写特定的代理,而是让一个处理器(handler)服务于各个对象

InvocationHandler 只有一个方法:

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

在代理实例上调用该方法。参数 proxy 是代理实例,method 是与在代理实例上调用的接口方法对应的 Method 实例,arg 是调用方法传入的参数。

(2) Proxy

反射 API 中还有一个 Proxy 类,其中的 newProxyInstance 静态方法用于创建一个指定接口的代理类的实例。该实例会把对接口方法的调用为派给处理器的 invoke 方法。

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

参数含义如下:

  • loader 是定义代理类的类加载器
  • interfaces 是代理类要实现的接口列表
  • h 是处理器,对接口方法的调用会委派给处理器的 invoke 方法

动态代理的流程

要使用 Java 的动态代理机制,需要以下流程:

step1: 编写一个类实现 InvocationHandler 接口。
step2: 调用 Proxy 类的静态方法 newProxyInstance 创建一个代理类的实例。
step3: 程序中使用这个代理类的实例调用接口中的方法。附加的动作在处理器的 invoke 方法中添加。

InvocationHandler 接口

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
package proxy;

import java.util.logging.Logger;
import java.util.logging.Level;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class LogHandler implements InvocationHandler {
private Logger logger = Logger.getLogger(this.getClass().getName());
private Object originalObj;

public Object bind(Object obj) {
this.originalObj = obj;
return Proxy.newProxyInstance(originalObj.getClass().getClassLoader()
,originalObj.getClass().getInterfaces()
,this
);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;

logger.log(Level.INFO, "method starts..." + method);
result = method.invoke(originalObj, args);
logger.log(Level.INFO, "method ends..." + method);

return result;
}
}

有以下几个注意点:

(1) 动态代理要求所代理的类必须是某个接口的实现,originalObj.getClass().getInterfaces() 不能为空。
(2) InvocationHandlerinvoke 方法将在被代理类实例的方法调用之前触发。
(3) InvocationHandlerinvoke 方法会传入被代理对象的方法对应的 Method 对象与方法参数,实际执行的方法交由 method.invoke(originalObj, args),我们在其调用前后加上记录动作,method.invoke 方法调用传回的对象是实际方法执行后的返回结果。

客户端程序

1
2
3
4
5
6
7
8
9
10
11
package proxy;

public class Client {
public static void main(String[] args) {
Greeting greeting = new GreetingImpl();

// 使用动态代理
Greeting handler = (Greeting) new LogHandler().bind(greeting);
handler.sayHello("王五");
}
}

结果如下:

1
2
3
4
5
Jun 15, 2022 7:08:31 PM proxy.LogHandler invoke
INFO: method starts...public abstract void proxy.Greeting.sayHello(java.lang.String)
Hello, 王五
Jun 15, 2022 7:08:31 PM proxy.LogHandler invoke
INFO: method ends...public abstract void proxy.Greeting.sayHello(java.lang.String)

Share