spring容器_Spring 容器的启动过程探秘
一. 前言
Spring家族特别庞大,对于开发人员而言,要想全面征服Spring家族,得花费不少的力气。俗话说,打蛇打七寸,那么Spring家族的“七寸”是什么呢?我心目中的答案一直都是 Spring Framework!
本篇文章记录我自己在学习Spring Framework的过程中的一小部分源码解读和梳理,来谈一谈Spring 容器在启动过程中是如何扫描Bean的。
二. 学习方法论
我相信每个想变成优秀的开发人员都想弄懂Spring源码,我亦如此。于是通过很多途径来找Spring源码的学习资料、买书、看视频等等。到头来发现只有自己静下心来一步一步跟着源码调试,一行一行的深入理解,才能深入理解Spring的奥妙!这个过程很枯燥,但优秀的猎手最能耐得住寂寞和浮躁!
我们知道,Spring容器的启动方式有多种:XML文件、注解、Java Config。在实际的使用中并不是选择其中某一种,而是相互搭配。其底层的容器启动过程是一样的,只是入口变了而已。另外,学习Spring的最佳方式就是自己将源码工程构建出来,这样便于源码阅读、备注、修改。构建出来的工程长这样:
![](/assets/blank.gif)
三. 代码入口
话不多说直接开干!代码入口如下:
@Configuration@ComponentScan("com.leon.funddatahouse")public class Config {}public class MyApplication { public static void main(String[] args) { // 我们基于注解的方式 AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Config.class); // 如果基于XML文件配置,则也可以如下: // ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); }}
在构造方法中,总共做了3件事情。这三件事情包括了整个Spring容器启动的所有过程!啃碎他们,便成功了一半!
public AnnotationConfigApplicationContext(Class>... componentClasses) { // 1.调用默认构造方法,进行容器环境的准备 this(); // 2.基于配置类注册相关信息 register(componentClasses); // 3.刷新整个容器 refresh(); }
四. 解析之前
在解析之前,先将容器和BeanFactory的UML类图放出。原因在于它们担任的角色、具备的功能太多太强大了,同时这也增加了源码理解的难度。因此这里先放出UML类图作为手册查看,便于理解源码。
4.1 容器UML类图
![](/assets/blank.gif)
4.2 BeanFactoryUML类图
![](/assets/blank.gif)
五. 源码解析
5.1 构造方法解析
5.1.1 初始化容器中的BeanFactory
在构造方法中,显式的调用了this(),既无参构造方法:
public AnnotationConfigApplicationContext() { // 1.实例化容器中的reader. 后面会详细解析 this.reader = new AnnotatedBeanDefinitionReader(this); // 2.实例化容器中的scanner.后面会详细解析 this.scanner = new ClassPathBeanDefinitionScanner(this);}
乍看一眼,这个无参构造方法做了两件事情,其实不然。它实际上等同于:
public AnnotationConfigApplicationContext() { // 1.调用父类构造方法 super(); // 2.实例化容器中的reader. 后面会详细解析 this.reader = new AnnotatedBeanDefinitionReader(this); // 3.实例化容器中的scanner.后面会详细解析 this.scanner = new ClassPathBeanDefinitionScanner(this);}
这一点很关键, 如果没有意识到这里隐形调用了父类构造方法的话, 那么接下来的路没法走, 因为在父类构造器中做了一件大事情:
// 在父类的构造方法中, 创建了容器中的BeanFactory.至此,容器中有了第一个程序创建的属性:beanFactorypublic GenericApplicationContext() { // 初始化容器的beanFactory,类型为DefaultListableBeanFactory this.beanFactory = new DefaultListableBeanFactory();}
BeanFactory 和 FacotryBean的区别, 请点击这里
5.1.2 实例化容器中的Reader
reader最主要的目的是用于辅助注册BeanDefinition,其具体的使用后文在介绍,这里我们只需知道它包含了哪些东西。
// 入参registry就是容器本身。因为通过上面的UML类图可以发现,容器间接继承了BeanDefinitionRegistrypublic AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) { // getOrCreateEnvironment() 方法最主要是获取环境。实际类型其实默认的就是StandardEnvironment类。这里的环境包括两方面: // 1.systemEnvironment:操作系统环境。这样,Spring就可以获取到操作系统、CPU核心数等操作系统本身的数据。 // 2.systemProperties:JVM的环境变量。这样,Spring就可以获取到JVM的基础数据,比如我们在启动参数中手动设置的环境变量等。 this(registry, getOrCreateEnvironment(registry));}
这里通过this() 调用了reader内部另一个构造方法:
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); Assert.notNull(environment, "Environment must not be null"); // 设置registry,已经知道它的就是容器本身:AnnotationConfigApplicationContext this.registry = registry; // 创建条件处理器 this.conditionEvaluator = new ConditionEvaluator(registry, environment, null); // 非常关键!提前往容器中注册一些必要的后置处理器 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}
这个构造方法很重要, 因为它涉及到spring容器当中的两个重要成员:条件解析器和后置处理器!
5.1.2.1 实例化条件处理器
相信熟悉Spring的人一定都知道或用过@ConditionalOnBean / @ConditionalOnClass 等条件注解.而这些条件注解的解析就是ConditionEvaluator.
public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry, @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) { // 实际上是委托给内部类ConditionContextImpl this.context = new ConditionContextImpl(registry, environment, resourceLoader);}// ------------分割线------------------// 内部的ConditionContextImpl构造器public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry, @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) { // 再说一遍,registry的实际类型就是 AnnotationConfigApplicationCont this.registry = registry; // 获取beanFactory,我们也知道了beanFactory其实就是 ConfigurableListableBeanFactory this.beanFactory = deduceBeanFactory(registry); // 从容器中获取environment,前面介绍过,容器中的environment的封装类是 StandardEnvironment this.environment = (environment != null ? environment : deduceEnvironment(registry)); // 资源加载器. 通过UML类图可以发现,resourceLoader就是容器, 因为容器间接继承了ResourceLoader this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry)); // 类加载器. 实际上就是获取beanFactory的类加载器。理应如此,容器当中的类加载器肯定要一致才行 this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);}
后面在解析BeanDefinition时我们还会遇到ConditionEvaluator, 其具体源码解析会用专门的文章来解析,本篇文章我们只需要知道它的作用即可.
5.1.2.2 注册一部分后置处理器
ConditionEvaluator初始化完成之后,接下来就特别重要了,因为在这里将提前注入一些后置处理器:
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) { // 空壳方法,实际委托给重载的方法 registerAnnotationConfigProcessors(registry, null);}
重载的方法如下(高能预警):
public static Set registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, @Nullable Object source) { // 获取容器中的beanFactory,通过前面的解析,我们知道,这里一定会获取到。因此将进入if分支 DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); if (beanFactory != null) { // 此时beanFactory的属性dependencyComparator为null,因为初始化过程中,内部成员变量如果没有默认值,则默认为null, // 所以如果第一次进来, 这里的判断一定成立,对dependencyComparator进行设置。 // AnnotationAwareOrderComparator继承了OrderComparator, // 因此可以对实现了Ordered接口、打上@Order或者@Priority注解的类进行排序。 // 也就是说,在这里设置beanFactory中的orderComparator,以支持解析bean的排序功能。 if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } // beanFactory初始化时,默认为SimpleAutowireCandidateResolver,因此第一次进来时这里的判断也一定成立。 // ContextAnnotationAutowireCandidateResolver最主要的作用就是支持@Lazy注解的类的处理。 if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } // 初始化一个bdh容器,用于盛放接下来将解析出来的后置处理器的bd。 Set beanDefs = new LinkedHashSet<>(8); // 容器在第一次初始化时,内部一个bd都没有的。 // 也就是说,从这里开始,容器将第一次装载bd,而这里的这些bd都是spring自带的后置处理器。 // 获取并注册ConfigurationClassPostProcessor后置处理器 的bd if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } // 获取并注册AutowiredAnnotationBeanPostProcessor后置处理器 的bd if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } // 获取并注册CommonAnnotationBeanPostProcessor后置处理器 的bd if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } // 获取并注册PersistenceAnnotationBeanPostProcessor后置处理器 的bd if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); try { def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, AnnotationConfigUtils.class.getClassLoader())); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex); } def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)); } // 获取并注册EventListenerMethodProcessor后置处理器 的bd if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME)); } // 获取并注册DefaultEventListenerFactory后置处理器 的bd if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME)); } return beanDefs;}
这个方法首次出现了BeanDefinition这个类. Spring的BeanDefinition相当于Java的Class
通过该方法之后, beanFactory中就存在了以上6个bd:
![](/assets/blank.gif)
曾经有人跟我说, 掌握了Spring的后置处理器, 那么整个Spring就掌握了10%! 可见其重要性. 但是在这里先不展开后置处理器(太多了),本篇文章的主线是容器启动过程。
5.1.2.3 reader初始化过程小结
到这里reader部分的初始化终于完成了。总结一下,reader的初始化主要干了这些事情:1.创建并设置容器当中的Environment属性。即默认为StandardEnvironment类。2.创建并设置容器当中的条件解析器,即ConditionEvaluator,其内部实际委托给内部类ConditionContextImpl。3.注册6个后置处理器到容器当中。注意这里仅是生成了后置处理器的BeanDefinition。还并没有进行bean解析和后置处理的执行。
5.1.3 实例化容器中的Scanner
解析完reader之后,继续解析scanner。这里的scanner的实际类型是ClassPathBeanDefinitionScanner。它最主要的目的就是扫描类路径下所有的class文件能否解析为bd。其最终调用的构造方法如下:
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) { // 1.委托给内部的另一个构造方法 this(registry, true);}// ------------------------分割线-------------------------public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) { // 2.又委托给内部的另一个构造方法 >_
5.1.3.1 registerDefaultFilters()方法
从最终的构造方法我们知道, Scanner在扫描的过程中,会使用过滤策略,并且使用了默认的过滤策略.默认策略就是以下这个方法解析.
protected void registerDefaultFilters() { // 扫描@Component注解的类 this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { // 扫描所有@ManageBean的类 this.includeFilters.add(new AnnotationTypeFilter( ((Class extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); } catch (ClassNotFoundException ex) { } try { // 扫描所有@Named的类 this.includeFilters.add(new AnnotationTypeFilter( ((Class extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false)); logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); } catch (ClassNotFoundException ex) { }}
这里的一个知识点:@ManageBean和@Named的作用和@Component是一样的。只是我们通常习惯使用@Component。
为什么这里没有添加默认扫描@Service、@Repository、@Controller呢?原因很简单,这些注解都间接继承了@Component了。到这里,scanner解析完毕,它做的最主要的事情就是添加默认的过滤器策略以便在后续中可以扫描出@Component注解的类。
六 默认构造方法小结
现在我们再来看一下构造方法:
public AnnotationConfigApplicationContext(Class>... componentClasses) { // 1.调用默认构造方法,进行容器环境的准备 this(); register(componentClasses); refresh();}
从入口看, 就只有这三行代码, 但其中的第一行,调用默认构造方法就做了这么多准备工作,其中也牵扯出了一些Spring整个体系中最重要的几个组件,比如BeanFactory / BeanDefinition / BeanDefinitionReader / BeanDefinitionScanner / Environment / ConditionEveluator / PostProcessor等等.随便拿一个出来都够喝一壶! 这些点会各个击破, 但不是本篇文章的重点,本篇文章的重点是先梳理整个启动过程的第一步: 构造方法的执行过程.
本文来源:http://r6f.cn/b47K
spring容器_Spring 容器的启动过程探秘相关推荐
- spring容器_Spring容器文档阅读要点记录
Spring容器文档阅读要点记录 相关的库代码位于 org.springframework.beans 和 org.springframework.context包下面 容器的基本的接口 基本接口:B ...
- SpringBoot 应用程序启动过程探秘
本文共 946字,阅读大约需要 3分钟 ! 概述 说到接触 SpringBoot 伊始,给我第一映像最深的是有两个关键元素: 对照上面的典型代码,这个两个元素分别是: @SpringBootAppli ...
- Spring启动过程详解
Spring启动过程详解 前言 spring容器启动过程 AnnotationConfigApplicationContext 有参数构造方法 无参数构造 AnnotatedBeanDefinitio ...
- Spring 容器的启动过程
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 本文来源:http://r6f.cn/b47K 一. 前言 ...
- spring中容器启动过程中初始化资源使用方法
一.定义 在业务场景中,有时需要我们在容器启动过程中加载资源,完成数据的初始化或者配置,需要在调用前加载进spring容器过程中去,有以下一些方法来实现 1.定义静态常量,随着类的生命周期加载而提前加 ...
- 【Spring】SpringIOC容器启动过程源码分析 以及 循环依赖问题
1.Spring是什么 Spring是一款轻量级的开发框架 . 简而言之 Spring提高了开发效率 两个核心 IOC 和 AOP 1.1 IOC ( Inversion of Control ) 是 ...
- Spring容器初始化和bean创建过程
文章目录 Spring容器初始化过程(注解) 1.this() 初始化bean读取器和扫描器 2. register(annotatedClasses) 3 refresh()刷新上下文 前言:一直想 ...
- spring依赖注入_Spring源码阅读:Spring依赖注入容器
依赖注入 依赖注入是Spring框架最核心的能力,Spring框架提供的AOP,WebMVC等其它功能都是以依赖注入容器作为基础构建的,Spring依赖注入容器类似于一个用于组装对象的框架内核,任何应 ...
- boot spring 没有父子容器_Spring 系列(二):Spring MVC的父子容器
1.背景 在使用Spring MVC时候大部分同学都会定义两个配置文件,一个是Spring的配置文件spring.xml,另一个是Spring MVC的配置文件spring-mvc.xml. 在这里给 ...
最新文章
- 都2020年,开发制作微信小程序商城,需要准备什么资料?应该不会不知道吧!
- mysql 升序 字段值为NULL 排在后面
- Linux的shell scripts的shell脚本练习
- CodeIgniter开发实际案例-新闻网站【转】
- 【final】评价①
- 移植U-Boot.1.3.1到S3C2440和S3C2410
- 巧用这19条MySQL优化,效率至少提高3倍
- 【四】Java流程控制
- 【Python3网络爬虫开发实战】3.1.1-发送请求
- Linux添加rose不能交互式登录,常见问题及注意事项 - Linux下环境安装配置Rose HA全攻略(图)_数据库技术_Linux公社-Linux系统门户网站...
- [FFmpeg] Ubuntu 16.04 安装 FFmpeg
- winscp普通用户向linux根目录中上传文件无法建立远程文件
- 使用正则表达式实现网页爬虫的思路详解
- poj3579 Median
- Delphi 开发so库,Delphi 调用SO库
- 将图片转化为.py文件
- Hex Editor
- 重庆APP开发价格费用如何计算?
- MTCNN人脸及特征点检测--基于树莓派3B+及ncnn架构
- npm ERR,fatal: unable to access ‘https://github.com/nhn/raphael.git/‘: OpenSSL SSL_read: Connection
热门文章
- python列表解析,生成表达式(一分钟读懂)
- 对称二叉树c++_数据结构学习(C++)——平衡二叉树
- python做项目管理代码_代码发布项目(三)——python操作git、代码发布流程(服务器管理、项目管理)...
- mybatisplus page排序_MyBatisPlus分页的同时指定排序规则说明
- 适合本科生的AI比赛目录
- 最小环(【CCF】NOI Online能力测试 提高组第三题)
- Linux中使用tar打包解包查看的使用方法
- RAP框架练习(续)
- Java IO实战操作(三)
- L2TP连接尝试失败,因为安全层在初始化与远程计算机的协商时遇到一个处理错误...