首页 > 基础资料 博客日记

【Java】万字解读Java的动态代理(JDK原生动态代理、CGLIB动态代理)

2024-10-01 05:00:06基础资料围观83

本篇文章分享【Java】万字解读Java的动态代理(JDK原生动态代理、CGLIB动态代理),对你有帮助的话记得收藏一下,看Java资料网收获更多编程知识

1. 前言

动态代理在Java中有着广泛的应用,比如 Spring AOP、RPC 远程调用、Java 注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。

下面我将着重的介绍两个常用的动态代理:JDK原生动态代理CGLIB 动态代理

2. 代理模式

当我们谈及 Java 的 动态代理 或者是 静态代理 ,我们都很容易谈及到设计模式中的—— 代理模式

在这里我想要提出几个问题,什么是代理模式?代理模式的构成是怎想的?如何实现代理模式?代理模式在实际开发中的左右是什么?Java的动态代理与设计代理模式有什么关系?如何实现Java的动态代理?

下面我们将围绕着抛出的这几个问题来展开这篇博文。

代理模式的定义:

代理模式是给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问,起到对代理对象已有功能的增强。
代理模式是一种结构型设计模式。

代理模式一般会存在三个角色:
1、抽象主题角色(Subject)

抽象主题角色指的是 `真实对象` 与 `代理对象` 的公共的法所抽象出来的一个接口或者是抽象类。
在 Java 编程成,我们就可以认为是一个 `接口` 或者是 `抽象类` 。

2、真实主题角色(RealSubject)

真实主题角色指的是被代理对象。

3、代理主题角色(ProxySubject)

代理主题角色指的是代理对象。

代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层。

如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:

静态代理

所谓 `静态` 也就是在 `程序运行前` 就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。

动态代理

而 `动态` 代理的源码是在 `程序运行期间` 由 JVM 根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件。

3. 静态代理

在介绍 动态代理 之前,我们介绍一下 静态代理 。由上可推到出,无论动态代理还是静态代理,都是代理模式的一种实践。下面我们将实现一套基础的 静态代理代码

代理模式分为 3 个组成部分:抽象主题对象、真实主题对象、代理主题对象。

定义抽象主题接口(上面我们已经介绍了抽象主题对象就是接口或抽象类)。此案例中我们使用接口。

// 定义接口
interface Subject {
    void request();
}

定义真实主题类

// 定义被代理类
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("Do invoke request(), now.");
    }
}

定义抽象主题类

// 定义代理类
class ProxySubject implements Subject {
    private Subject realSubject;

    public ProxySubject(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        before();
        realSubject.request();
        after();
    }
    
    private void before() {
        System.out.println("准备开始执行方法...");
    }

    private void after() {
        System.out.println("方法方法执行结束...");
    }

}

定义客户端,查看执行效果

public class Client{
    public static void main(String[] args) {
        Subject realSubject = new RealSubject();
        Subject proxySubject = new ProxySubject(realSubject);
        proxySubject.request();
    }
}

输出结果:

准备开始执行方法...
Do invoke request(), now.
方法方法执行结束...

这样,我们就完成了一个静态代理的编写。从上面的代码我们不难发现 静态代理 存在一个问题,代理主题类与真实主题类之间的 耦合程度太高 ,当真实主题类中增加、删除、修改方法后,那么代理主题类中的也必须增加、删除、修改相应的方法,提高了代码的维护成本。另一个问题就是当代理对象代理多个 Subject 的实现类时,多个实现类中必然出在不同的方法,由于代理对象要实现与目标对象一致的接口(其实是包含关系),必然需要编写众多的方法,极其容易造成臃肿且难以维护的代码。

相比与于静态代理,动态代理则不存在上述的诸多问题,下面我们进入 JDK 的动态代理。

4. 动态代理

静态代理 章节中为我们可知,静态代理存在着诸多的问题,最主要的问题是静态代理类需要对被代理类做手动的方法映射。造成这个问题的原因是代理对象是通过硬编码得到的,是在程序编译前就已经存在的,所以顺着这个思路,我们不难得到一个方向,如何代理对象不是通过硬编码提前写好的,而是在程序运行中动态生产的,且生成的代理对象可以对被代理类的方法做自动的映射,那么问题不就解决了吗?是的,这也就是动态代理的大致解决方案。

4.1 JDK原生动态代理

