【老王读Spring IoC-1】Spring IoC之控制反转引入

前言

上文提到
要实现"控制反转"功能的话,我们需要解决 bean class 的扫描问题,只有将这些 bean class 扫描出来了,我们才知道要创建哪些 bean 的实例。
所以,Spring 首先要解决的问题是:

  1. BeanDefinition 的扫描和存储(即:BeanDefinition 的注册)。
  2. 根据 BeanDefinition 来创建 bean 的实例

本文主要分析一下第一个问题: BeanDefinition 是如何被扫描和注册的?

版本约定

Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)

正文

在研究 IoC 源码之前,我们需要大致了解一下它是怎么使用的。

准备工作

首先,我们准备一个干净的工程。

为什么要准备一个干净的工程来研究源码?
答:请阅读之前我讲过的 阅读源码的步骤

工程中的示例代码可以选择以下两种方式:

方式一:编程式的方式启动 Spring 容器

public class ContainerTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.kvn.beans");
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Arrays.stream(beanDefinitionNames).forEach(System.out::println);
        UserService userService = applicationContext.getBean(UserService.class);
        System.out.println(userService);
    }
}

方式二:通过 SpringBoot 的方式启动容器
(这种方式更接近真实场景,推荐使用这种方式来研究,因为它运行过的代码基本就是我们日常项目中要运行的代码,是更加贴近于实际的一种简化)

@RestController
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(com.kvn.Application.class);
        app.setBannerMode(Banner.Mode.OFF);
        app.run(args);
    }

    @GetMapping("/status")
    public String status() {
        return "OK!";
    }

}

容器启动的入口是在 org.springframework.context.support.AbstractApplicationContext#refresh() 。可以将断点打在这里。

我是怎么知道 Spring IoC 容器的启动入口的?
答:在没有 SpringBoot 的时代,我们的项目都在通过外置 tomcat 来启动的,许多入口类都在放在 web.xml 中进行配置的。
其中,就包括 Spring 容器启动相关的配置。我们一般会配置 org.springframework.web.context.ContextLoaderListenerorg.springframework.web.servlet.DispatcherServlet
它们俩最终都会各自去启动一个容器,启动容器会去调用 org.springframework.context.support.AbstractApplicationContext#refresh(),这个方法也就是容器启动的入口方法。

补充1:
ContextLoaderListener: 启动 Spring 的 root WebApplicationContext
DispatcherServlet: 提供最核心的请求分发能力,同时也会启动一个 SpringMVC 的容器,容器的 parent 指向 root WebApplicationContext(ContextLoaderListener 启动的容器)。

补充2:web.xml 的配置类似下面这种

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:application.xml</param-value>
</context-param>

<!-- spring context listener -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- spring mvc dispatcher servlet -->
<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

我们现在都生活在 SpringBoot 带来的 easy life 下,上面的知识可能都很少了解。不过没关系,阅读过本文后,你将有所了解 _
这里有个大体的印象即可。因为在 JSP 风靡的时代,我们可是对 servlet、JSP 9大内置对象烂熟于心的,不过现在也只是有个淡淡的印象。
所以不用太纠结怎么样找到这个入口,记住关键的入口类是 AbstractApplicationContext#refresh() 即可。
如果你还想深入研究下 SpringMVC xml 时代的配置,可以自行研究,这里不做展开。

正式开始

有了上面的工程准备后,我们就可以运行下测试代码。
没有问题之后,就可以打个断点在 AbstractApplicationContext#refresh() 上。

简化之后的代码大概分了 12 步:

public void refresh() throws BeansException, IllegalStateException {

    // 1. Prepare this context for refreshing.
    prepareRefresh();

    // 2. Tell the subclass to refresh the internal bean factory.
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

    // 3. Prepare the bean factory for use in this context.
    prepareBeanFactory(beanFactory);

    // 4. Allows post-processing of the bean factory in context subclasses.
    postProcessBeanFactory(beanFactory);

    // 5. Invoke factory processors registered as beans in the context.
    invokeBeanFactoryPostProcessors(beanFactory);

    // 6. Register bean processors that intercept bean creation.
    registerBeanPostProcessors(beanFactory);

    // 7. Initialize message source for this context.
    initMessageSource();

    // 8. Initialize event multicaster for this context.
    initApplicationEventMulticaster();

    // 9. Initialize other special beans in specific context subclasses.
    onRefresh();

    // 10. Check for listener beans and register them.
    registerListeners();

    // 11. Instantiate all remaining (non-lazy-init) singletons.
    finishBeanFactoryInitialization(beanFactory);

    // 12. Last step: publish corresponding event.
    finishRefresh();
}

代码还是非常清晰的,只是我们要搞清楚每一步的作用是什么。
源码中的英文注释是非常重要的,它会大概描述这个方法是干什么用的,对增强我们的理解非常有帮助。

这次我们要抓住的重点是:BeanDefinition 是如何被扫描出来的,又是如何注册的。所以,跟这个无关的代码我们都可以先忽略。

我们可以大胆的猜想一下,可能的步骤如下:

  1. 扫描指定包路径下所有的 class,将符合条件的 class 组装成一个 BeanDefinition 对象
  2. 将 BeanDefinition 注册到 Map 容器中

BeanDefinition 是 Spring 抽象出的模型,用来存储 bean 的定义

总结

本文对 IoC 的"控制反转"进行了分析,"控制反转"需要解决的问题是:

  1. BeanDefinition 的扫描和注册
  2. 根据 BeanDefinition 来创建 bean 的实例

后续的文章中将从源码的角度来分析 BeanDefinition 的扫描和注册过程,以及 bean 的创建过程。


SpringIoC源码视频讲解:

课程地址
SpringIoC源码解读由浅入深https://edu.51cto.com/sd/68e86

如果本文对你有所帮助,欢迎点赞收藏!

源码测试工程下载:
老王读Spring IoC源码分析&测试代码下载
老王读Spring AOP源码分析&测试代码下载

公众号后台回复:下载IoC 或者 下载AOP 可以免费下载源码测试工程…

阅读更多文章,请关注公众号: 老王学源码
gzh


系列博文:
【老王读Spring IoC-0】Spring IoC 引入
【老王读Spring IoC-1】IoC 之控制反转引入
【老王读Spring IoC-2】IoC 之 BeanDefinition 扫描注册
【老王读Spring IoC-3】Spring bean 的创建过程
【老王读Spring IoC-4】IoC 之依赖注入原理
【老王读Spring IoC-5】Spring IoC 小结——控制反转、依赖注入

相关阅读:
【Spring源码三千问】@Resource 与 @Autowired 的区别
【Spring源码三千问】bean name 的生成规则
【Spring源码三千问】BeanDefinition详细分析
【Spring源码三千问】Spring 是怎样解决循环依赖问题的?
【Spring源码三千问】哪些循环依赖问题Spring解决不了?
【Spring源码三千问】@Lazy为什么可以解决特殊的循环依赖问题?
【Spring源码三千问】BeanDefinition注册、Bean注册、Dependency注册有什么区别?
【Spring源码三千问】Bean的Scope有哪些?scope=request是什么原理?
【Spring源码三千问】为什么要用三级缓存来解决循环依赖问题?二级缓存行不行?一级缓存行不行?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老王学源码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值