二十四种设计模式(3)-代理模式
前言
代理模式是很多框架的底层应用,spring提供了很好的代理模式,一般不用开发者手动编写代理模式实现业务,但是原理很重要。代理分为静态代理和动态代理,根据实现方式又分为JDK动态代理和CGLIB动态代理,下面从两个维度分析了代理模式:
- 从源码角度分析了JDK动态代理和CGLIB动态代理的区别及适用场景;
- 静态代理和动态代理的区别,为什么静态代理使用比较少?
代理模式-JDK动态代理
代理模式分为两种:静态代理和动态代理。静态代理是在代码编译期,就给被代理对象生成了一个不可改变的代理类,代理少的时候可以用,效果比动态代理更好。动态代理就是在运行期间动态生成代理类,需要消耗的时间会更久一点,但是数量太多的时候使用更方便。
下面给出静态代理的例子。zhangsan给wangwu送花、邀请看电影,由lisi代替去做。这里zhangsan就是代理类,lisi就是被代理类。
-
创建业务接口
-
创建业务接口的实现类,重写业务方法
-
创建业务接口的代理实现类,重写业务方法。代理实现类中必须有被代理的实现类
-
执行及结果
从上面的静态代理示例可看出两个问题:
首先,一般业务接口有很多,可以理解为我们写spring项目的service接口,需要为每个业务实现类都写一个静态代理,这样代理代码会很多。
其次,一个业务接口中,会有很多业务方法,代理类中需要重写每个业务方法,假如都是给业务方法加上事务,那就会有很多重复的代码。
为了解决上述问题,基于反射实现动态代理,动态代理只需要传入被代理类,就能够获取到代理对象,完成功能的增强,适用于所有实现了接口的类。代理类是在使用的时候获取的,也就是在使用的时候,通过传入的被代理类,通过实现被代理类的接口,实现代理。
下面实现事务的动态代理:
5. 业务接口
-
业务接口实现类
-
新建类,实现InvocationHandler接口。动态代理没有明确的LiSi了,而是使用反射机制动态获取LiSi,也就是代理类需要动态获取。
-
执行接结果
看完动态代理的例子,下面探讨一下为什么JDK动态代理要求被代理类必须实现了某一接口,或者本身是接口?
这是因为动态代理生成的代理类继承了Proxy类,并且会实现被代理对象实现的所有接口,看一下Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);这句代码,在创建代理类对象的时候,传入了被代理类的所有接口target.getClass().getInterfaces(),代理类会去实现这些被传入的接口,从而重写所有接口的所有方法。由此可见,我们可以将生成的代理类强转为任意一个被代理类实现的接口或者Proxy去使用,但是Proxy里面几乎全是静态方法,没有实例方法,所以转换成Proxy意义不大,几乎没什么用。假设我们的被代理类没有实现任何接口,那么就意味着你只能将生成的代理类强转换成Proxy,那么就算生成了代理类,其实也没什么用。
网上给出了一个方法,可以将代理类的class文件保存到本地,然后使用反编译工具看到代理类的代码,下面是反编译之后的部分代码,其中VIPMovie是业务接口,GangTieXiaMovie实现了VIPMovie接口,动态代理Proxy0可以看到继承了Proxy接口,并且实现了VIPMovie业务接口。
代理模式-CGLIB动态代理
JDK动态代理要求被代理类必须实现了接口,如果被代理类没有实现接口,那么使用JDK动态代理就会出现问题,因为代理类已经继承了Proxy类,如果再继承被代理类,就会出现问题,因为java是单继承。这就出现了Cglib动态代理,但是被代理类需要被继承,因此被代理类不能是final修饰的类。示例如下:
-
创建业务类,不能被final修饰,不实现接口
-
创建动态代理类,底层一样是反射机制实现
-
结果及展示
可以看得出来,不管是JDK还是Cglib动态代理,都是利用了反射机制,虽然实现原理有点不同,但是代码结构很类似。网上给出了一个方法,可以将代理类的class文件保存到本地,然后使用反编译工具看到代理类的代码,下面是反编译之后的部分代码,其中CaptainAmerica2MovieImpl是业务类,动态代理CaptainAmerica2MovieImpl
E
n
h
a
n
c
e
r
B
y
C
G
L
I
B
EnhancerByCGLIB
EnhancerByCGLIB5c3ddcfe继承了CaptainAmerica2MovieImpl接口,并且实现了Factory接口。
从代理对象反编译源码可以知道,代理对象继承于CaptainAmerica2MovieImpl ,拦截器调用intercept()方法, intercept()方法由自定义CglibProxyInterceptor实现,所以,最后调用CglibProxyInterceptor中的intercept()方法,从而完成了由代理对象访问到目标对象的动态代理实现。