JDK原生动态代理的组成分为三个部分: 抽象主题角色真实主题角色增强主题橘色

抽象主题角色和代理模式的抽象主题角色是一样的,都是抽象出来的接口或类,对于JDK原生动态代理而言,抽象主题角色就是接口。

真实主题角色和代理模式的真实主题角色一样,都是被代理类。

增强主题角色在JDK原生代理中值的是实现了 InvocationHandler 接口的类,其目的是对真实主题角色的方法的增强。 InvocationHandler 接口中只有一个方法 invoke 方法,所有的动态代理对象中的映射方法在执行时都是调用的 InvocationHandler 接口中的 invoke 方法,在调用 invoke 方法时,动态代理对象会将 被代理对象的方法动态代理对象映射的方法的参数 传递给 InvocationHandlerinvoke 方法, invoke 方法的实现是由程序员编写的,这样程序员就可以在 被代理对象的方法 执行 前后 进行增强。

下面我们来实现一下JDK原生动态代理

1、定义抽象主题角色接口 UserService

public interface UserService {
    public void select();
    public void update();
}

2、定义真实主题角色类 UserServiceImpl

public class UserServiceImpl implements UserService{
    @Override
    public void select() {
        System.out.println("执行UserService.select()方法");
    }

    @Override
    public void update() {
        System.out.println("执行UserService.update()方法");
    }
}

3、定义增加主题角色类 LogHandler

public class LogHandler implements InvocationHandler {
    // 用于存储真实的被代理对象
    Object target;

    public LogHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 真实方法执行前的增强
        before(method.getName());
        // 真实方法执行
        Object result = method.invoke(target, args);
        after(method.getName());
        // 真实方法执行后的增强
        return result;
    }
    
    private void before(String methodName) {
        System.out.printf("[%s] 准备开始执行%s方法%n", new Date(), methodName);
    }

    private void after(String methodName) {
        System.out.printf("[%s] %s方法方法执行结束%n", new Date(), methodName);
    }
}

4、编写客户端(其中有生产JDK原生动态代理的核心代码)

public class Client {
    public static void main(String[] args) {
        // 实例化被代理对象
        UserService targetInstance = new UserServiceImpl();

        // 获取被代理对象的类加载器,用作生成代理对象的必要参数
        ClassLoader classLoader = targetInstance.getClass().getClassLoader();
        
        // 获取被代理对象的实现接口,用作生成代理对象的必要参数
        // 方法映射就是基于这个参数实现的
        Class<?>[] interfaces = targetInstance.getClass().getInterfaces();

        // 获取被代理对象的增强主题类,用作生成代理对象的必要参数
        LogHandler logHandler = new LogHandler(targetInstance);

        // 生成代理对象的核心代码!!!!
        UserService proxyInstance = (UserService)Proxy.newProxyInstance(classLoader, interfaces, logHandler);

        // 使用代理对象执行方法
        proxyInstance.select();
        System.out.println();
        proxyInstance.update();
    }
}

执行结果:

[Tue Feb 20 17:58:14 CST 2024] 准备开始执行select方法
执行UserService.select()方法
[Tue Feb 20 17:58:14 CST 2024] select方法方法执行结束

[Tue Feb 20 17:58:14 CST 2024] 准备开始执行update方法
执行UserService.update()方法
[Tue Feb 20 17:58:14 CST 2024] update方法方法执行结束

我相信你一定和我一样好奇,生产的动态代理对象到底是什么样子的。 下面我将提供一个工具类,将代理对象生成出来,让大家一睹它的芳容。

代理工具类 ProxyUtils

