学习Spring框架时,遇到动态代理,就将代理模式整理一下
1. 什么是设计模式?
- 设计模式:一些通用的解决固定问题的方式
2. 为什么需要代理模式?
可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强。
3. 什么是代理模式
代理模式作为23种经典设计模式之一,其比较官方的定义为“为其他对象提供一种代理以控制对这个对象的访问”,简单点说就是,之前A类自己做一件事,在使用代理之后,A类不直接去做,而是由A类的代理类B来去做。代理类其实是在之前类的基础上做了一层封装。java中有静态代理、JDK动态代理、CGLib动态代理的方式。静态代理指的是代理类是在编译期就存在的,相反动态代理则是在程序运行期动态生成的。
概念:
- 真实对象:被代理的对象
- 代理对象:增强真实对象
- 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的
4. 实现方式
1. 静态代理
有一个类文件描述代理模式
1)实现步骤:
- 代理对象与真实对象实现相同的接口
- 代理对象中创建真实对象
- 使用真实对象调用方法
- 增强方法
2)具体实例
接口:
1 | package site.qujq.staticProxy; |
被代理类(联想电脑厂商):
1 | package site.qujq.staticProxy; |
代理类(经销商):
1 | package site.qujq.staticProxy; |
在不使用代理的情况下测试:
1 | package site.qujq.staticProxy; |
结果是:
1 | 卖出一台电脑,价值:8000.0 |
使用代理的情况下进行测试:
1 | package site.qujq.staticProxy; |
结果是:
1 | 卖出一台电脑,价值:8000.0 |
对比,使用静态代理和不使用静态代理,可以发现使用了代理之后,可以在被代理方法的执行前或后加入别的代码,实现诸如权限及日志的操作。
但,静态代理也存在一定的问题,如果被代理方法很多,就要为每个方法进行代理,增加了代码维护的成本。有没有其他的方式可以减少代码的维护,那就是动态代理。
2. JDK动态代理
在内存中形成代理类
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:
- 基于接口的动态代理(本节)
- 基于子类的动态代理(下一小节)
1) 实现步骤:
- 代理对象和真实对象实现相同的接口
- 代理对象 = Proxy.newProxyInstance();
- 使用代理对象调用方法。
- 增强方法
2)增强方式:
- 增强参数列表
- 增强返回值类型
- 增强方法体执行逻辑
3)具体实现
接口:(与静态代理一样)
1 | package site.qujq.staticProxy; |
被代理对象(联想电脑厂商):
1 | package site.qujq.proxy; |
代理对象(经销商):
1 | package site.qujq.proxy; |
不使用代理的测试和上面一样,不再叙述。
测试结果:
1 | 这里是代理。。。。 |
从上面可以看出代理类是由Proxy这个类通过newProxyInstance方法动态生成的,生成对象后使用“实例调用方法”的方式进行方法调用,那么代理类的被代理类的关系只有在执行这行代码的时候才会生成,因此成为动态代理。
JDK的动态代理也存在不足,即被代理类必须要有实现的接口,如没有接口则无法使用动态代理(从newProxyInstance方法的第二个参数可得知,必须传入被代理类的实现接口),那么需要使用CGLib动态代理。
3. CGLib动态代理
CGLib代理是功能最为强大的一种代理方式,因为其不仅解决了静态代理需要创建多个代理类的问题,还解决了JDK代理需要被代理对象实现某个接口的问题。对于需要代理的类,如果能为其创建一个子类,并且在子类中编写相关的代理逻辑,因为“子类 instanceof 父类”,因而在进行调用时直接调用子类对象的实例,也可以达到代理的效果。CGLib代理的原理实际上是动态生成被代理类的子类字节码,由于其字节码都是按照jvm编译后的class文件的规范编写的,因而其可以被jvm正常加载并运行。这也就是CGLib代理为什么不需要为每个被代理类编写代理逻辑的原因。这里需要注意的是,根据CGLib实现原理,由于其是通过创建子类字节码的形式来实现代理的,如果被代理类的方法被声明final类型,那么CGLib代理是无法正常工作的,因为final类型方法不能被重写。
1)细节
基于子类的动态代理:
- 涉及的类:Enhancer
- 提供者:第三方cglib库
如何创建代理对象:
- 使用Enhancer类中的create方法
创建代理对象的要求:
被代理类不能是最终类
create方法的参数:
Class:字节码
它是用于指定被代理对象的字节码。
Callback:用于提供增强的代码
它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。我们一般写的都是该接口的子接口实现类:MethodInterceptor
2)具体实现
被代理对象(电脑厂商):此时不需要实现接口
1 | package site.qujq.cglibProxy; |
代理对象:
1 | package site.qujq.cglibProxy; |
结果是:
1 | 这里是CGLib动态代理。。。。 |
5.总结
对静态代理、JDK动态代理、CGLib动态代理做一个总结,静态代理的维护成本比较高,有一个被代理类就需要创建一个代理类,而且需要实现相同的接口。动态代理模式和CGLib动态代理的区别是JDK动态代理需要被代理类实现接口,而CGLib则是生成被代理类的子类,要求被代理类不能是final的,因为final类无法被继承。