标签:容器 反转 bean context Spring 引入 IoC class BeanDefinition
IoC之控制反转引入
前言
上文提到:
要实现"控制反转"功能的话,我们需要解决 bean class 的扫描问题,只有将这些 bean class 扫描出来了,我们才知道要创建哪些 bean 的实例。
所以,Spring 首先要解决的问题是 BeanDefinition 的扫描和存储(即: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.ContextLoaderListener
和org.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 是如何被扫描出来的,又是如何注册的。所以,跟这个无关的代码我们都可以先忽略。
我们可以大胆的猜想一下,可能的步骤如下:
- 扫描指定包路径下所有的 class,将符合条件的 class 组装成一个 BeanDefinition 对象
- 将 BeanDefinition 注册到 Map 容器中
BeanDefinition 是 Spring 抽象出的模型,用来存储 bean 的定义
总结
本文对 IoC 的"控制反转"进行了分析,"控制反转"需要解决的问题是 BeanDefinition 的扫描和注册问题。
后续的文章中将从源码的角度来分析 BeanDefinition 的扫描和注册过程。
如果本文对你有所帮助,欢迎点赞收藏!
标签:容器,反转,bean,context,Spring,引入,IoC,class,BeanDefinition 来源: https://blog.csdn.net/wang489687009/article/details/119908869
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。