动态代理

代理模式

代理模式(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();
    }
}

静态代理有两个弱点:

  1. 代理对象的一个接口只服务于一种类型的对象,要为每个服务创建代理类,程序规模稍大,静态代理就无法胜任了
  2. 若接口增加方法,除了每个实现类需要实现这个方法,所有的代理类也都要实现这个方法,增加代码维护的复杂度

于是乎到了”动态代理“上场的时候了

动态代理

JDK动态代理

JDK动态代理主要依赖反射,并依赖JDK中的java.lang.reflect.Proxy、java.lang.ClassLoader、java.lang.reflect.InvocationHandler。

实现动态代理主要是下面几个步骤:

  1. 实现InvocationHandler接口,创建自己的调用处理器
  2. 给Proxy类提供代理类的ClassLoader和代理接口类型创建动态代理类
  3. 以调用处理器的Class对象为参数,利用反射机制得到动态代理类的构造函数
  4. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象

下面同样以买房来个示例:
首先实现自己的调用处理器:

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");这段代码可以把生成的代理类字节码文件保存下来,如下

从上图可以看出:

  1. 代理类继承了Proxy接口,并且实现了要代理的接口,由于Java不支持多继承,所以JDK动态代理不能代理类
  2. 重写了hashCode、equals、toString方法
  3. 有个静态代码块,通过反射获取代理类的所有方法
  4. 通过invoke执行代理类中的目标方法buyHouse

CGLIB代理

使用CGLIB大致分为四个步骤:

  1. 创建被代理的对象
  2. 创建方法拦截器
  3. 创建代理对象
  4. 调用代理对象

如下示例:

首先是被代理类

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机制首先会将类的方法信息解析出来,然后为其建索引,调用的时候只要传索引,就能找到相应的方法进行调用。

  1. 为所有的方法建索引
  2. 调用前先根据方法信息得到索引
  3. 调用时根据索引匹配相应的方法

CGLIB在字节码层面将方法和索引的关系建立,避免了反射调用,反编译后得到getIndex源代码如下:

上述方法获得索引,再利用索引根据下面的方法进行调用

反编译代理对象的源码前先看下代理调用的过程图:

从图中可以看出:

  1. 客户端调用代理对象的被代理方法
  2. 代理对象将调用委派给方法拦截器统一接口intercepter
  3. 方法拦截器执行前置操作,然后调用方法代理的统一接口invokeSuper
  4. 方法代理的invokeSuper初始化代理对象和被代理对象的fastClass
  5. 初始化后再调用代理对象的fastClass
  6. 代理对象的fastClass能够快速的找到代理对象的代理方法
  7. 代理对象的代理方法再调用被代理对象的被代理方法
  8. 执行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修饰

你可能感兴趣的:(java)