public class ProxyUtils {
    /**
     * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
     * @param clazz     需要生成动态代理类的类
     * @param proxyName 为动态生成的代理类的名称
     */
    public static void generateClassFile(Class clazz, String proxyName) {
        // 根据类信息和提供的代理类名称,生成字节码
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.printf("代理对象%s生成位置:%s.class%n",proxyName, paths + proxyName);
        FileOutputStream out = null;
        try {
            //保留到硬盘中
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

生成代理对象

public class Client {
    public static void main(String[] args) {
        // 实例化被代理对象
        UserService targetInstance = new UserServiceImpl();

        // 生成代理类(我只是想看一下代理类到底长什么样而已)
        ProxyUtils.generateClassFile(
            targetInstance.getClass(),
            "UserServiceImplProxy" + System.currentTimeMillis()
         );
    }
}

执行结果

代理对象UserServiceImplProxy1708423094323生成位置:/E:/project/z_other/demo/target/classes/com/example/proxy/jdkProxy/UserServiceImplProxy1708423094323.class

JDK原生的动态代理执行过程如下图所示:

下面就是生成的代理对象,注解有说明哦。
我们可以发现这个对象继承了 Proxy 类和实现了 抽象主题角色类(UserService) 。继承了 Proxy 类就意味着该类可以访问 proxy 中的成员属性,例如 InvocationHandler 接口。实现了 UserService 就以为这其可以完成对 UserServiceImpl 类的接口映射。

public final class UserServiceImplProxy1708423094323 extends Proxy implements UserService {
    // 这 5 个变量就是用来存储被代理对象的方法的
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public UserServiceImplProxy1708423094323(InvocationHandler var1) throws  {
        super(var1);
    }
    
    // 从被代理对象映射出来的方法
    public final void select() throws  {
        try {
            // 核心执行代码(调用)
            // 调用 InvocationHandler 的 invoke 方法
            // 将被代理对象的方法和参数传递给 invoke 方法
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

   // 从被代理对象映射出来的方法
    public final void update() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    // 从被代理对象映射出来的方法
    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    // 从被代理对象映射出来的方法
    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    
    // 从被代理对象映射出来的方法
    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        // 这就是代理对象获取被代理对象真实方法的过程
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.example.proxy.jdkProxy.UserService").getMethod("select");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.example.proxy.jdkProxy.UserService").getMethod("update");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

4.2 CGLIB动态代理

在上面的 4.1 JDK原生动态代理 模块中,我们知道动态代理的实现是基于接口实现的。本章我们将介绍 CGLIB动态代理 ,CGLIB 实现动态代理的方式与 JDK 代理略有区别,CGLIB 是基于继承实现的动态代理,CGLIB 会生成一个动态代理子类,这个子类需要重写被代理对象的所有非 finanl 方法,在子类中采用方法拦截的技术拦截所有的父类方法调用,顺势织入横切逻辑。

在介绍基于 CGLIB 实现动态代理之前,我想先说一下实现动态代理需要实现的几个模块:

首先,我们需要一个被代理对象(普通类,不要求需要实现什么接口);
其次,我们需要编写一个增强类(我们需要代理的目的就是为了实现对原本方法的增强),所有的增强逻辑都基于它来实现,增强类需要实现 CGLIB 的 MethodInterceptor接口,并重写其中唯一一个方法 intercept,后面操作就可 JDK 的动态代理类似了,基于intercept 方法区执行被代理类的方法,并在方法执行前后完成对被代理方法的增强;
最后,我们需要基于 CGLIB 的对象生成目标对象的被代理对象。

下面是基于 CGLIB 实现动态代理的案例:

编写一个被代理对象 UserService

public class UserService {
    public void select() {
        System.out.println("执行UserService.select()方法");
    }
    public void update() {
        System.out.println("执行UserService.update()方法");
    }
}

编写对被代理对象的增强类 LogInterceptor

public class LogInterceptor implements MethodInterceptor {
    /**
     *
     * @param o           表示要增强的对象(被代理对象)
     * @param method      表示被拦截的方法
     * @param objects     表示参数列表
     * @param methodProxy 表示对连接方法的代理,invokeSuper方法表示对被代理对象方法的调用
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前置增强
        before(method.getName());
        // 调用被实际方法
        Object result = methodProxy.invokeSuper(o, objects);
        // 后置增强
        after(method.getName());
        // 返回实际对象的结果
        return result;
    }
    private void before(String methodName) {
        System.out.printf("[%s] 准备开始执行%s方法%n", new Date(), methodName);
    }
    private void after(String methodName) {
        System.out.printf("[%s] %s方法方法执行结束%n", new Date(), methodName);
    }
}

生成代理对象并执行代理方法:

public class Client {
    public static void main(String[] args) {
        // Enhancer 为 CGLIB 代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置被代理类(父类),以便 CGLIB 去生成该类的子类
        enhancer.setSuperclass(UserService.class);
        // 你可以认为是设置增强方法
        enhancer.setCallback(new LogInterceptor());

        // 生成代理对象
        UserService proxy = (UserService) enhancer.create();
        
        // 执行代理方法
        proxy.select();
        System.out.println();
        proxy.update();
    }
}

输出结果:

[Thu Feb 22 17:46:18 CST 2024] 准备开始执行select方法
执行UserService.select()方法
[Thu Feb 22 17:46:18 CST 2024] select方法方法执行结束

[Thu Feb 22 17:46:18 CST 2024] 准备开始执行update方法
执行UserService.update()方法
[Thu Feb 22 17:46:18 CST 2024] update方法方法执行结束

现在有这么一个需求,对于同一个代理对象中的不同方法,我想实现不同的增强逻辑,应该如何优雅的实现。

CGLIB 中支持对一个类设置多个不同的增强类,但是多个增强类无法增强同一个方法,这也就意味着我们需要为代理独享编写过滤器。有上面的介绍我们可得:想要实现对同一个类的不同方法实现差异性增强,我们需要多写一个代理过滤器,以便可以为不同的方法分配不同的增强方案。

我的大概实现方案如下:
1) 通过在方法上 引用 不同的自定义 注解 去,去 区分 不同方法的 增强 方案。
2) 编写多个增强类(实现类MethodInterceptor接口的类)去实现不同的增强逻辑。下面我会编写一个 日志增强参数校验增强
3)基于 CGLIB 语法生成多重增强代理类。

编写参数检查注解 CheckProxy 和日志注解 LogProxy

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckProxy {
}


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogProxy {
}

编写被代理对象 UserService,并其方法上打上不同的注解:

public class UserService {
    @CheckProxy
    public void select() {
        System.out.println("执行UserService.select()方法");
    }
    @LogProxy
    public void update() {
        System.out.println("执行UserService.update()方法");
    }
}

编写日志增强类 LogInterceptor 和参数检查增强类 CheckInterceptor

public class LogInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before(method.getName());
        Object result = methodProxy.invokeSuper(o, objects);
        after(method.getName());
        return result;
    }

    private void before(String methodName) {
        System.out.printf("[%s] 准备开始执行%s方法%n", new Date(), methodName);
    }
    private void after(String methodName) {
        System.out.printf("[%s] %s方法方法执行结束%n", new Date(), methodName);
    }
}
public class CheckInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        check(objects);
        return methodProxy.invokeSuper(o, objects);
    }
    
    private void check(Object[] objects) {
        boolean b = Arrays.stream(objects).allMatch(Objects::nonNull);
        System.out.printf("[%s] 正在进行参数检查%n", new Date());
        if (b) {
            System.out.printf("[%s] 参数检查通过%n", new Date());
        } else {
            throw new RuntimeException("存在非法参数");
        }
    }
}

编写代理增强过滤器 InterceptorFilter

这个地方我要介绍一下大家的疑惑点,我们可以看到 accept 方法的返回值是 int 且我分别返回了 0,1, 2 。accept 返回的是 增强类数组的索引 ,之所以会有增强类数组的存在,是因为我们在生成代理对象之前,需要向 Enhancer 中注入 增强类数组。代理方法在被执行前后被哪一个增强类所增强,是由 我们编写并实现了 CallbackFilter 接口的 InterceptorFilter 类所分配的,即 accept方法 的返回值

public class InterceptorFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if (method.isAnnotationPresent(LogProxy.class)) {
            return 0;
        } else if (method.isAnnotationPresent(CheckProxy.class)) {
            return 1;
        }
        return 2;
    }
}

生成代理对象:

public class Client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        // 设置增强类数组
        enhancer.setCallbacks(new Callback[]{new LogInterceptor(), new CheckInterceptor(), NoOp.INSTANCE});
        // 设置增强类过滤器
        enhancer.setCallbackFilter(new InterceptorFilter());

        UserService proxy = (UserService) enhancer.create();
        proxy.select();
        System.out.println();
        proxy.update();
    }
}

执行结果:

[Thu Feb 22 18:42:21 CST 2024] 正在进行参数检查
[Thu Feb 22 18:42:21 CST 2024] 参数检查通过
执行UserService.select()方法

[Thu Feb 22 18:42:22 CST 2024] 准备开始执行update方法
执行UserService.update()方法
[Thu Feb 22 18:42:22 CST 2024] update方法方法执行结束

这样我们就实现了对被代理对象的不同方法的差异性增强。

5. 总结

太累了,总结就择机再说吧。上面介绍的很详细了。


文章来源:https://blog.csdn.net/m0_45067620/article/details/136176227
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云