如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
- 代理模式的定义
-
- 代理模式是一种设计模式,它为其他对象提供一种代理,以控制对这个对象的访问。代理对象可以在客户端和目标对象之间起到中介的作用,它可以在不改变目标对象接口的情况下,增强目标对象的功能或者对目标对象的访问进行控制。
- 代理模式的结构
-
- 主题(Subject)接口:定义了真实对象和代理对象共同的接口,客户端通过这个接口来访问真实对象和代理对象。例如,在一个文件读取系统中,主题接口可以是
FileReaderSubject
,它定义了读取文件内容的抽象方法,如readFile(String fileName)
。 - 真实主题(Real Subject):实现了主题接口,是真正完成业务逻辑的对象。比如,在文件读取系统中,真实主题可以是
FileReaderImpl
,它实现了FileReaderSubject
接口,具体实现了从文件系统中读取文件内容的方法。 - 代理(Proxy):同样实现了主题接口,代理对象内部包含了真实主题对象的引用。它可以在调用真实主题对象的方法之前或之后,添加额外的逻辑,如权限验证、日志记录等。在文件读取系统中,代理对象
FileReaderProxy
可以在调用FileReaderImpl
的readFile
方法之前,先检查用户是否有读取文件的权限。
- 主题(Subject)接口:定义了真实对象和代理对象共同的接口,客户端通过这个接口来访问真实对象和代理对象。例如,在一个文件读取系统中,主题接口可以是
- 代理模式的类型
-
- 静态代理
// 接口定义
interface Calculator {
int add(int a, int b);
}
// 真实主题(被代理类)
class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
}
// 代理类
class CalculatorProxy implements Calculator {
private Calculator calculator;
public CalculatorProxy(Calculator calculator) {
this.calculator = calculator;
}
@Override
public int add(int a, int b) {
System.out.println("在加法运算前进行日志记录");
int result = calculator.add(a, b);
System.out.println("在加法运算后进行日志记录");
return result;
}
}
-
-
- 定义:静态代理是在编译时期就已经确定了代理类和被代理类的关系。代理类和被代理类都需要实现同一个接口,代理类通过持有被代理类的实例来调用其方法,并在调用前后添加自己的逻辑。
- 示例:假设我们有一个接口
Calculator
,它有一个方法add(int a, int b)
用于计算两数之和。 - 在这个例子中,
CalculatorProxy
是CalculatorImpl
的代理类。当客户端调用CalculatorProxy
的add
方法时,它会先打印日志,然后调用CalculatorImpl
的add
方法,最后再打印日志。
-
-
- 动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 动态代理的调用处理器
class CalculatorInvocationHandler implements InvocationHandler {
private Object target;
public CalculatorInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在动态代理方法调用前进行日志记录");
Object result = method.invoke(target, args);
System.out.println("在动态代理方法调用后进行日志记录");
return result;
}
}
Calculator calculator = new CalculatorImpl();
Calculator proxy = (Calculator) Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class[]{Calculator.class},
new CalculatorInvocationHandler(calculator));
int result = proxy.add(1, 2);
-
-
- 定义:动态代理是在运行时动态地生成代理类。在Java中,主要通过
java.lang.reflect.Proxy
类和InvocationHandler
接口来实现动态代理。动态代理不需要像静态代理那样为每个被代理类都编写一个代理类,它可以根据需要动态地创建代理对象。 - 示例:还是以上面的
Calculator
接口为例,我们可以这样实现动态代理: - 客户端使用动态代理的代码如下:
- 在这个动态代理的例子中,
CalculatorInvocationHandler
是实现了InvocationHandler
接口的调用处理器。Proxy.newProxyInstance
方法会在运行时动态地生成一个代理对象,这个代理对象实现了Calculator
接口。当客户端调用代理对象的方法时,会触发InvocationHandler
的invoke
方法,在这个方法中可以添加代理逻辑,如日志记录,然后再调用真实对象(CalculatorImpl
)的方法。
- 定义:动态代理是在运行时动态地生成代理类。在Java中,主要通过
-
- 代理模式的应用场景
-
- 远程代理:用于访问远程对象,就好像访问本地对象一样。例如,在分布式系统中,客户端通过代理对象来调用远程服务器上的对象。代理对象负责处理网络通信等细节,如Java RMI(Remote Method Invocation)就使用了远程代理。
- 虚拟代理:当创建一个对象的代价比较大(如加载大型资源文件、初始化复杂对象等)时,可以使用虚拟代理。虚拟代理先创建一个简单的代理对象来代替真实对象,当真正需要使用真实对象时,再进行创建。例如,在网页加载图片时,可以先显示一个占位符(代理),当图片真正加载完成后(真实对象创建完成),再显示真实的图片。
- 安全代理:用于控制对敏感对象的访问。代理对象可以在访问真实对象之前,对用户的权限进行验证。例如,在企业级应用中,对某些敏感数据的访问,只有具有特定权限的用户才能通过代理访问到真实的数据。