简述
代理模式,顾名思义,首先一定是有一个代理方,和一个被代理方的区别。而我们日常生活中,中介找房租房就是一个非常直观的代理案例,中介代理房东的房子展示给客户,客户通过中介就能够直接进行看房、选房等一系列操作,非常方便。
如图所示,客户想看什么房子,直接找中介就行了。完事Java里面,代理模式下,我们要找什么对象,找代理类(目标对象)就行了。
适用场景
代理模式很明显的是一个代理方可以代理多个目标对象,如同中介可以托管多个房东的房子,那么使用场景:
1.Spring AOP切面,Spring两大核心之一,面向切面编程奠基者。
2.事务@Transaction 注解,通过代理模式实现SQL的提交与回滚。
3.项目接口监控日志Log打印,最简单的一种项目日志打印方法,直接将Request与Response打印在日志文件中。当然输出源可以有多种:日志文件、输出文件通过Filebeat监听收集或者通过消息中间件Kafka存储在ES中(ELK日志收集)、数据库MySQL、缓存等等。
4.MyBatis的Mapper接口代理
5.全局异常捕获 GlobalException
6.LCN、seata等分布式事务框架 小伙伴最熟悉的应该就是@GlobalTransaction 注解了
PS:一个注解解决分布式事务问题!
7.分库分表框架shadingjdbc 代理数据源
8.自定义注解(反射+AOP)
9.RPC远程调用
以上列举了一系列我们实际开发的项目中经常会用到的技术以及碰到的场景,这时候我们会发现,原来代理模式可以做这么多的事情。 代理模式:我真能干!
特性与分类
优点:实现方法扩展或增强,提高代码复用性、冗余性、扩展性、智能型,各层代码职责清晰。
缺点:在客户端和访问对象中增加了代理对象,代理对象的生成是需要时间的,并且有些类型的代理可能造成请求的速度变慢。且代理模式需要额外的工作,有些代理模式实现十分复杂。
分类:代理模式分为JDK代理(接口代理)与Cglib代理(类代理),双方是取长补短,灵活分工。追求性能极致。
当然,代理模式亦可动静自如,分为静态代理与动态代理,静态代理编写一个代理类代理目标类,而动态代理可以理解为一次编译,N次代理。
下面我们对于种种代理情况进行编码分析。
源码地址
代理模式相关源码码云地址:https://gitee.com/yiang-hz/blog
路径:blog -> design -> proxy
静态代理
静态代理缺点非常明显,每出现一个目标对象就需要定义一个代理对象,非常繁琐。
接口代理
接口代理,一个类可以实现多个接口,当然如果没有实现接口则无法被JDK的这种形式代理,则需要使用类代理。
步骤1:创建一个接口
public interface OrderService {
String addOrder(String id, String name);
}
步骤2:创建该接口的实现类
public class OrderImpl implements OrderService {
@Override
public String addOrder(String id, String name) {
System.out.println("addOrder-- id:" + id + " name:" + name);
return "执行结束";
}
}
步骤3:创建代理类
public class OrderListenerImpl implements OrderService {
private final OrderImpl order;
public OrderListenerImpl(OrderImpl order) {
this.order = order;
}
@Override
public String addOrder(String id, String name) {
System.out.println("OrderService... 接口代理开始");
return order.addOrder(id, name);
}
}
步骤4:
public class TestProxy {
public static void main(String[] args) {
inFaceProxy();
}
/**
* 静态代理,通过接口实现 (JavaJDK动态代理的实现原理)
*/
private static void inFaceProxy() {
OrderListenerImpl orderListenerImpl = new OrderListenerImpl(new OrderImpl());
orderListenerImpl.addOrder("1短袜", "小米18");
}
}
当被请求或者被调用时,使用代理类对象OrderListenerImpl来代理 OrderImpl类的对象调用
执行输出:
OrderListenerImpl... 接口代理开始
addOrder-- id:1短袜 name:小米18
类代理
类代理,一个类只能继承一个类,默认会继承Object类。
步骤1:编写代理类,继承前面写到的的OrderImpl实现类
public class OrderClassListenerImpl extends OrderImpl {
@Override
public String addOrder(String id, String name) {
System.out.println("OrderClassListenerImpl... 类代理开始");
return super.addOrder(id, name);
}
}
步骤2:测试类添加该方法,main方法添加对其调用进行测试
/**
* 静态代理,通过类实现 (Cglib代理的实现原理)
* 缺点:因为是继承实现,Java不能多继承
*/
private static void inClassProxy() {
OrderClassListenerImpl orderClassListenerImpl = new OrderClassListenerImpl();
orderClassListenerImpl.addOrder("1", "小米18");
}
当被请求或者被调用时,使用代理类对象OrderListenerImpl获取OrderImpl实现
执行输出:
OrderClassListenerImpl... 类代理开始
addOrder-- id:1 name:小米18
静态代理我们分析完成之后,继续分析动态代理,在动态代理我们直接就演变成了项目中的JDK代理实现以及Cglib代理实现。
动态代理
动态代理完美解决了静态代理编码繁琐的缺点,我们依赖的jar就有很多对代理进行了封装,如最熟悉的Spring框架,然后是Java开发核心的JDK,再者Hutool工具类框架等等。
JDK动态代理
JDK动态代理实现原理是通过反射去执行方法,再走回调拦截。根据类的接口生成回调类。
实现思路:
1.需要拼接Java的源代码文件,JDK默认生成的文件命名格式为 $Proxy*.java 其中*是自增长数值
2.将拼接好的Java源代码文件进行编译,编译成 $Proxy*.class文件
3.使用类加载器将class文件加载到内存中
4.使用Java反射机制,赋值并且调用目标方法
具体实现:
由于篇幅问题,指定在码云的项目中了,根据文章头部的代码地址拉取查看即可。
Cglib代理
Cglib代理实现原理则是通过类代理,基于字节码技术(ASM)。并且提供FastClass索引机制提升效率,调用速度是远快于JDK动态代理的。但是会生成相关的索引文件,生成效率要低于JDK动态代理。
实现思路:
1.直接采用ASM实现生成.class文件
2.采用类加载器读取.class文件至内存中
3.采用FastClass机制调用目标方法(索引机制)
具体实现:
由于篇幅问题,指定在码云的项目中了,根据文章头部的代码地址拉取查看即可。
总结
总体来说,JDK动态代理,在创建类的过程中是较快的,但是生成类之后通过反射调用速度会慢很多,且必须实现接口才能应用该代理。而Cglib在生成的时候会较慢,固一般采用缓存机制来优化该问题。