- 代理模式
- 代理模式角色定义
- 静态代理
3.1 静态代理实例
3.2 静态代理的缺点- 动态代理
4.1 基于JDK原生动态代理实现
1. 代理模式
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
通过代理模式可以做到:
- 隐藏委托类的具体实现。
- 实现客户与委托类的解耦,在不改变委托类代码的情况下添加一些额外的功能(日志、权限)等。
2. 代理模式角色定义
三类对象:
- Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法。
- RealSubject(真实主题角色):真正实现业务逻辑的类。
- Proxy(代理主题角色):用来代理和封装真实主题。
3. 静态代理
静态代理是指代理类在程序运行前就已经存在。
3.1 静态代理实例
首先定义一组接口Sell,用来提供广告和销售等功能。然后提供Vendor类(厂商,被代理对象)和Shop(超市,代理类),它们分别实现了Sell接口。
Sell接口定义如下:
package javaBasic.proxy;
/**
* 首先定义一组接口Sell,用来提供广告和销售等功能。
*/
public interface Sell {
/**
* 出售
*/
void sell();
/**
* 广告
*/
void ad();
}
Vendor类定义如下:
package javaBasic.proxy;
public class Vendor implements Sell {
@Override
public void sell() {
System.out.println("Shop sell goods");
}
@Override
public void ad() {
System.out.println("Shop advert goods");
}
}
Shop类定义如下:
package javaBasic.proxy;
/**
* 其中代理类Shop通过聚合的方式持有了被代理类Vendor的引用,
* 并在对应的方法中调用Vendor对应的方法。
* 在Shop类中我们可以新增一些额外的处理,比如筛选购买用户、记录日志等操作。
*/
public class Shop implements Sell{
private Sell sell;
public Shop(Sell sell){
this.sell = sell;
}
@Override
public void sell() {
System.out.println("代理类Shop, 处理sell");
sell.sell();
}
@Override
public void ad() {
System.out.println("代理类Shop, 处理ad");
sell.ad();
}
}
其中代理类Shop通过聚合的方式持有了被代理类Vendor的引用,并在对应的方法中调用Vendor对应的方法。在Shop类中我们可以新增一些额外的处理,比如筛选购买用户、记录日志等操作。
客户端使用代理类的方式:
package javaBasic.proxy;
/**
* 针对客户看到的是Sell接口提供了功能,而功能又是由Shop提供的。
* 我们可以在Shop中修改或新增一些内容,而不影响被代理类Vendor。
*/
public class StaticProxy {
public static void main(String[] args) {
// 供应商---被代理类
Vendor vendor = new Vendor();
// 创建供应商的代理类Shop
Sell sell = new Shop(vendor);
// 客户端使用时面向的是代理类Shop。
sell.ad();
sell.sell();
}
}
在上述代码中,针对客户看到的是Sell接口提供了功能,而功能又是由Shop提供的。我们可以在Shop中修改或新增一些内容,而不影响被代理类Vendor。
3.2 静态代理的缺点
- 当需要代理多个类时,代理对象要实现与目标对象一致的接口。要么,只维护一个代理类来实现多个接口,但这样会导致代理类过于庞大。要么,新建多个代理类,但这样会产生过多的代理类。
- 当接口需要增加、删除、修改方法时,目标对象与代理类都要同时修改,不易维护。
4. 动态代理
动态代理是指代理类在程序运行时进行创建的代理方式。这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据Java代码中的“指示”动态生成的。
相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
4.1 基于JDK原生动态代理实现
实现动态代理通常有两种方式:JDK原生动态代理和CGLIB动态代理。这里,我们以JDK原生动态代理为例。
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
InvocationHandler接口定义了如下方法:
/**
* 调用处理程序
*/
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args);
}
实现了该接口的中介类用做“调用处理器”,当调用代理类对象的方法时,这个调用会直接跳转到invoke方法之中,代理类对象作为proxy参数传入,参数method标识了具体调用的是代理类的哪个方法,args为该方法的参数。这样对代理类中的所有方法的调用都会变为对invoke的调用,可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。
Proxy类用来实例化指定代理对象所关联的调用处理器。
下面以添加日志为例演示动态代理。
首先是建立LogHandler类:
package javaBasic.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; //被代理的对象, 实际的方法执行者
public LogHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target,args);
after();
return result;
}
//调用invoke方法之前执行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
//调用invoke方法之后执行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
客户端编写程序使用动态代理代码如下:
package javaBasic.proxy;
import java.lang.reflect.Proxy;
public class DynamicProxyMain {
public static void main(String[] args) {
//创建中介类实例
LogHandler logHandler = new LogHandler(new Vendor());
//获取代理类实例
Sell sell = (Sell) Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler);
//通过代理类对象调用代理类方法,实际上会转到invoke方法(LogHandler之中的sell方法)调用
//这个代理类之中的方法调用是通过反射实现的
sell.sell();
sell.ad();
}
}
执行后结果(已经成功为我们的被代理类统一添加了执行方法之前和执行方法之后的日志):
log start time [Fri Aug 21 09:39:04 AEST 2020]
Shop sell goods
log end time [Fri Aug 21 09:39:04 AEST 2020]
log start time [Fri Aug 21 09:39:04 AEST 2020]
Shop advert goods
log end time [Fri Aug 21 09:39:04 AEST 2020]
动态代理的原理:
执行上述方法后会生成一个$Proxy0.class类文件。该类继承了Proxy类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法。
由于动态代理类继承了Proxy类,所以每个代理类都会关联一个InvocationHandler方法调用处理器。
类和所有方法都被public final修饰,所以代理类只可被使用,不可以再被继承。
每个方法都有一个Method对象来描述,Method对象在static静态代码块中创建,以“m+数字”的格式命名。
调用方法的时候通过super.h.invoke(this,m1,(Object[])null);调用。其中的super.h.invoke实际上是在创建代理的时候传递给Proxy.newProxyInstance的LogHandler对象,它继承InvocationHandler类,负责实际的调用处理逻辑。