Java动态代理

·3476·8 分钟·
AI摘要: 本文介绍了Java动态代理的两种实现方式:JDK动态代理和CGLIB动态代理。JDK动态代理通过`InvocationHandler`接口和`Proxy`类实现,避免了手动编写代理类,提高了开发效率。而CGLIB动态代理则通过反射机制在目标类的字节码中生成子类并重写方法来实现代理,适用于需要彻底增强各种类的场景。

动态代理在java中非常常用,但是主要是在框架中运用广泛,掌握之后对于理解框架比较好,日常开发很少会直接用到动态代理。

动态代理

首先是何为代理,当我们想要为某个类增加新的功能,但是又不想修改原来的类,可以写一个代理类来处理,这就是静态代理。如果我们直接在原来的类里面修改,可能越修改越长,导致函数或者类内部逻辑异常复杂,不利于维护。

试想,在一个已经写好的业务代码,比如有个函数叫updateDataBase,我们希望增加日志打印的功能,在updateDataBase内部多加日志代码就很不好看,导致真正的业务代码淹没在日志代码中,导致维护性差。

何须动态代理?何必动态?

  1. 静态代理需要手动为每个目标类写一个代理类,一个两个还好,如果有成千上万个目标类,想为它们都加上日志逻辑,写一个代理类是非常痛苦耗时的

  2. 如果想修改代理逻辑,不仅仅加上日志功能,还加上其他功能,那么修改写好的代理类是一件费时费力的事情

JDK动态代理

一种比较常见的代理实现方式是通过JDK实现,只需要通过InvocationHandler接口和Proxy类就能避免手动写代理类。

Proxy用来生成代理类对象,可以看做是代理类的工厂方法,使用频率最高的方法是newProxyInstance(), 用这个方法来生成一个代理对象。


Blog static Object newProxyInstance(ClassLoader loader,

                                      Class<?>[] interfaces,

                                      InvocationHandler h)

    throws IllegalArgumentException

{

    ......

}

  • loader: 类加载器。用于加载代理对象

  • interfaces:被代理类实现的一些接口

  • h:实现了InvocationHandler接口的对象

其中,InvocationHandler用来自定义处理逻辑,当我们调用代理类的方法的时候,会被转发到InvocationHandler类的invoke方法上,在invoke方法内部才会调用目标类的方法。


Blog interface InvocationHandler {

/**

 * 当你使用代理对象调用方法的时候实际会调用到这个方法

 */

Blog Object invoke(Object proxy, Method method, Object[] args)

    throws Throwable;

}

具体而言,在InvocationHandler接口类的invoke方法中,需要传入三个参数:

  • proxy:动态生成的代理类

  • method:

  • args:参数

实现步骤

  1. 定义一个接口和其实现类

  2. 自定义InvocationHandler并重写invoke方法

  3. 通过Proxy来创建代理对象


// 创建一个接口

Blog interface Celebrity {

    void performActivity(String activity);

}


// 真正的目标类,也就是需要代理的目标对象

Blog class ZhangSan implements Celebrity {

    @Override

    Blog void performActivity(String activity) {

        System.out.println("张三正在做活动:" + activity);

    }

}


// 代理类需要实现InvocationHandler接口类的invoke方法

Blog class CelebrityAgent implements InvocationHandler {

    private Celebrity celebrity;



    Blog CelebrityAgent(Celebrity celebrity) {

        this.celebrity = celebrity;

    }



    @Override

    Blog Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("经纪人正在安排活动...");

        Object result = method.invoke(celebrity, args);

        System.out.println("活动安排完成!");

        return result;

    }

}


// 使用Proxy动态创建一个代理类

Blog class DynamicProxyDemo {

    Blog static void main(String[] args) {

        Celebrity realCelebrity = new ZhangSan(); // 这是我们真正的明星

        Celebrity agentCelebrity = (Celebrity) Proxy.newProxyInstance(

                Celebrity.class.getClassLoader(),

                new Class[]{Celebrity.class},

                new CelebrityAgent(realCelebrity)

        );



        agentCelebrity.performActivity("签售会"); // 这里我们通过“经纪人”来安排活动

    }

}

那么,这样就通过jdk实现了动态代理。

回到之前提到的静态代理的缺点,在动态代理中,只需要定义一次实现了InvocationHandlerCelebrityAgent代理类,就能为所有的目标类(比如张三、李四、王五)都创建一个代理类,这在静态代理中是需要手写三个代理类的。另外,如果我们想要修改代理的逻辑,就直接在CelebrityAgent中修改一次就行,十分方便。

CGLIB动态代理

Jdk动态代理的原理是基于接口和反射,对目标接口生成增强后的代理类,并在内部通过反射来调用目标类的原始方法。因此,Jdk动态代理是和接口强绑定的,这在编程的时候大大减少了使用范围。比如,我们希望给我们写好的Spring Boot应用的所有Controller方法增加日志记录的功能,但是这些Controller方法压根都没有去实现一个统一的接口,JDK动态代理就难以实现。

为了彻底放飞自我,无拘无束地增强各种类,Spring中重点使用的是CGLIB的动态代理,通过自动生成目标类的子类,重写目标方法的方法来实现代理。具体而言,在目标类的字节码中修改生成子类(代理类)

就二者的效率来说,大部分情况都是 JDK 动态代理更优秀。

Kaggle学习赛初探