首页 前端知识 深度解析 Spring 源码:揭秘JDK动态代理的奥秘

深度解析 Spring 源码:揭秘JDK动态代理的奥秘

2024-06-09 10:06:02 前端知识 前端哥 223 360 我要收藏

在这里插入图片描述

文章目录

    • 一、JDK动态代理简介
      • 1.1 JDK 动态代理的基本原理和使用场景
      • 1.2 Spring 如何利用动态代理实现 AOP
    • 二、探究 Spring 中的动态代理实现
      • 2.1 深入 JdkDynamicAopProxy 类
        • 2.1.1 JdkDynamicAopProxy 类结构
        • 2.1.2 getProxy 方法的实现
        • 2.1.3 determineClassLoader 方法的实现
        • 2.1.4 newProxyInstance 方法的实现
      • 2.2 理解 InvocationHandler 接口
        • 2.2.1 InvocationHandler 在 Spring 中的角色和使用方式
        • 2.2.2 invoke 方法的作用
      • 2.3 解析拦截器链的处理
        • 2.3.1 深入研究 AopProxyChain 对象的构建和作用
        • 2.3.2 探讨拦截器链在 Spring AOP 中的执行顺序和机制
    • 三、实践与应用
      • 通过对 Spring 源码的解析,学习如何编写自定义的 AOP 拦截器

一、JDK动态代理简介

1.1 JDK 动态代理的基本原理和使用场景

JDK动态代理是Java语言提供的一种实现动态代理的方式,其基本原理是利用反射机制在运行时动态生成代理类和代理对象。

基本原理

  1. 接口定义:定义一个接口(或者是一组接口),用于描述需要被代理的行为。
  2. InvocationHandler接口:编写一个实现了InvocationHandler接口的类,该类负责实际的代理逻辑。InvocationHandler接口只有一个方法invoke(Object proxy, Method method, Object[] args),当代理对象的方法被调用时,invoke方法会被调用,并在其中执行代理逻辑。
  3. Proxy类:使用Proxy类的newProxyInstance方法动态地创建代理对象。newProxyInstance方法接受三个参数:ClassLoader、一个接口数组和一个InvocationHandler对象。在运行时,Proxy类会动态生成一个实现了指定接口的代理类,并通过传入的InvocationHandler对象来调用实际的代理逻辑。
  4. 代理对象调用:当调用代理对象的方法时,实际上是调用了InvocationHandler接口的invoke方法,该方法会根据被调用的方法和传入的参数执行相应的代理逻辑。

使用场景

  1. 日志记录:通过代理可以在方法执行前后记录日志,实现日志记录的功能。
  2. 性能监控:可以在方法执行前后记录方法的执行时间,从而进行性能监控。
  3. 事务管理:在方法执行前后开启和提交事务,实现事务管理的功能。
  4. 权限控制:在方法执行前进行权限验证,实现权限控制的功能。
  5. 远程调用:可以通过代理在调用远程对象的方法时添加网络通信的逻辑,实现远程调用的功能。

1.2 Spring 如何利用动态代理实现 AOP

Spring AOP的实现基于代理模式装饰器模式,在目标方法执行前后或异常抛出时,通过代理对象来执行额外的逻辑,如日志记录、事务管理、权限控制等。通过配置切面通知,可以将这些额外逻辑统一地应用到多个目标类的方法中,从而实现横切关注点的分离和复用。

在Spring AOP中,主要利用了JDK动态代理和CGLIB动态代理两种方式。

  1. JDK动态代理(本篇)
    • 当被代理的目标对象实现了接口时,Spring会使用JDK动态代理。
    • Spring AOP利用java.lang.reflect.Proxy类来创建代理对象,该类要求被代理的类必须实现至少一个接口。
    • Spring在运行时动态生成了一个实现了相同接口的代理对象,代理对象中的方法会委托给InvocationHandler接口的实现类来执行增强逻辑。
    • JDK动态代理的优势在于它不需要引入额外的库,但缺点是被代理的类必须实现接口。
  2. CGLIB动态代理(下篇)
    • 当被代理的目标对象没有实现接口时,Spring会使用CGLIB动态代理。
    • CGLIB是一个强大的,高性能的代码生成库,它通过在运行时生成字节码的方式来动态创建代理类。
    • Spring AOP利用CGLIB来生成被代理对象的子类,并在子类中重写需要增强的方法,将增强逻辑织入到重写的方法中。
    • CGLIB动态代理的优势在于它可以代理没有实现接口的类,但缺点是需要引入CGLIB库,并且生成的代理类会比较庞大。

二、探究 Spring 中的动态代理实现

本文主要结合动态代理的维度以及织入切面逻辑来分析源码,其它相关源码,读者感兴趣可自行去分析。

织入切面逻辑的过程

  1. 当 Spring 容器启动时,会解析配置中的切面和通知,并生成代理对象的定义。
  2. 当目标 bean 被注入到其他 bean 中时,Spring 会检查该 bean 是否需要进行代理。
  3. 如果需要代理,则根据配置选择使用 JDK 动态代理还是 CGLIB 动态代理来创建代理对象。
  4. 在代理对象中,对目标方法的调用会被重定向到拦截器链中,拦截器链中包含了需要织入的切面逻辑。
  5. 在方法执行前后,拦截器链会按照配置的顺序执行切面逻辑。

2.1 深入 JdkDynamicAopProxy 类

2.1.1 JdkDynamicAopProxy 类结构

JdkDynamicAopProxy 类的实现做一些准备工作,包括声明变量、初始化变量、定义静态成员等。

在这里插入图片描述

2.1.2 getProxy 方法的实现

getProxy 方法中,会创建 Proxy.newProxyInstance,并传入 JdkDynamicAopProxy 的实例作为 InvocationHandler

