花瓣小跑 发表于 2024-6-14 22:37:01

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

https://img-blog.csdnimg.cn/direct/1dcd0053301749cda97b21a71fc99125.gif#pic_center


一、JDK动态代理简介

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

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

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

[*]日志记录:通过代理可以在方法执行前跋文录日志,实现日志记录的功能。
[*]性能监控:可以在方法执行前跋文录方法的执行时间,从而举行性能监控。
[*]事务管理:在方法执行前后开启和提交事务,实现事务管理的功能。
[*]权限控制:在方法执行前举行权限验证,实现权限控制的功能。
[*]远程调用:可以通过代理在调用远程对象的方法时添加网络通讯的逻辑,实现远程调用的功能。
1.2 Spring 怎样利用动态代理实现 AOP

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

[*]JDK动态代理(本篇):

[*]当被代理的目的对象实现了接口时,Spring会使用JDK动态代理。
[*]Spring AOP利用java.lang.reflect.Proxy类来创建代理对象,该类要求被代理的类必须实现至少一个接口。
[*]Spring在运行时动态生成了一个实现了雷同接口的代理对象,代理对象中的方法会委托给InvocationHandler接口的实现类来执行增强逻辑。
[*]JDK动态代理的上风在于它不需要引入额外的库,但缺点是被代理的类必须实现接口。

[*]CGLIB动态代理(下篇):

[*]当被代理的目的对象没有实现接口时,Spring会使用CGLIB动态代理。
[*]CGLIB是一个强盛的,高性能的代码生成库,它通过在运行时生成字节码的方式来动态创建代理类。
[*]Spring AOP利用CGLIB来生成被代理对象的子类,并在子类中重写需要增强的方法,将增强逻辑织入到重写的方法中。
[*]CGLIB动态代理的上风在于它可以代理没有实现接口的类,但缺点是需要引入CGLIB库,而且生成的代理类会比较庞大。

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

   本文主要结合动态代理的维度以及织入切面逻辑来分析源码,别的相关源码,读者感爱好可自行去分析。
织入切面逻辑的过程:

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

2.1.1 JdkDynamicAopProxy 类结构

JdkDynamicAopProxy 类的实现做一些预备工作,包括声明变量、初始化变量、定义静态成员等。
https://img-blog.csdnimg.cn/direct/5962942c043b449d95a6e82c1d6e637c.png#pic_center
2.1.2 getProxy 方法的实现

在 getProxy 方法中,会创建 Proxy.newProxyInstance,并传入 JdkDynamicAopProxy 的实例作为 InvocationHandler 。
https://img-blog.csdnimg.cn/direct/f7a7d65e4f4243c0847432b6164b66eb.png#pic_center
2.1.3 determineClassLoader 方法的实现

用于确定最终使用的类加载器,确保动态代理类可以或许正确加载所需的类。
https://img-blog.csdnimg.cn/direct/f7d5c94f1d0848549b4ffa6c73749fc8.png#pic_center
2.1.4 newProxyInstance 方法的实现

主要用于创建代理实例,其中包含了一些安全性检查和异常处置惩罚。
https://img-blog.csdnimg.cn/direct/76cfd0d0d367491bbfca11fec129bea7.png#pic_center
2.2 明白 InvocationHandler 接口

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

InvocationHandler接口定义了一个用于处置惩罚代理对象方法调用的统一入口,当代理对象的方法被调用时,会触发invoke方法的执行,通过实现invoke方法来定义代理对象方法调用时的活动,例如添加日志、实现权限控制等。
https://img-blog.csdnimg.cn/direct/e73237622acc4499a0b710a8c509c7d1.png#pic_center
2.2.2 invoke 方法的作用

   在 invoke 方法中,会根据方法名和参数,调用对应的拦截器。
invoke()的实现类较多,本文解读AopProxyChain实现类下的invoke方法。
主要用于代理对象的调用处置惩罚程序的实现,用于处置惩罚代理对象的方法调用。
https://img-blog.csdnimg.cn/direct/f3df079623d141ecb7f929cfaa12a1b5.png#pic_center
承接invoke方法实现,主要是根据拦截器链的情况来决定是直接调用目的方法还是通过拦截器链来调用,并在方法调用结束后举行相应的处置惩罚。
https://img-blog.csdnimg.cn/direct/f63ccf8ab69c4168915b57c5a5280d94.png#pic_center
2.3 分析拦截器链的处置惩罚

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

在 invoke 方法中,JdkDynamicAopProxy 会调用 AopProxyChain 对象的 proceed 方法。
https://img-blog.csdnimg.cn/direct/90c395be5a6f45ebbb308b7d20a531ec.png#pic_center
2.3.2 探讨拦截器链在 Spring AOP 中的执行序次和机制

AopProxyChain 封装了拦截器链,负责按照序次执行拦截器的逻辑。
https://img-blog.csdnimg.cn/direct/08e4f2d28eef4a56a4b8e6389e184872.png#pic_center
三、实践与应用

   通过对 Spring 源码的分析,学习怎样编写自定义的 AOP 拦截器

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

[*]编写自定义拦截器:

[*]创建一个类,实现 Spring 的 MethodInterceptor 接口,该接口定义了拦截器的核心方法 invoke。
[*]在 invoke 方法中编写自定义的拦截逻辑,比如在目的方法执行前后执行一些操作,大概更换目的方法的执行等。

[*]配置拦截器:

[*]使用 Spring 的配置方式(XML、Java Config、注解)将自定义的拦截器配置到 Spring 容器中。
[*]将拦截器与目的 bean 关联起来,可以通过切点表达式或其他方式指定在哪些方法上应用拦截器。

[*]测试:

[*]编写测试用例,验证自定义拦截器是否按照预期工作。
[*]确保拦截器可以或许正确地拦截目的方法,而且执行自定义的拦截逻辑。

[*]调试和优化:

[*]如果遇到问题,可以通过调试来查找缘故原由。
[*]根据实际需求,对拦截器举行优化和调解,确保其性能和功能都符合预期。

使用注解形式配置的Demo:

[*]创建一个自定义的拦截器 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;
    }
}

[*]创建一个注解 CustomAnnotation,用来标记需要被拦截的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
}

[*]修改 UserService 和 UserServiceImpl,在需要拦截的方法上添加 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);
    }
}

[*]使用 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);
    }
}

[*]编写一个测试类来验证拦截器是否按预期工作。
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
出息万里,全要各人自去积极

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 深度分析 Spring 源码:揭秘JDK动态代理的奥秘