代理模式
代理模式 (Proxy) 就是为一个对象创建一个替身,用来控制对当前对象的访问,目的就是为了在不直接操作对象的前提下对对象进行访问。
为什么要用代理模式?
- 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
代理模式主要分为两种:
- 静态代理:在运行前,就确定好代理类、被代理类之间的关系
- 动态代理:在运行时动态的创建一个代理类,实现一个或多个接口,将方法的调用转发到指定的类
静态代理
第一步:创建服务类接口
public interface BuyHouse {void buyHouse();
}
第二步:实现服务类接口
public class BuyHouseImpl implements BuyHouse {public void buyHouse() {System.out.println("我要买房");
}
}
第三部:创建代理类
public class BuyHouseProxy implements BuyHouse {
private BuyHouse buyHouse;
public BuyHouseProxy(BuyHouse buyHouse) {this.buyHouse = buyHouse;}
public void buyHouse() {System.out.println("买房前的准备");
buyHouse.buyHouse();
System.out.println("买房后的装修");
}
}
第四步:测试
public class ProxyTest {public static void main(String[] args) {BuyHouse buyHouse = new BuyHouseImpl();
buyHouse.buyHouse();
BuyHouse buyHouseProxy = new BuyHouseProxy(buyHouse);
buyHouseProxy.buyHouse();}
}
静态代理有两个弱点:
- 代理对象的一个接口只服务于一种类型的对象,要为每个服务创建代理类,程序规模稍大,静态代理就无法胜任了
- 若接口增加方法,除了每个实现类需要实现这个方法,所有的代理类也都要实现这个方法,增加代码维护的复杂度
于是乎到了”动态代理“上场的时候了
动态代理
JDK 动态代理
JDK 动态代理主要依赖反射,并依赖 JDK 中的 java.lang.reflect.Proxy、java.lang.ClassLoader、java.lang.reflect.InvocationHandler。
实现动态代理主要是下面几个步骤:
- 实现 InvocationHandler 接口,创建自己的调用处理器
- 给 Proxy 类提供代理类的 ClassLoader 和代理接口类型创建动态代理类
- 以调用处理器的 Class 对象为参数,利用反射机制得到动态代理类的构造函数
- 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
下面同样以买房来个示例:
首先实现自己的调用处理器:
public class MyInvocationHandler implements InvocationHandler {
private Object obj;
public MyInvocationHandler(Object obj) {this.obj = obj;}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("买房前的准备");
method.invoke(obj, args);
System.out.println("买房后的装修");
return null;
}
}
测试类:
public class DynamicProxyTest {public static void main(String[] args) {
// 保存生成的代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
BuyHouse buyHouse = new BuyHouseImpl();
InvocationHandler invocationHandler = new MyInvocationHandler(buyHouse);
Class clazz = buyHouse.getClass();
BuyHouse buyHouseProxy = (BuyHouse)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), invocationHandler);
buyHouseProxy.buyHouse();}
}
从上可以看出主要是 Proxy.newProxyInstance 这个方法去生成代理类,现在来看看这个方法的实现
再看上图中的 getProxyClass0()方法,这个方法主要是生成代理类的字节码文件
直接看下 ProxyClassFactory 如何生成代理类
在测试类中加上 System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”); 这段代码可以把生成的代理类字节码文件保存下来,如下
从上图可以看出:
- 代理类继承了 Proxy 接口,并且实现了要代理的接口,由于 Java 不支持多继承,所以 JDK 动态代理不能代理类
- 重写了 hashCode、equals、toString 方法
- 有个静态代码块,通过反射获取代理类的所有方法
- 通过 invoke 执行代理类中的目标方法 buyHouse
CGLIB 代理
使用 CGLIB 大致分为四个步骤:
- 创建被代理的对象
- 创建方法拦截器
- 创建代理对象
- 调用代理对象
如下示例:
首先是被代理类
public class BuyHouseService {public void buyHouse() {System.out.println("我要买房");
}
}
然后是拦截器
public class BuyHouseServiceIntercepter implements MethodInterceptor {public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("买房前的准备");
proxy.invokeSuper(obj, args);
System.out.println("买房后的装修");
return null;
}
}
测试类
public class CglibTest {public static void main(String[] args) {System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/xiechao/myWork/learn/com/sun/cglib");
//Enhancer 是生成代理类的工厂
Enhancer enhancer = new Enhancer();
// 设置代理的超类 即被代理的对象
enhancer.setSuperclass(BuyHouseService.class);
// 设置拦截方法
enhancer.setCallback(new BuyHouseServiceIntercepter());
// 生成代理对象
BuyHouseService buyHouseService = (BuyHouseService)enhancer.create();
buyHouseService.buyHouse();}
}
看下 CGLIB 的核心代码
fastClass 机制
JDK 动态代理使用反射调用目标对象的方法,在 CGLIB 中为了更好的提升性能,采用 fastClass 机制。fastClass 机制首先会将类的方法信息解析出来,然后为其建索引,调用的时候只要传索引,就能找到相应的方法进行调用。
- 为所有的方法建索引
- 调用前先根据方法信息得到索引
- 调用时根据索引匹配相应的方法
CGLIB 在字节码层面将方法和索引的关系建立,避免了反射调用,反编译后得到 getIndex 源代码如下:
上述方法获得索引,再利用索引根据下面的方法进行调用
反编译代理对象的源码前先看下代理调用的过程图:
从图中可以看出:
- 客户端调用代理对象的被代理方法
- 代理对象将调用委派给方法拦截器统一接口 intercepter
- 方法拦截器执行前置操作,然后调用方法代理的统一接口 invokeSuper
- 方法代理的 invokeSuper 初始化代理对象和被代理对象的 fastClass
- 初始化后再调用代理对象的 fastClass
- 代理对象的 fastClass 能够快速的找到代理对象的代理方法
- 代理对象的代理方法再调用被代理对象的被代理方法
- 执行 intercepter 的后置操作,方法结束
现在来看反编译后的代理对象的源代码,运行时实际上回生成三个 class,分别是:
- 代理类
- 代理类对应的 fastClass
- 被代理类对应的 fastClass
生成的代理类如下:
可以看出代理类是继承了对象,这点与 JDK 不同,jdk 是实现了接口
下面是代理对象的代理方法
intercepter 方法中比较让人关注的是 CGLIB$buyHouse$0$Method 参数
这个参数是在代理类被加载时会执行其静态方法,创建 buyHouse 方法的代理方法
再点进 create 方法瞧一瞧
其中,c1 是被代理类,c2 是代理类,desc 是代理方法和备代理方法的参数信息,name1 是被代理方法名,name2 是代理方法名,该方法创建了代理方法
再来看下 intercepter 方法中调用 methodProxy 的 invokeSuper 方法
看下初始化方法 init()
初始化后就能拿到代理类和代理方法的索引,就能按照索引对代理方法进行调用
通过索引直接查到代理类的代理方法为 CGLIB$buyHouse$0
这样就能在该方法中调用被代理类的被代理方法
CGLIB 代理的大致流程就是这样
总结
CGLIB 代理和 JDK 代理的不同点:
- JDK 动态代理必须实现接口,CGLIB 既支持对接口的代理也支持对对象的代理
- CGLIB 使用 FastClass 机制快速调用被代理类,JDK 中使用反射调用被代理类,所以 CGLIB 性能上更有优势
共同点:
- 都有统一接口,JDK 的是 InvocationHandler,CGLIB 是 MethodIntercepter
- 生成的代理类其中方法都被 final 修饰