在这里插入图片描述

2.1.3 determineClassLoader 方法的实现

用于确定最终使用的类加载器,确保动态代理类能够正确加载所需的类。

在这里插入图片描述

2.1.4 newProxyInstance 方法的实现

主要用于创建代理实例,其中包含了一些安全性检查和异常处理。

在这里插入图片描述

2.2 理解 InvocationHandler 接口

2.2.1 InvocationHandler 在 Spring 中的角色和使用方式

InvocationHandler接口定义了一个用于处理代理对象方法调用的统一入口,当代理对象的方法被调用时,会触发invoke方法的执行,通过实现invoke方法来定义代理对象方法调用时的行为,例如添加日志、实现权限控制等。

在这里插入图片描述

2.2.2 invoke 方法的作用

invoke 方法中,会根据方法名和参数,调用对应的拦截器。

invoke()的实现类较多,本文解读AopProxyChain实现类下的invoke方法。

主要用于代理对象的调用处理程序的实现,用于处理代理对象的方法调用。
在这里插入图片描述

承接invoke方法实现,主要是根据拦截器链的情况来决定是直接调用目标方法还是通过拦截器链来调用,并在方法调用结束后进行相应的处理。

在这里插入图片描述

2.3 解析拦截器链的处理

2.3.1 深入研究 AopProxyChain 对象的构建和作用

invoke 方法中,JdkDynamicAopProxy 会调用 AopProxyChain 对象的 proceed 方法。

在这里插入图片描述

2.3.2 探讨拦截器链在 Spring AOP 中的执行顺序和机制

AopProxyChain 封装了拦截器链,负责按照顺序执行拦截器的逻辑。

在这里插入图片描述

三、实践与应用

通过对 Spring 源码的解析,学习如何编写自定义的 AOP 拦截器

编写自定义的 AOP 拦截器步骤

  1. 编写自定义拦截器
    • 创建一个类,实现 Spring 的 MethodInterceptor 接口,该接口定义了拦截器的核心方法 invoke。
    • 在 invoke 方法中编写自定义的拦截逻辑,比如在目标方法执行前后执行一些操作,或者替换目标方法的执行等。
  2. 配置拦截器
    • 使用 Spring 的配置方式(XML、Java Config、注解)将自定义的拦截器配置到 Spring 容器中。
    • 将拦截器与目标 bean 关联起来,可以通过切点表达式或其他方式指定在哪些方法上应用拦截器。
  3. 测试
    • 编写测试用例,验证自定义拦截器是否按照预期工作。
    • 确保拦截器能够正确地拦截目标方法,并且执行自定义的拦截逻辑。
  4. 调试和优化
    • 如果遇到问题,可以通过调试来查找原因。
    • 根据实际需求,对拦截器进行优化和调整,确保其性能和功能都符合预期。

使用注解形式配置的Demo

  1. 创建一个自定义的拦截器 CustomInterceptor,实现 MethodInterceptor 接口。
public class CustomInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 在目标方法执行前输出日志
        System.out.println("Before invoking method: " + invocation.getMethod().getName());

        // 执行目标方法
        Object result = invocation.proceed(); 

        // 在目标方法执行后输出日志
        System.out.println("After invoking method: " + invocation.getMethod().getName());

        return result;
    }
}
  1. 创建一个注解 CustomAnnotation,用来标记需要被拦截的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
}
  1. 修改 UserServiceUserServiceImpl,在需要拦截的方法上添加 CustomAnnotation 注解。
public interface UserService {
    @CustomAnnotation
    void addUser(String username);
}

public class UserServiceImpl implements UserService {
    @Override
    @CustomAnnotation
    public void addUser(String username) {
        System.out.println("User added: " + username);
    }
}
  1. 使用 Spring 的 Java Config 来配置拦截器和切面。
@Configuration
public class AppConfig {

    /**
     * 返回一个 UserService 实例
     */
    @Bean
    public UserService userService() {
        return new UserServiceImpl();
    }

    /**
     * 返回一个 CustomInterceptor 的实例,即自定义的拦截器
     */
    @Bean
    public CustomInterceptor customInterceptor() {
        return new CustomInterceptor();
    }

    /**
     * 返回一个 DefaultAdvisorAutoProxyCreator 实例,负责自动代理被 @AspectJ 注解标记的 Bean
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }

    /**
     * 返回一个 DefaultPointcutAdvisor 实例,将拦截器和切点绑定在一起
     */
    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor() {
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        // 将自定义的拦截器设置为 Advisor 的 advice,即在目标方法执行前后所执行的逻辑
        advisor.setAdvice(customInterceptor());
       // 设置切点,即确定在哪些方法上应用拦截器的条件
        advisor.setPointcut(annotationMatchingPointcut());
        return advisor;
    }

    /**
     * 返回一个 AnnotationMatchingPointcut 实例,切点用于匹配带有 CustomAnnotation 注解的方法
     */
    @Bean
    public AnnotationMatchingPointcut annotationMatchingPointcut() {
        return AnnotationMatchingPointcut.forMethodAnnotation(CustomAnnotation.class);
    }
}
  1. 编写一个测试类来验证拦截器是否按预期工作。
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        // 在执行 addUser 方法之前,拦截器执行了自定义的前置逻辑,并在方法执行完毕后执行了自定义的后置逻辑
        userService.addUser("Alice");
    }
}

// 输出结果
Before invoking method: addUser
User added: Alice
After invoking method: addUser

前程万里,全要各人自去努力

转载请注明出处或者链接地址:https://www.qianduange.cn//article/11683.html
评论
发布的文章

【web前端】CSS浮动

2024-06-18 00:06:02

HTML CSS做的商城页面

2024-06-18 00:06:23

去空行小工具Html Javascript

2024-06-18 00:06:21

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!