设计模式之动态代理
一、什么是代理模式?
***1. 代理模式的定义:***为其他对象提供一种代理以控制对这个对象的访问。使用代理模式创建代理对象,让代理对象控制目标对象的访问(目标对象可以是远程的对象、创建开销大的对象或需要安全控制的对象),并且可以在不改变目标对象的情况下添加一些额外的功能。
通俗的说,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用
2. 代理模式涉及到的角色:
(1)抽象角色:声明真实对象和代理对象的共同接口;可以是抽象类,也可以是接口,是一个最普通的业务类型定义,无特殊要求。
(2)代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
也叫委托类、代理类。它把所有抽象角色定义的方法给真实角色实现,并且在真实角色处理完毕前后做预处理和善后工作。(最简单的比如打印日志)(自己并未实现业务逻辑接口,而是调用真实角色来实现。)
(3)真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。也叫被委托角色、被代理角色。是业务逻辑的具体执行者。(真正实现了业务逻辑接口。)
示意图:
3. 特征:
实现共同的接口以便于增强某个共有的方法,代理类拥有真实类的引用
二、静态代理
***1. 静态代理模式:***静态代理说白了就是在程序运行前就已经存在代理类的字节码文件,代理类和原始类的关系在运行前就已经确定。最大的特点就是在程序运行时代理类的.class文件就已经存在了,但是这有一个很大的缺陷即每一个代理类只能为一个接口服务。
2. 静态代理案例:
1 | package test.staticProxy; |
3. 总结:
如果要按照上述的方式(静态代理)使用代理模式,那么真实角色必须是实现已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,大量使用静态代理会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。
三、动态代理
***1. 动态代理模式简介:***动态代理类(Proxy)的源码是在程序运行期间通过JVM反射等机制动态生成,代理类和委托类的关系是运行时才确定的。
2. 动态代理模式两种创建方式:
Foo是一个interface
(1)getProxyClass()静态方法负责创建动态代理类,它的完整定义如下:
1 | public static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces) throws IllegalArgumentException |
例子:
1 | /**** 方式一 ****/ |
参数loader 指定动态代理类的类加载器,参数interfaces 指定动态代理类需要实现的所有接口。
由此可以知道: 生成的代理类没有无参构造函数,只有一个拥有InvocationHandler参数的构造器,利用这个构造器给生成的代理类中的InvocationHandler属性赋值。
(2)newProxyInstance()静态方法负责创建动态代理类的实例,它的完整定义如下:
1 | public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException |
CLassLoader loader: 类的加载器 (指定动态代理类的类加载器)
Class<?>[] interfaces: 得到全部的接口 (指定动态代理类需要实现的所有接口)
InvocationHandler h: 得到InvocationHandler接口的子类的实例 (指定与动态代理类关联的 InvocationHandler 对象)
例子:
1 | /**** 方式二 ****/ |
假定变量foo 是一个动态代理类的实例,并且这个动态代理类实现了Foo 接口,那么”foo instance of Foo”的值为true。把变量foo强制转换为Foo类型是合法的:(Foo) foo //合法
但是要注意的是,这里的Foo是接口,强制转换时,必须使用接口,不能强制转换为某个实现类,如果使用Foo的实现类进行强制转换则会报错 java.lang.ClassCastException: com.sun.proxy.$Proxy13 cannot be cast to ….
因为JDK代理只能代理接口不能代理类,生成的代理类都是接口,具体的实现类在handler里面。
3. 运用案例:
1 | /***先创建需要动态代理的接口*****/ |
4. 总结:
所谓动态代理=动态编译+反射技术,就是通过用户传进来的接口去动态生成java类,在动态生成的java类中去组合InvocationHandler对象的引用,并在这个对象中的每个方法里都执行了InvocationHandler的invoke方法。这样就给外部提供了一个可变化的接口,用户就可以根据需要去实现InvocationHandler里面的invoke方法,从而实现动态的增加功能。
5. 补充:
但这里又引入一个问题,如果某个类没有实现接口,就不能使用JDK动态代理,所以Cglib代理就是解决这个问题的。
Cglib是以动态生成的子类继承目标的方式实现,在运行期动态的在内存中构建一个子类
四、spring中的动态代理模式
**前文提到JDK代理和Cglib代理两种动态代理,优秀的Spring框架把两种方式在底层都集成了进去,我们无需担心自己去实现动态生成代理。那么,Spring是如何生成代理对象的?**创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。
如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用Cglib代理。然后从容器获取代理后的对象,在运行期植入”切面”类的方法。通过查看Spring源码,我们在DefaultAopProxyFactory类中,找到这样一段话
简单的从字面意思看出,如果有接口,则使用Jdk代理,反之使用Cglib,这刚好印证了前文所阐述的内容。Spring AOP综合两种代理方式的使用前提有会如下结论:如果目标类没有实现接口,且class为final修饰的,则不能进行Spring AOP编程!
APO:就是通过动态代理生成代理对象,对象里面再传入你需要增强的功能包装成的类(此类不需要和目标类实现一样的接口,可以是完全无关的类),这样就能实现在原来的功能上增加功能。
那我们来手动实现一个AOP代理工厂类
1 | public class ProxyFactoryBean { |