代理模式解决的问题:对于系统的一个类中的很多方法或一系列的类,需要在其执行之前或之后进行增强处理(例如添加日志),这时候就可以使用代理简化代码,不用每个地方都添加重复代码。
代理实现的方式
代理实现的划分:目前根据其能达到的代理的功能特性来划分,基础的方式只能代理固定的类,被叫做静态代理;JDK与CGLIB支持动态的类,被叫做动态代理,实际使用过程中还是动态代理好使,毕竟是已经封装好的工具。
-
静态代理
静态代理只固定代理一个类,也就是在原有类的方法执行之前或之后添加需要执行的逻辑。
核心:代理类持有被代理类的对象,和被代理类保持同样的方法结构(可实现通过杨的接口或自己控制)
例如:我现在有一个查询数据库的公共Service
其实现是直接查询数据库数据
但是现在要求将所有的数据库查询先走Redis缓存查询,所以我就添加了数据库查询的代理类,和数据库查询的类实现同一个接口,其中持有了数据库查询的对象,在实现查询时先执行redis查询再使用DB查询的对象执行原有方法
总结:静态代理和装饰者有点像,但是装饰者是拿固定的方法作为功能块直接继承来用(没创建对象),代理则更侧重于整个类的代理,持有原来的对象干活儿。
另外,静态代理的写法其实是有点像策略模式的,只是第二种策略代理了第一种策略,场景不一样 -
动态代理
如果让你手写动态代理,你会怎么写?
动态代理,顾名思义,其意思是动态的代理一些类,不需要你每个像静态代理那样手写其代理类。
我觉得按照逻辑讲有以下几步:
第一步:定位需要代理的目标群体
可能的场景:需要代理的可能是多个类,这些类可能在某个固定文件夹下,也可能分布在不同地方
如何找到这些类?一般人肯定会对这些类做标识,在代码里面其实注解就是标识的一种。另一种则是主动告知,例如mybatis指定mapper文件的位置注解,其蕴含了目标文件的位置(没错,mybatis也用了代理模式)
第二步:获取需要代理类的对象
有了位置信息即可进行下一步,获取需要代理类的对象,并进行代理操作。目前只知道类的位置,如何获取其对象?直接反射一套带走啊
String className = "com.example.MyClass";
Class<?> clazz = Class.forName(className);
MyClass obj = (MyClass) clazz.getDeclaredConstructor().newInstance();
第三步:完成代理
理论上来讲,在第二步获取到了被代理类的对象后直接通过对象调用其方法,在调用方法前或方法后添加增强逻辑即可。
JDK代理使用方法:
创建代理处理类 JdkInvocationHandler,JDK InvocationHandler暴露出代理类的执行方法,方便添加逻辑
代理类使用方式
将JDK生成的代理类显示到项目同级
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
查看JDK生成的代理类发现其也是实现了被代理类所实现的interface,和静态代理的写法一样
但是在其实现查询数据库方法部分有所不同
super.h,从$Proxy0.class的父类查看h代表了代理处理类,即JdkInvocationHandler
m3代表了查询数据库Service的getData方法
所以代理类中getData最终的实现是调用JdkInvocationHandler中的invoke方法,第一个参数是当前生成的代理类 Proxy0,第二个参数是DataService的getData方法,讲到底这玩意最终只是为了调用被代理的方法,费劲,然后invoke就回到了最开始handler暴露的入口。。。
在handler里面的invoke写东西就相当于在代理类的getData实现里面写东西,妙啊。
漏了一步,代理类的创建通过Proxy类提供的API传入目标类以及我们的handler即可
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
DataService dataService = jdkInvocationHandler.getProxy();
dataService.getData();
在这里获取到的代理类其实就是DataService 的一个实现类对象,所以用的DataService 接的,调用getData方法实际就会走代理类Proxy0的实现!
其大概过程如下:
另:cglib的代理下篇再整理,代理这玩意写的复杂,实际也就那点功能