• 手把手教你调试SpringBoot启动 IoC容器初始化源码,spring如何解决循环依赖


    • 授人以鱼不如授人以渔,首先声明这篇文章并没有过多的总结和结论,主要内容是教大家如何一步一步自己手动debug调试源码,然后总结spring如何解决的循环依赖,最后,操作很简单,有手就行。
    • 本次调试 是使用@Autowired注入,通过来调试源码看spring如何解决的循环依赖问题。
    • 首先创建一个简单的springBoot项目,引入spring-boot-test包即可。可以使用idea提供的spring官网推荐的快速创建。
      •  maven依赖

      • <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>
      • <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>
      • <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.1</version>
        </dependency>
      • <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.1</version>
        <scope>compile</scope>
        </dependency>
      •  编写调试源码的三个类

      • @Getter
        @Setter
        @Component
        public class A {
        @Autowired
        private B b;
        }
      • @Getter
        @Setter
        @Component
        public class B {

        @Autowired
        private A a;
        }
      • @SpringBootTest
        @RunWith(SpringRunner.class)
        public class IocCodeSourceTest {

        @Autowired
        private ApplicationContext applicationContext;

        @Test
        public void test_sourceCode_01() {
        Object bean = applicationContext.getBean("a");
        Object bean1 = applicationContext.getBean("b");
        //通过在此打断点设置可以发现,A和B都初始化完成了
        System.out.println(bean);
        System.out.println(bean1);
        }

        }
      • 注意:这里不能使用lombok提供的注解@Data,因为@Data中重写了equals和hascodes方法,
        System.out.println(bean)
        这一句代码这输出的时候,会去调用bean的hashcode,在计算A对象的hashcode的时候,又调用了属性B的hashcode方法,同理计算B属性的hashcode由去调用了A的,造成了死递归。最终造成线程的虚拟机栈(方法栈)一直入栈,栈溢出。如下图,hashcode9331和9332一直会递归调用。
      •   没时间的童鞋可以直接去看我的github项目地址:https://github.com/coffeebabeCGG/spring/tree/master/src/main/java/com/example/springstudy
    • 调试方法:

    首先需要知道,spring默认创建bean都是单例的,而单例的bean是在容器初始化完成后就创建完成。因此这里使用springboot启动调试,最终还是会进入到run方法,到refresh()方法,再去初始化容器。

    下面会教几个关键的debug调试点。掌握这几个关键的调试点后,源码怎么走的,可以看最终的调试结果流程图,自己结合源码去一步一步debug。

    1、进入getbean()

     2、紧接着进去AbstractBeanFactory实现方法

         3、最后进入AbstractBeanFactory的doGetBean()

         4、关键点:在debug上设置条件,因为容器初始化的时候,会有很多spring本身的bean需要先去加载,所以,要在这里设置我们需要去debug观察的bean,我们的bean没有配置beanName,默认使用的类名小写。

         5、接下来就可以一路F9放行,小伙伴们看自己的快捷键,每个人都不一样。当断点停下,就可以观察spring在创建 A B对象的调用栈了。

        关键点:调试源码的时候,深入之后很容易就迷路,有个小技巧。下面截图的左下角红色标注,小伙伴们一定记得学会看方法的调用栈。为了简单理解,可以先这么理解,在开始调用一个方法时候,Jvm会创建一个线程,例如下图就是main线程。

        然后 方法之间的调用是通过栈 这个数据结构来实现的。Jvm中称为java虚拟机栈,每一个方法都是一个栈帧。

        结合下图去理解:

          main线程中(即一个虚拟机栈):run方法是一个栈帧,然后run方法中调用了refreshContxt,那么refreshContext也是一个栈帧,被压入栈顶,以此类推。目前我们当前main线程的虚拟机栈的栈顶是doGetBean方法。

      说了这么多有什么用呢?加入我们在调试过程中,迷路了,我们可以看下调用栈,找到我们来的地方。是哪一个栈帧,可以利用idea提供的下图的回到上一个断点。再回到我们开始的地方,这样一步一步调试就不会迷路了。

    • 调试流程图:
      • 最后附上自己调试之后的总结流程图。下一篇博客,会结合这个流程图,解释一下,三级缓存的具体作用,以及为什么需要使用到了三级缓存?
  • 相关阅读:
    A. Vasya and Book
    B. Curiosity Has No Limits
    A. Link/Cut Tree
    C. Yuhao and a Parenthesis
    D2. Magic Powder
    B. Approximating a Constant Range
    51nod1185 威佐夫游戏 V2 (模拟乘法)
    博弈论(巴什博奕,威佐夫博弈,尼姆博弈,斐波那契博弈)
    sg函数模板
    D.Starry的神奇魔法(矩阵快速幂)
  • 原文地址:https://www.cnblogs.com/wa1l-E/p/14115042.html
Copyright © 2020-2023  润新知