代理模式
代理模式简介
代理模式是一种结构性设计模式。用于无法直接控制和访问目标对象时,通过给目标对象提供一个代理对象,通过代理对象来间接操作目标对象。
代理模式的应用场景很多,比如Spring AOP、RPC远程调用、服务器反向代理等,通常用来不改变目标类的情况下,对目标对象进行保护或拓展。
代理模式的分类
代理模式按照使用场景进行分类,可分为以下几类:
- 远程代理。
- 虚拟代理。
- 保护代理。
- Cache代理。
- 同步化代理。
- 智能引用代理。
由于各种编程语言的语言特性不尽相同,在不同的语言中代理模式的实现也有所不同。由于在Java可以在运行时在JVM中创建新的字节码,所以可以根据代理类字节码创建的时机,将代理模式分为静态代理和动态代理,静态代理是指在编译期就生成代理类,动态代理是指在运行期生成代理类。其中,动态代理又由生成字节码方式的不同分为JDK动态代理和CGLib动态代理。
静态代理
静态代理实现
Subject是对代理行为的抽象,是代理类和目标类的公共接口。
public interface Subject {void request();}
RealSubject是目标类,这里实现真正的doSomeThing的行为
public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("Do something");}}
Proxy是代理类,对目标类的行为进行封装和增强。
public class Proxy implements Subject {private Subject target; // 被代理的对象Proxy(Subject target) {this.target = target;}@Overridepublic void request() {before(); // 对目标类增强的行为target.request(); // 调用目标对象的方法after(); // 对目标类增强的行为}private void before() {System.out.println("Before ...");}private void after() {System.out.println("After ...");}}
在客户端类中使用代理对象来替代目标对象
public class Client {public static void main(String[] args) {RealSubject realSubject = new RealSubject(); // 目标对象Proxy proxy = new Proxy(realSubject); // 代理对象proxy.request(); // 通过代理对象去操作目标对象}}
运行结果
Before ...Do somethingAfter ...
静态代理优缺点
优点
静态代理是代理模式实现方式的一种,所以它拥有代理模式的优点,在不改变目标类的情况下对目标类进行保护和增强。
缺点
- 当需要代理的行为要改变时,目标类和代理类都有进行修改,不宜维护。
- 一个代理类只能为一个接口服务,程序开发过程中会出现很多代理类。
- 当需要对大量行为进行同样的扩展的时候(比如在所有的目标对象方法中都要添加同样的after和before),需要在代理类中为每个行为写很多相同的代码。
JDK动态代理
在JDK中,提供了Proxy.newProxyInstance()方法来生成代理类,使用方法如下。
JDK动态的使用
代理接口
public interface Subject {void request();}
RealSubject是目标类,这里实现真正的doSomeThing的行为
public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("Do something");}}
SubjectInvocationHandler
getProxy()函数中通过Proxy.newProxyInstance()方法返回通过反射生成的代理类的对象,Proxy.newProxyInstance()方法的三个参数含义如下。
- ClassLoader loader:代理类的类加载器
- Class<?>[] interfaces:代理类需要实现的接口
InvocationHandler handler:代理对象方法调用的实际处理者,所有接口中的方法以及hashCode()、equals()、toString()调用时都会调用handler的invoke()方法。
public class SubjectInvocationHandler implements InvocationHandler {// 目标对象private Subject target;public SubjectInvocationHandler(Subject target) {this.target = target;}public Subject getProxy() {// 通过反射生成代理类的代理对象。return (Subject) Proxy.newProxyInstance(// 指定生成的代理类的类加载器Thread.currentThread().getContextClassLoader(),// 这是获取target的接口,也可以直接传入接口,比如这里传入new Class[]{Subject.class}this.target.getClass().getInterfaces(),//要绑定的InvocationHandlerthis);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();Object result = method.invoke(target, args); // 调用目标对象的方法after();return result; // 返回目标对象方法的返回值}private void before() {System.out.println("Before ...");}private void after() {System.out.println("After ...");}}
public class Client {public static void main(String[] args) {System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");RealSubject realSubject = new RealSubject(); // 目标对象SubjectInvocationHandler handler = new SubjectInvocationHandler(realSubject);Subject proxy = handler.getProxy(); // 真正的代理对象proxy.request(); // 通过代理对象去操作目标对象}}
JDK动态代理的原理
JDK动态代理会生成一个java.lang.reflect.Proxy类的子类,然后由指定的类加载器加载到方法区中,这个子类名为”com.sun.proxy.$ProxyN“,并实现了目标接口的方法,并在接口方法的函数体中通过反射去调用Object invoke(Object proxy, Method method, Object[] args) 方法,这样,代理接口中所有方法以及Object中的hashCode()、equals()、toString()调用时都会进入到 invoke(Object proxy, Method method, Object[] args)方法中。
具体原理解析见JDK动态代理源码分析
JDK动态代理的使用限制
由于JDK动态代理的实现是运行时在JVM中生成一个名为com.sun.proxy.$ProxyN的Class,这个Class继承了java.lang.reflect.Proxy类,而Java中只能单继承,所以JDK动态代理必须将代理的行为定义在一个或多个接口中,如果目标类没有实现接口则无法使用JDK动态代理。
其次,由于JDK动态代理的实现是在com.sun.proxy.$ProxyN实现的接口中通过反射调用InvocationHandler的invoke方法,所以会存在性能的问题。
CGLib动态代理
CGLib动态代理的使用
目标类RealSubject
public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("Do something");}}
方法拦截器类,继承MethodInterceptor并实现intercept方法,在intercept中进行拦截。
intercept的参数如下。
- Object object:代理的目标对象
- Method method:拦截的方法
- Object[] objects:参数参数列表
MethodProxy methodProxy:方法代理
public class Interceptor implements MethodInterceptor {@Overridepublic Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {before();// 这里使用方法代理的invokeSuper方法调用目标类的方法,目标类是代理类的子类。// 使用invoke是调用代理类的方法,会造成死循环。Object result = methodProxy.invokeSuper(object, objects);after();return result; // 返回代理后的结果}private void before() {System.out.println("Before ...");}private void after() {System.out.println("After ...");}}
客户端类
public class Client {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(RealSubject.class); // 设置超类,cglib是通过继承来实现的enhancer.setCallback(new Interceptor());RealSubject realSubject = (RealSubject)enhancer.create(); // 创建代理类并返回一个代理类对象realSubject.request(); // 通过代理类调用方法}}
CGLib动态代理的原理
CGLib动态代理和JDK动态代理都是在代理类方法中进行拦截,不过二者在动态生成代理类的方法不同,JDK通过创建一个Proxy类的子类并实现代理接口的方法来生成代理类,CGLib通过ASM生成目标类的子类(子类重写目标类的所有非final方法)来生成代理类。
CGLib动态代理的使用限制
- 由于Cglib是通过生成目标类的子类来实现代理类,所以Cglib无法对声明为final的目标类进行代理。
CGLib在创建代理对象时花费的时间比JDK多很多,对于需要频繁创建对象的情况,使用CGLib代价会比较大。
三种代理方式的比较
从代理类生成时间上来看,静态代理的代理类在编译期就生成了,JDK和CGLib在运行时生成,前者不需要消耗JVM运行时间,但后者具有动态性,更加灵活。
- 从代理类生成方式上来看,静态代理的代理类人为实现(可接口,可继承)生成代理类,JDK代理通过创建一个Proxy类的子类并实现代理接口的方法来生成代理类,CGLib代理通过ASM生成目标类的子类来生成代理类。
- 从使用限制上来看,静态代理和CGLib对目标类没有要求(可接口,可为类),JDK的目标类需要额外的接口。
- 从性能消耗上来看,静态代理的带运行时没有额外的性能消耗,JDK动态代理在方法调用时(反射)性能消耗较大,CGLib在生成对象时性能消耗较大。
