设计模式-深度分析代理模式
大家都知道SpringAOP使用代理模式实现,到底是怎么实现的?我们来一探究竟,并且自己仿真首先还原部分细节。
定义
代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型模式。
在某种情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式UML代理模式一般包含3种角色:
1、抽象主题角色(Subject):抽象主题类的主要职责是声明真实主题与代理的共同接口方法,该类可以是接口也可以是抽象类;
2、真实主题角色(RealSubject):该类也被称为被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象;
3、代理主题角色(Proxy):也称代理类,其内部持有RealSubject的引用,因此具备完全的对RealSubject的代理权。客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增加一些处理代码。
在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知。代理模式属于结构型模式,分为静态代理和动态代理。
应用场景
生活中的租房中介、售票黄牛、婚介、经纪人、事务代理、非侵入式日志监听等,都是代理模式的实际体现。当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
代码实例
通用写法
抽象主题角色ISubject 真实主题角色 代理主题角色:增加了before()和after()两个增强方法 客户端调用代码 运行结果但上面的场景有个弊端,就是自己父亲只会给自己的子女去物色对象,别人家的孩子还得通过别人去物色.社会上这项业务发展成了一个产业,出现了媒婆、婚介所等结构,而且还有各种各样的定制套餐.如果还使用静态代理成本就太高了,我们需要一个更加通用的解决方案,来满足任何单身人士找对象的需求.这就由静态代理升级到了动态代理.
采用动态代理基本上只要是人(IPerson)就可以提供相亲服务.动态的底层实现一般不用我们自己亲自去实现,已经有很多现成的API.在java生态中,目前最普遍使用的是JDK自带的代理和CgLib提供的类库.下面我们首先基于JDK的动态代理来升级一下代码.
从静态代理到动态代理
只要是人,都可以寻找对象. 张三 小美 创建媒婆(婚介所)类 测试代码 运行结果静态模式在业务中的应用
这里小伙伴们可能会觉得还是不知道如何将代理模式应用到实际的业务场景中,我们来看一个实际的业务场景.
在分布式业务场景中,通常会对数据库进行分库分表,分库分表之后使用java操作时就可能需要配置多个数据源,我们通过配置数据源路由来动态切换数据源.
创建Order订单类 创建OrderDao持久层操作类 创建IOrderService接口 创建OrderService实现类接下来使用静态代理,主要完成的功能是:根据订单创建时间自动按年进行分库.根据开闭原则,我们修改原来写好的代码逻辑,通过代理对象来完成.先创建数据源路有对象,使用ThreadLocal的单例来实现DynamicDataSourceEntry类:
DynamicDataSourceEntry类:动态切换数据源. 创建切换数据源的代理类:OrderServiceStaticProxy 测试类 运行结果结果符合我们的预期.
其实,动态代理和静态代理的基本思路是一致的,只不过动态代理功能更加强大,随着业务的扩展适应性更强.
动态代理在业务场景中的应用
上面的案例理解了,我们在看数据源动态路由业务,帮助小伙伴们加深对动态代理的印象.
创建动态代理类OrderServiceDynamicProxy. 测试方法依然能够达到相同的运行效果.但是,使用动态代理之后,我们不仅能实现Order的数据源动态路由,还可以实现其他任何类的数据源路由.当然,有个比较重要的约定,必须实现getCreateTime()方法,因为路由规则是根据时间来运算的.我们可以通过接口规范来达到约束的目的,在此就不再举例.
运行结果手写JDK动态代理实现原理
不仅知其然,还要知其所有然.既然JDK动态代理功能如此强大,那么他是如何实现的呢?我们现在来探究一下原理,并模仿JDK动态代理动手写一个属于自己的动态代理.
我们知道JDK动态代理采用字节码重组,重新生成对象来替代原始对象,以达到动态代理的目的.
JDK动态代理生成对象的步骤如下:
1、获取被代理对象的引用,并且获取他的所有接口,反射获取;
2、JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口;
3、动态生成Java代码,新加的业务逻辑方法由一定的逻辑代码调用(在代码中体现);
4、编译新生成的Java代码.class文件;
5、重新加载到JVM中运行.
以上过程就叫字节码重组.JDK中有一个规范,在ClassPath下只要是$开头的.class文件都是字节码重组自动生成的.那么我们有没有办法看到代替后的对象的阵容呢?做一个这样的测试,我们将内存中的对象字节码通过文件流输出到一个新的.class文件,然后利用反编译工具查看其源代码.
我们发现,$Proxy0继承了Proxy类,同时还实现了IPerson接口,并且重写了findLove()等方法.在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,重写的方法用反射调用目标对象的方法.小伙伴们此时一定会好奇:这些代码是哪里来的呢?其实是JDK帮我们自动生成的.现在我们不依赖JDK,自己来动态生成源代码、动态完成编译,然后替代目标对象并执行.
创建GPInvocationHandler接口 创建GPProxy类 创建GPClassLoader类 创建GpMeipo媒婆类 测试代码到此,手写JDK动态代理就完成了.小伙伴们是不是又多了一个面试用的杀手锏呢?
CGLib代理调用API及原理分析
简单看一下CGLib代理的使用,还是以媒婆为例,创建CglibMeipo类:
创建CglibMeipo类 创建客户端Customer类这里有个小细节,CGLib代理的目标对象不需要实现任何接口,它是通过动态继承目标对象实现动态代理的.来看测试代码:
测试代码CGLib代理的实现原理又是怎样的呢?我们可以在测试代码中加上一句代码,将CGLib代理后的.class文件写入磁盘,然后反编译来一探究竟.
利用CGLib的代理类可以将内存中的.class文件写入本地磁盘重新执行代码,我们会发现在E://cglib_proxy_class/目录下多了三个.class文件.
分析源代码,调用过程:
代理对象调用this.findLove()方法->调用拦截器->methodProxy.invokeSuper->CGLIB$findLove$0->被代理对象findLove()方法
CGLib代理执行代理方法的效率之所以比JDK的高,是因为CGLib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个类,这个类会为代理类或被代理类的方法分配一个index(int类型);这个inx当做一个入参,FastClass就可以直接定位要调用的方法并直接进行调用,省去了反射调用,所以调用效率比JDK代理通过反射调用高.
CGLib和JDK动态代理对比
1、JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象;
2、JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,CGLib代理实现更复杂,生成代理类比JDK动态代理效率低.
3、JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高.
代理模式于Spring生态
1、代理模式在Spring中的应用
2、Spring中的代理选择原则
(1)当Bean有实现接口时,Spring就会用JDK动态代理;
(2)当Bean没有实现接口时,Spring会选择CGLib代理;
(3)Spring可以通过配置强制使用CGLib代理,只需在Spring的配置文件中加入如下代码:
使用CGLib代理配置项
静态代理和动态代理的本质区别
1、静态代理只能通过手动完成代理操作,如果被处理类增加了新的方法,代理类需要同步增加,违背开闭原则;
2、动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则;
3、若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
代理模式优点
1、代理模式能将代理对象与真实被调用目标对象分离;
2、在一定程度上降低了系统的耦合性,扩展性好;
3、可以起到保护目标对象的作用;
4、可以增强目标对象的功能。
代理模式缺点
1、代理模式会造成系统设计中类的数量增加;
2、在客户端和目标对象中增加一个代理对象,会导致请求处理速度变慢;
3、增加了系统的复杂度。
网友评论