1. 代理模式定义
代理模式(Proxy):为其他对象提供一种代理以控制对这种对象的访问,可以提供额外的功能或控制。
通俗来说,当无法或者直接访问某个对象,可通过一个代理对象间接访问。代理模式可以理解为生活中的代理,服装代理、卖货代理等。
代理模式有什么好处?
作为使用者与真实对象没有直接交集,不会操作到真实对象。通过代理隔离。
1. 代理模式的结构
代理模式中的三种角色:
- 抽象角色:代理角色和真实角色对外提供的公共方法,一般为接口或者抽象类,定义一种行为。
- 真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用,最终要引用的对象。真正的业务逻辑在此。
- 代理角色:需要实现抽象角色接口,是真实角色的代理,内部含有真实角色对象的引用,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的控制流程都放到代理角色中处理。
注:一般,代理可以理解为代码增强,可以在源代码逻辑的前后增加其他代码逻辑。
真实角色和代理角色实现了同样的接口。
3. 静态代理
静态代理 : 静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者继承相同的父类,手动创建代理类,在程序运行前代理类的.class文件就已存在。
静态代理的具体实现
在不改变原有方法的基础上打印日志,以该需求为例:
1. 首先定义抽象接口:
被代理类和代理类都需要实现该接口。
java
public interface Calculator {
void add(int a, int b);
}
2. 真实角色类,被代理类:
实现了Calculator接口。
java
public class CalculatorImpl implements Calculator{
@Override
public void add(int a, int b) {
System.out.println("a + b = " + (a + b));
}
}
3. 代理类,需要含有被代理类对象的引用:
实现了Calculator
接口,含有被代理类CalculatorImpl
对象的引用。
java
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");
}
}
4. 客户端:
java
public class Client {
public static void main(String[] args) {
CalculatorImpl calculatorImpl = new CalculatorImpl();
Proxy proxy = new Proxy(calculatorImpl);
proxy.add(1, 1);
}
}
优化
一般来说,被代理对象和代理对象是一对一的关系。当然一个代理对象对应多个被代理对象也是可以的。
假设另外一个功能也实现了这个接口,这时可以:
java
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);
}
}
面向接口编程 ,这样一个代理对象可以对应多个被代理对象,客户端必须传入一个具体的实现类。但这时一个代理类只能实现一个接口。
静态代理的缺点
- 代码冗余:代理类与被代理类有重复的的代码。
- 维护困难: 接口若发生变化,代理类与被代理类都要进行相应的修改。
- 不适合大规模使用:如果代理大量的类,手动创建代理类较繁琐。
- 只能代理特定的接口:如果要代理的类不实现接口,就无法使用静态代理。
- 不支持横切关注点的复用:例如需要在不同的代理类中写相同的日志。安全检查等。无法做到复用。
- 一个代理类只能实现一个接口:一对多时,一个代理类只能实现一个接口,一个代理类不能实现全部功能。
4. 动态代理
动态代理之所以存在,是为了解决静态代理的那些限制。
动态代理 :在运行时动态创建代理类和其实例。通过反射机制 实现。JDK提供Proxy
和InvocationHandler
来完成这件事。
有关反射的文章:Java基础------反射(Reflection)基础
Proxy类
Java中的 java.lang.reflect.Proxy
类是动态代理的核心组件之一。这个类用于在运行时创建代理类的实例。它提供了一个静态方法 newProxyInstance()
用于创建代理对象。
java
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
newProxyInstance
有三个参数:
loader
:用于定义代理类的类加载器。interfaces
:代理类要实现的接口列表,数组,可以实现多个接口。h
:用于将方法调用分派的调用处理程序。
java
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class<?>[] { Foo.class },
handler);
强转,创建一个代理对象,这个对象怎么去调用被代理类中的方法的?
这时就用到了InvocationHandler
,InvocationHandler
起到了一个回调作用。
InvocationHandler接口
java.lang.reflect.InvocationHandler
接口是动态代理的另一个重要组件。该接口只有一个方法 invoke()
,用于在代理对象的方法被调用时执行自定义的逻辑。
当生成的代理对象调用代理对象的方法 时,将调用的方法传到invoke()
方法中去,invoke()
方法会被调用。
这里可以将InvocationHandler
接口看作是一个监听,当代理对象调用方法时,就会执行invoke()
中的内容。
invoke()
方法接收三个参数:代理对象,被调用的方法对象和方法的参数,它负责执行代理逻辑。
java
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke
方法有三个参数:
proxy
:代理对象,通常是生成的代理对象。你可以使用它来调用被代理对象的方法,但通常不会使用它,因为会导致递归调用。文末会说明。method
: 被代理对象的方法对象,它表示将被调用的方法。你可以使用它来获取方法的信息,例如方法的名称、参数、返回类型等。args
: 方法的参数数组,包含了调用方法时传递的参数。
Object
是invoke()
方法的返回值,通常是被代理方法的返回值。如果被代理方法返回基本数据类型,会自动装箱为相应的包装类型。
动态代理的具体实现
1. 定义一个抽象接口:
java
public interface Calculator {
void add(int a, int b);
}
2. 创建被代理对象:
java
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));
}
}
3. 定义一个类去实现InvocationHandler接口:
java
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. 生成代理对象:
java
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);
}
}
运行结果:
注:
Q:为什么InvocationHandler中invoke()方法中的代理对象谨慎使用?
A:因为会导致递归调用。
javaCalculator 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. 动态代理源码分析
动态代理源码分析请看后续文章...