1. 代理模式定义
代理模式(Proxy):为通过一个代理对象控制对目标对象的访问,实现访问控制和功能增强的双重目的。
通俗来说,当无法或者直接访问某个对象,可通过一个代理对象间接访问。代理模式可以理解为生活中的代理,服装代理、卖货代理等。
代理模式有什么好处?
- 访问控制:代理作为守门员,控制对敏感对象的访问
- 功能解耦:在不修改目标对象的前提下增强功能(日志、事务等)
- 延迟加载:代理可延迟目标对象的创建(如大文件加载)
2. 代理模式的结构
代理模式中的三种角色:
- 抽象角色: 定义代理和目标对象的共同行为契约,一般为接口或者抽象类。
- 真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用,最终要引用的对象。真正的业务逻辑在此。
- 代理角色:需要实现抽象角色接口,是真实角色的代理,内部含有真实角色对象的引用,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的控制流程都放到代理角色中处理。
注:一般,代理可以理解为代码增强,可以在源代码逻辑的前后增加其他代码逻辑。
真实角色和代理角色实现了同样的接口。
3. 静态代理
静态代理: 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者继承相同的父类,手动创建代理类,在程序运行前代理类的 .class 文件就已存在。
3.1 静态代理的具体实现
在不改变原有方法的基础上打印日志,以该需求为例:
3.1.1 首先定义抽象接口
被代理类和代理类都需要实现该接口。
public interface Calculator {
void add(int a, int b);
}
3.1.2 真实角色类,被代理类
实现了Calculator接口。
public class CalculatorImpl implements Calculator{
@Override
public void add(int a, int b) {
System.out.println("a + b = " + (a + b));
}
}
3.1.3 代理类,需要含有被代理类对象的引用
实现了Calculator
接口,含有被代理类CalculatorImpl
对象的引用。
public class Proxy implements Calculator{
CalculatorImpl calculatorImpl;
public Proxy(CalculatorImpl calculatorImpl) {
this.calculatorImpl = calculatorImpl;
}
@Override
public void add(int a, int b) {
System.out.println("Before add calculator");
calculatorImpl.add(a, b);
System.out.println("After add calculator");
}
}
3.1.4 客户端
public class Client {
public static void main(String[] args) {
CalculatorImpl calculatorImpl = new CalculatorImpl();
Proxy proxy = new Proxy(calculatorImpl);
proxy.add(1, 1);
}
}
3.2 优化
一般来说,被代理对象和代理对象是一对一的关系。当然一个代理对象对应多个被代理对象也是可以的。
假设另外一个功能也实现了这个接口,这时可以:
public class Proxy implements Calculator{
Calculator calculator;
public Proxy(Calculator calculator) {
this.calculator = calculator;
}
@Override
public void add(int a, int b) {
calculator.add(a, b);
}
}
面向接口编程,这样一个代理对象可以对应多个被代理对象,客户端必须传入一个具体的实现类。但这时一个代理类只能实现一个接口。
3.3 静态代理的缺点
- 代码冗余:代理类与被代理类有重复的的代码。
- 维护困难: 接口若发生变化,代理类与被代理类都要进行相应的修改。
- 不适合大规模使用:如果代理大量的类,手动创建代理类较繁琐。
- 只能代理特定的接口:如果要代理的类不实现接口,就无法使用静态代理。
- 不支持横切关注点的复用:例如需要在不同的代理类中写相同的日志。安全检查等。无法做到复用。
- 一个代理类只能实现一个接口:一对多时,一个代理类只能实现一个接口,一个代理类不能实现全部功能。
4. 动态代理
动态代理之所以存在,是为了解决静态代理的那些限制。
动态代理:在运行时动态创建代理类和其实例。通过反射机制实现。JDK提供Proxy
和InvocationHandler
来完成这件事。
核心组件:
- Proxy类:动态代理类的工厂
- InvocationHandler接口:代理逻辑处理器
4.1 Proxy
Java 中的 java.lang.reflect.Proxy
类是动态代理的核心组件之一。这个类用于在运行时创建代理类的实例。它提供了一个静态方法 newProxyInstance()
用于创建代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
参数解析:
- loader: 定义代理类的类加载器
- interfaces: 代理类要实现的接口列表
- h: 处理方法调用的处理器
代码示例:
// 目标对象
RealService realService = new RealService();
// 创建代理对象
ServiceInterface proxy = (ServiceInterface) Proxy.newProxyInstance(
RealService.class.getClassLoader(),
new Class<?>[]{ServiceInterface.class},
new LoggingHandler(realService)
);
强转,创建一个代理对象,这个对象怎么去调用被代理类中的方法的?
这时就用到了InvocationHandler
,InvocationHandler
起到了一个回调作用。
4.2 InvocationHandler
java.lang.reflect.InvocationHandler
接口是动态代理的另一个重要组件。该接口只有一个方法 invoke()
,用于在代理对象的方法被调用时执行自定义的逻辑。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
当生成的代理对象调用代理对象的方法时,将调用的方法传到invoke()
方法中去,invoke()
方法会被调用。
这里可以将InvocationHandler
接口看作是一个监听,当代理对象调用方法时,就会执行invoke()
中的内容。
invoke
方法有三个参数:
proxy
:代理对象,通常是生成的代理对象。你可以使用它来调用被代理对象的方法,但通常不会使用它,因为会导致递归调用。文末会说明。method
: 被代理对象的方法对象,它表示将被调用的方法。你可以使用它来获取方法的信息,例如方法的名称、参数、返回类型等。args
: 方法的参数数组,包含了调用方法时传递的参数。
代码示例:
public class LoggingHandler implements InvocationHandler {
private final Object target; // 被代理的真实对象
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 前置增强:日志记录
System.out.println("[LOG] 调用方法: " + method.getName());
System.out.println("[LOG] 参数: " + Arrays.toString(args));
long start = System.nanoTime();
// 调用真实对象的方法
Object result = method.invoke(target, args);
// 后置增强:性能监控
long duration = System.nanoTime() - start;
System.out.printf("[LOG] 方法执行耗时: %.2f ms%n", duration / 1_000_000.0);
return result;
}
}
Object
是invoke()
方法的返回值,通常是被代理方法的返回值。如果被代理方法返回基本数据类型,会自动装箱为相应的包装类型。
4.3 动态代理的具体实现
4.3.1 定义一个抽象接口
public interface Calculator {
void add(int a, int b);
void reduce(int x, int y);
}
4.3.2 创建被代理对象
public class CalculatorImpl implements Calculator{
@Override
public void add(int a, int b) {
System.out.println("a + b = " + (a + b));
}
@Override
public void reduce(int x, int y) {
System.out.println("a + b = " + (x - y));
}
}
4.3.3 定义一个类去实现 InvocationHandler 接口
public class MyInvocationHandler implements InvocationHandler {
private Object realObject;
public MyInvocationHandler(Object realObject) {
this.realObject = realObject;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("Before invoking the method: " + method.getName());
Object o1 = method.invoke(realObject, objects);//通过反射调用被代理类中方法
System.out.println("After invoking the method: " + method.getName());
return o1;
}
}
4.3.4 生成代理对象
public class Client {
public static void main(String[] args) {
//创建被代理对象
Calculator realObject = new CalculatorImpl();
//创建代理处理器
MyInvocationHandler h = new MyInvocationHandler(realObject);
//创建代理对象
Calculator o = (Calculator) Proxy.newProxyInstance(//强转,通过该对象调用方法
Calculator.class.getClassLoader(),
new Class[]{Calculator.class},
h);
//使用代理对象调用方法
o.add(1, 1);
o.reduce(1, 1);
}
}
4.3.5 运行结果
注:
Q:为什么InvocationHandler中invoke()方法中的代理对象谨慎使用?
A:因为会导致递归调用。
Calculator o = (Calculator) Proxy.newProxyInstance( Calculator.class.getClassLoader(), new Class[]{Calculator.class}, new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println(o); return method.invoke(realObject, objects); } });
这段代码报错栈溢出
StackOverflowError
,System.out.println(o);
相当于System.out.println(o.toString());
该语句会再次调用invoke()方法,以致循环调用。
5. 动态代理源码分析
动态代理源码分析请看后续文章...