文章目录
一、代理模式
①. 概念
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接 引用目标对象,代理对象作为访问对象和目标对象之间的中介
②. 结构
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对 象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访 问、控制或扩展真实主题的功能。
1. 静态代理
- UML
1.1 代码
- SellTrickets
public interface SellTrickets {
void sell();
}
- Cinema
public class Cinema implements SellTrickets {
@Override
public void sell() {
System.out.println("电影院卖票");
}
}
- ProxyPerson
public class ProxyPerson implements SellTrickets{
private Cinema cinema = new Cinema();
@Override
public void sell() {
System.out.println("代理人卖票,挣差价");
cinema.sell();
}
}
- 测试类
public class Client {
public static void main(String[] args) {
ProxyPerson proxy = new ProxyPerson();
proxy.sell();
}
}
//执行结果
代理人卖票,挣差价
电影院卖票
2. JDK动态代理
- UML
2.1 代码
- 诊断工具
- ProxyFactory
public class ProxyFactory {
private Cinema cinema = new Cinema();
public SellTrickets getProxyObject(){
/*
ClassLoader loader: 类加载器,用于加载代理类,可以通过目标对象获取类加载器
Class<?>[] interfaaces: 代理类实现的接口的字节码对象
InvocationHandler h : 代理对象的调用处理程序
*/
SellTrickets sellTrickets = (SellTrickets)Proxy.newProxyInstance(
cinema.getClass().getClassLoader(),
cinema.getClass().getInterfaces(),
new InvocationHandler() {
/*
Objcet proxy:代理对象,和proxyObject对象是同一个对象,在invoke方法中基本不用
Method method:对接口中的方法进行封装的method对象
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Proxy的invoke被调用了");
Object invoke = method.invoke(cinema, args);
return invoke;
}
}
);
return sellTrickets;
}
}
- 测试类
public class Client {
public static void main(String[] args) {
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//使用factory对象的方法获取代理对象
SellTrickets proxyObject = factory.getProxyObject();
proxyObject.sell();
System.out.println(proxyObject.getClass());
//让程序一直执行,不然拿不到代理类
//1.找到arthas-boot.jar,在文件下运行cmd
//2.键入java -jar arthas-boot.jar
//3.选择该运行时的名字
//3.jad 对象的全类名(见测试结果里面)
while (true);
}
}
- 测试结果
Proxy的invoke被调用了
电影院卖票
class com.sun.proxy.$Proxy0//对象的全类名
- 动态获取
public final class $Proxy0
extends Proxy
implements SellTrickets {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
m3 = Class.forName("缁撴瀯鍨嬫ā寮?.DP01浠g悊妯″紡.Proxy02鍔ㄦ?佷唬
}
public final void sell() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
2.3 执行流程
- 在测试类中通过代理对象调用sell()方法
- 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
- 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的 invoke方法
- invoke方法通过反射执行了真实对象所属类(Cinema)中的sell()方法
3. CGLib动态代理
如果没有定义SellTickets接口,只定义了Cinema(电影院类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。
<!-- asm包 --><dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>9.0-beta</version></dependency><!-- cglib包 --><dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version></dependency>
- UML
3.1 代码
- Cinema
public class Cinema implements SellTrickets { @Override public void sell() { System.out.println("电影院卖票"); }}
- ProxyFactory
public class ProxyFactory implements MethodInterceptor { //声明火车站对象 private Cinema station = new Cinema(); public Cinema getProxyObject() { //创建Enhancer对象,类似于JDK代理中的Proxy类 Enhancer enhancer = new Enhancer(); //设置父类的字节码对象。指定父类 enhancer.setSuperclass(Cinema.class); //设置回调函数 enhancer.setCallback(this); //创建代理对象 Cinema proxyObject = (Cinema) enhancer.create(); return proxyObject; } public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //System.out.println("方法执行了"); System.out.println("待售挣差价(CGLib代理)"); //要调用目标对象的方法 Object obj = method.invoke(station, objects); return obj; }}
- 测试类
public class Client {
public static void main(String[] args) {
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//获取代理对象
Cinema proxyObject = factory.getProxyObject();
//调用代理对象中的sell方法卖票
proxyObject.sell();
}
}
待售挣差价(CGLib代理)
电影院卖票
4. 三种代理的对比
4.1 对比
- jdk代理和CGLIB代理
- 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在 JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者 方法进行代理,因为CGLib原理是动态生成被代理类的子类。
- 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代 理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率 低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代 理,如果没有接口使用CGLIB代理。
- 动态代理和静态代理
- 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中 的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们 可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
- 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实 现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
4.2 优缺点
4.2.1 优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
4.2.2 缺点
- 增加了系统的复杂度;
4.3 使用场景
- 远程代理
- 虚拟处理
- 保护代理
- 防火墙代理