持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情。
代理模式
今天学习一下代理模式,是一个很重要的设计模式,常说框架的灵魂就是代理+反射,就可以看出代理模式的重要性。
代理模式(Proxy Pattern)就是多一个代理类出来,替原对象进行一些操作。对真实角色的方法进行代理并增强。并且不会影响真实角色的逻辑。这种类型的设计模式属于结构型模式。
应用:
Spring aop 环绕通知
mybatis对于Mapper的代理
静态代理
我们以买房为例
三个角色:
- 房东 (被代理对象)
- 经纪人 (代理对象)
- 客户 (客户端)
对比装饰器模式
代理模式和装饰器模式很相似,透明化的装饰器模式要求将装饰方法抽象出来作为一个接口,装饰器去实现该接口。而代理模式没有做要求,代理模式只有一个要求:代理对象和真实对象实现同一个接口。
静态代理
静态代理在编译期间就会生成代理对象,代理对象唯一或只能够代理一类对象。
定义抽象接口:
public interface SellHouse {
void sell();
}
房东实现卖房子抽象接口:
public class HouseOwner implements SellHouse{
public void sell(){
System.out.println("房东卖房子");
}
}
经纪人:
public class HouseProxy implements SellHouse{
//被代理对象
private SellHouse houseOwner;
public void setHouseOwner(SellHouse houseOwner) {
this.houseOwner = houseOwner;
}
//销售也卖房子,但是包括前置服务和后续维护服务
public void sell(){
prevWork();
houseOwner.sell();
nextWork();
}
//定义抽象方法,这样可以对客户端隐藏实现细节
private void prevWork(){
System.out.println("挂牌");
System.out.println("联系客户");
System.out.println("签合同");
}
private void nextWork(){
System.out.println("维护");
}
}
测试:
@Test
public void test2(){
//经纪人
HouseProxy houseProxy = new HouseProxy();
houseProxy.setHouseOwner(new HouseOwner());
houseProxy.sell();
}
优点
- 真实对象(被代理对象)业务纯粹(房东不用操心客户,也不用学签合同)
- 简单,好理解
- 对客户端隐藏实现细节(房东信息不会泄露)
缺点
- 只能代理一类对象(实现同一接口的对象)
动态代理
动态代理就是为了解决,静态代理代理角色单一,不够通用的问题。动态代理是在运行期间,通过反射机制实现动态代理。
分类:
- jdk动态代理 只能代理实现接口的类(生成代理对象依赖源对象类的接口列表)
- Cglib 动态代理 可以代理实现接口的类和实现抽象类的类
JDK动态代理
需要Proxy类和InvocationHandler接口的支持。
proxy类用于生成代理对象
InvocationHandler,要想实现JDK动态代理必须实现这个接口,实现这个接口的类称为:代理对象的调用处理程序
实现(卖房子例子)
抽象接口和房东都是一样的
实现InvocationHandler接口
@Data
public class SellHouseHandler implements InvocationHandler {
//代理SellHouse这一类的真实角色
private SellHouse sellHouse;
/**
* 生成代理对象
*
* 参数说明:
* - 真实对象类加载器
* - 真实对象接口列表
* - 实现InvocationHandler接口的实例,也就是this
*/
public Object getProxy() {
return Proxy.newProxyInstance(
sellHouse.getClass().getClassLoader(),
sellHouse.getClass().getInterfaces(),
this);
}
/**
* 实现invocationHandler接口必须重写的方法
*
* @param proxy 代理角色
* @param method 真实角色执行方法的包装(可以通过反射获取)
* @param args 真实对象执行方法的参数列表
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(Arrays.toString(proxy.getClass().getInterfaces()));
prevWork();
Object invoke = method.invoke(sellHouse, args);
nextWork();
return invoke;
}
private void prevWork(){
System.out.println("挂牌");
System.out.println("联系客户");
System.out.println("签合同");
}
private void nextWork(){
System.out.println("维护");
}
}
关于invoke的method说明:
可以认为是对真实角色方法的包装
public class HouseOwner implements SellHouse {
public void sell(){
System.out.println("房东卖房子");
}
@Test
public void test() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method sell = HouseOwner.class.getDeclaredMethod("sell");
sell.invoke(houseOwner,null);
}
}
测试:
@Test
public void test(){
SellHouseHandler sellHouseHandler = new SellHouseHandler();
sellHouseHandler.setSellHouse(new HouseOwner());
SellHouse proxy = (SellHouse)sellHouseHandler.getProxy();
proxy.sell();
}
总结
实现方式不是这一种,把Proxy和Invoke方法参数理解了就行。比如说真实角色可以不通过set的方式注入,可以在getProxy(class>)中声明参数。
通用的写法,既然使用了反射,那么就得体现他的强大,利用反射可以代理所有实现了接口的类。
@Data
public class CommonInvocationHandler implements InvocationHandler {
//代理所有对象
private Object target;
public Object getProxy() {
return Proxy.newProxyInstance(
object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
prevWork();
Object invoke = method.invoke(target, args);
nextWork();
return invoke;
}
private void prevWork() {
System.out.println(target.getClass().getName() + "类的实例被代理");
System.out.println("该类实现的接口列表为:" + Arrays.toString(target.getClass().getInterfaces()));
}
private void nextWork() {
System.out.println(target.getClass().getName() + "执行完毕");
}
}
优点
- 静态代理优点
- 修复静态代理缺点
缺点
- 走了一遍代理,且使用反射,性能受影响。考虑他的强大这一点损耗可以忽略
- 不能代理类,只能代理实现接口的类
cglib
JDK动态代理存在一个缺陷,就是不能够代理未实现接口的类。Cglib可以代理所有。
依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
实现
被代理的对象
public class Obj {
/**
* 定义两个方法
* 一个是普通方法,一个是被final修饰的方法
*/
final public void doSomeThing() {
System.out.println("doSomeThing");
}
public void doOtherThing() {
System.out.println("doOtherThing");
}
}
方法拦截器。拦截所有借助此拦截器生成的代理对象,执行方法都会走这里。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开始增强");
//method.invoke(o, objects);
methodProxy.invokeSuper(o, objects);
System.out.println("增强结束");
return null;
}
}
代理对象生成器
代理对象的生成依赖于真实对象的Class对象,和方法拦截器。
@Data
public class CgLibProxy {
private Class<? extends Object> object;
public Object getProxy() {
//创建Enhancer对象,类似于JDK动态代理的Proxy类
Enhancer enhancer = new Enhancer();
//设置目标类的字节码文件,很明显使用反射实现
enhancer.setSuperclass(object);
//设置借助哪个拦截器实现(也就是做哪些增强)
enhancer.setCallback(new MyMethodInterceptor());
//这里的creat方法就是正式创建代理类
return enhancer.create();
}
}
使用
public static void main(String[] args) {
CgLibProxy cgLibProxy = new CgLibProxy();
cgLibProxy.setObject(Obj.class);
//这里的creat方法就是正式创建代理类
Obj proxy = (Obj)cgLibProxy.getProxy();
//调用代理类的final方法
proxy.doSomeThing();
//调用代理类的非final方法
proxy.doOtherThing();
}
结论
- 使用反射实现代理
- 只能对非final方法做增强**