代理模式(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中的java.lang.reflect.Proxy、java.lang.ClassLoader、java.lang.reflect.InvocationHandler。
实现动态代理主要是下面几个步骤:
下面同样以买房来个示例:
首先实现自己的调用处理器:
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");这段代码可以把生成的代理类字节码文件保存下来,如下
从上图可以看出:
使用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源代码如下:
上述方法获得索引,再利用索引根据下面的方法进行调用
反编译代理对象的源码前先看下代理调用的过程图:
从图中可以看出:
现在来看反编译后的代理对象的源代码,运行时实际上回生成三个class,分别是:
生成的代理类如下:
可以看出代理类是继承了对象,这点与JDK不同,jdk是实现了接口
下面是代理对象的代理方法
intercepter方法中比较让人关注的是CGLIB$buyHouse$0$Method参数
这个参数是在代理类被加载时会执行其静态方法,创建buyHouse方法的代理方法
再点进create方法瞧一瞧
其中,c1是被代理类,c2是代理类,desc是代理方法和备代理方法的参数信息,name1是被代理方法名,name2是代理方法名,该方法创建了代理方法
再来看下intercepter方法中调用methodProxy的invokeSuper方法
看下初始化方法init()
初始化后就能拿到代理类和代理方法的索引,就能按照索引对代理方法进行调用
通过索引直接查到代理类的代理方法为CGLIB$buyHouse$0
这样就能在该方法中调用被代理类的被代理方法
CGLIB代理的大致流程就是这样
CGLIB代理和JDK代理的不同点:
共同点: