1. 概述
本文,我们来补充 《精尽 Spring Boot 源码分析 —— SpringApplication》 文章,并未详细解析的 BeanDefinitionLoader 。在 SpringApplication 中,我们可以看到 #load(ApplicationContext context, Object[] sources)
方法中,是如下一段代码:
// SpringApplication.java |
下面,我们来一起揭开它的面纱~
2. BeanDefinitionLoader
org.springframework.boot.BeanDefinitionLoader
,BeanDefinition 加载器(Loader),负责 Spring Boot 中,读取 BeanDefinition 。其类上的注释如下:
// BeanDefinitionLoader.java |
2.1 构造方法
// BeanDefinitionLoader.java |
-
<1>
处,设置sources
属性。它来自方法参数Object... sources
,来自SpringApplication#getAllSources()
方法,代码如下:// BeanDefinitionLoader.java
/**
* 主要的 Java Config 类的数组
*/
private Set<Class<?>> primarySources;
private Set<String> sources = new LinkedHashSet<>();
/**
* Return an immutable set of all the sources that will be added to an
* ApplicationContext when {@link #run(String...)} is called. This method combines any
* primary sources specified in the constructor with any additional ones that have
* been {@link #setSources(Set) explicitly set}.
* @return an immutable set of all sources
*/
public Set<Object> getAllSources() {
Set<Object> allSources = new LinkedHashSet<>();
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
}
return Collections.unmodifiableSet(allSources);
}- 默认情况下,返回的结果是
Spring#run(Class<?> primarySource, String... args)
方法的Class<?> primarySource
的方法参数。例如说:MVCApplication 。
- 默认情况下,返回的结果是
-
<2.1>
处,创建 AnnotatedBeanDefinitionReader 对象,设置给annotatedReader
属性。 -
<2.2>
处,创建 XmlBeanDefinitionReader 对象,设置给xmlReader
属性。 -
<2.3>
处,创建 GroovyBeanDefinitionReader 对象,设置给groovyReader
属性。其中,#isGroovyPresent()
方法,判断是否可以使用 Groovy 。代码如下:// BeanDefinitionLoader.java
private boolean isGroovyPresent() {
return ClassUtils.isPresent("groovy.lang.MetaClass", null);
} -
<2.4>
处,创建 ClassPathBeanDefinitionScanner 对象,并设置给scanner
属性。其中,ClassExcludeFilter 是 BeanDefinitionLoader 的内部静态类,继承 AbstractTypeHierarchyTraversingFilter 抽象类,用于排除对sources
的扫描。代码如下:// BeanDefinitionLoader.java
/**
* Simple {@link TypeFilter} used to ensure that specified {@link Class} sources are
* not accidentally re-added during scanning.
*/
private static class ClassExcludeFilter extends AbstractTypeHierarchyTraversingFilter {
private final Set<String> classNames = new HashSet<>();
ClassExcludeFilter(Object... sources) {
super(false, false);
for (Object source : sources) {
if (source instanceof Class<?>) {
this.classNames.add(((Class<?>) source).getName());
}
}
}
protected boolean matchClassName(String className) {
return this.classNames.contains(className);
}
}- 如果不排除,则会出现重复读取 BeanDefinition 的情况。
2.2 setBeanNameGenerator
#setBeanNameGenerator(BeanNameGenerator beanNameGenerator)
方法,代码如下:
// BeanDefinitionLoader.java |
2.3 setResourceLoader
#setResourceLoader(ResourceLoader resourceLoader)
方法,代码如下:
// BeanDefinitionLoader.java |
2.4 setEnvironment
#setEnvironment(ConfigurableEnvironment environment)
方法,代码如下:
// BeanDefinitionLoader.java |
2.5 load
#load()
方法,执行 BeanDefinition 加载。代码如下:
// BeanDefinitionLoader.java |
- 针对不同
source
类型,执行不同的加载逻辑。 -
<1>
处,如果是 Class 类型,则调用#load(Class<?> source)
方法,使用 AnnotatedBeanDefinitionReader 执行加载。详细解析,见 「2.5.1 load(Class<?> source)」 。 -
<2>
处,如果是 Resource 类型,则调用#load(Resource source)
方法,使用 XmlBeanDefinitionReader 执行加载。详细解析,见 「2.5.2 load(Resource source)」 。 -
<3>
处,如果是 Package 类型,则调用#load(Package source)
方法,使用 ClassPathBeanDefinitionScanner 执行加载。详细解析,见 「2.5.3 load(Package source)」 。 -
<4>
处,如果是 CharSequence 类型,则调用#load(CharSequence source)
方法,各种尝试去加载。例如说source
为"classpath:/applicationContext.xml"
。详细解析,见 「2.5.4 load(CharSequence source)」 。 -
<5>
处,无法处理的类型,抛出 IllegalArgumentException 异常。
2.5.1 load(Class<?> source)
#load(Class<?> source)
方法,使用 AnnotatedBeanDefinitionReader 执行加载。代码如下:
// BeanDefinitionLoader.java |
-
<1>
处,调用#isComponent(Class<?> type)
方法,判断是否为 Component 。代码如下:// BeanDefinitionLoader.java
private boolean isComponent(Class<?> type) {
// This has to be a bit of a guess. The only way to be sure that this type is
// eligible is to make a bean definition out of it and try to instantiate it.
// 如果有 @Component 注解,则返回 true
if (AnnotationUtils.findAnnotation(type, Component.class) != null) {
return true;
}
// Nested anonymous classes are not eligible for registration, nor are groovy
// closures
// 暂时忽略
if (type.getName().matches(".*\\$_.*closure.*") || type.isAnonymousClass()
|| type.getConstructors() == null || type.getConstructors().length == 0) {
return false;
}
return true;
}- 因为 Configuration 类,上面有
@Configuration
注解,而@Configuration
上,自带@Component
注解,所以该方法返回true
。
- 因为 Configuration 类,上面有
-
<2>
处,调用AnnotatedBeanDefinitionReader#register(Class<?>... annotatedClasses)
方法,执行注册。
2.5.2 load(Resource source)
#load(Resource source)
方法,使用 XmlBeanDefinitionReader 执行加载。代码如下:
// BeanDefinitionLoader.java |
- 调用
XmlBeanDefinitionReader#loadBeanDefinitions(Resource resource)
方法,从 XML 中加载 BeanDefinition 。
2.5.3 load(Package source)
#load(Package source)
方法,使用 ClassPathBeanDefinitionScanner 执行加载。代码如下:
// BeanDefinitionLoader.java |
2.5.4 load(CharSequence source)
#load(CharSequence source)
方法,各种尝试去加载。代码如下:
按照
source
是 Class > Resource > Package 的顺序,尝试加载。
// BeanDefinitionLoader.java |
-
<1>
处,解析source
。因为,有可能里面带有占位符。 -
<2>
处,将source
转换成 Class ,然后执行 「2.5.1 load(Class<?> source)」 的流程。 -
<3>
处,尝试按照 Resource 进行加载。 -
<3.1>
处,调用#findResources(String source)
方法,获得source
对应的 Resource 数组。代码如下:// BeanDefinitionLoader.java
private Resource[] findResources(String source) {
// 创建 ResourceLoader 对象
ResourceLoader loader = (this.resourceLoader != null) ? this.resourceLoader : new PathMatchingResourcePatternResolver();
try {
// 获得 Resource 数组
if (loader instanceof ResourcePatternResolver) {
return ((ResourcePatternResolver) loader).getResources(source);
}
// 获得 Resource 对象
return new Resource[] { loader.getResource(source) };
} catch (IOException ex) {
throw new IllegalStateException("Error reading source '" + source + "'");
}
} -
<3.2>
处,遍历resources
数组,调用#isLoadCandidate(Resource resource)
方法,判断是否为符合条件的 Resource 。代码如下:// BeanDefinitionLoader.java
private boolean isLoadCandidate(Resource resource) {
// 不存在,则返回 false
if (resource == null || !resource.exists()) {
return false;
}
// 判断 resource 是 ClassPathResource 类,不是一个 package
if (resource instanceof ClassPathResource) {
// A simple package without a '.' may accidentally get loaded as an XML
// document if we're not careful. The result of getInputStream() will be
// a file list of the package content. We double check here that it's not
// actually a package.
String path = ((ClassPathResource) resource).getPath();
if (path.indexOf('.') == -1) {
try {
return Package.getPackage(path) == null;
} catch (Exception ex) {
// Ignore
}
}
}
// 返回 true ,符合条件
return true;
} -
<3.3>
处,执行 「2.5.2 load(Resource source)」 的流程。 -
<3.4>
处,有加载到,则认为成功,返回。 -
<4>
处,尝试按照 Package 进行加载。 -
<4.1>
处,调用#findPackage(CharSequence source)
方法,获得 Package 对象。代码如下:// BeanDefinitionLoader.java
private Package findPackage(CharSequence source) {
// <X> 获得 source 对应的 Package 。如果存在,则返回
Package pkg = Package.getPackage(source.toString());
if (pkg != null) {
return pkg;
}
try {
// Attempt to find a class in this package
// 创建 ResourcePatternResolver 对象
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(getClass().getClassLoader());
// 尝试加载 source 目录下的 class 们
Resource[] resources = resolver.getResources(ClassUtils.convertClassNameToResourcePath(source.toString()) + "/*.class");
// 遍历 resources 数组
for (Resource resource : resources) {
// 获得类名
String className = StringUtils.stripFilenameExtension(resource.getFilename());
// 按照 Class 进行加载 BeanDefinition
load(Class.forName(source.toString() + "." + className));
break;
}
} catch (Exception ex) {
// swallow exception and continue
}
// 返回 Package
return Package.getPackage(source.toString());
}- 虽然逻辑比较复杂,我们只需要看看
<X>
处的前半部分的逻辑即可。
- 虽然逻辑比较复杂,我们只需要看看
-
<4.2>
处,执行 「2.5.3 load(Package source)」 的流程。 -
<5>
处,无法处理,抛出 IllegalArgumentException 异常。
666. 彩蛋
简单小文一篇。如果胖友不了解 Spring BeanDefinition ,可以补充看看 《【死磕 Spring】—— IoC 之加载 BeanDefinition》 文章。
如果想要测试 SpringFactoriesLoader 的各种情况,可以调试 BeanDefinitionLoaderTests 提供的单元测试。
参考和推荐如下文章:
- 一个努力的码农 《spring boot 源码解析8-SpringApplication#run第8步》
- oldflame-Jm 《Spring boot源码分析-BeanDefinitionLoader(7)》
相关推荐
在Spring Boot的应用启动过程中,`BeanDefinitionLoader`扮演着至关重要的角色,它是Spring Boot自动配置的核心部分。在源码分析中,我们首先看到`prepareContext`方法被调用,这是`SpringApplication`类中的一个...
本示例“spring boot整合JPA——demo”将演示如何在Spring Boot项目中配置和使用JPA。 首先,我们需要理解Spring Boot与JPA的关系。Spring Boot是基于Spring框架的快速开发工具,它通过自动化配置减少了常规设置...
**Spring Boot创建与运行项目详解** Spring Boot是Java开发领域中的一个热门框架,它通过简化配置和自动装配,使得创建和运行Spring应用变得更加容易。在本篇内容中,我们将深入探讨如何利用Spring Boot来创建和...
深入学习spring boot 懂得各个标签,注解的用途和原理
《果子学院Spring Boot源码解析》是一套深入学习Spring Boot源码的教程,旨在帮助开发者深入了解这个流行的Java开发框架的内部工作机制。Spring Boot简化了Java应用的初始搭建以及开发过程,它集成了大量常用的第三...
通过源码分析,开发者可以更好地理解Spring Boot的自动装配、启动流程以及如何自定义启动器。Spring Boot的自动装配原理涉及到Spring Boot的核心特性,它简化了基于Spring的应用开发,通过自动配置减少了大量的配置...
在这个版本中,我们将深入探讨Spring Boot的核心特性、工作原理以及如何通过源码来理解其内部机制。 首先,Spring Boot的核心理念是“约定优于配置”,它通过预设许多默认配置,减少了开发者需要手动配置的繁琐工作...
《Spring Boot实战派》源码提供了丰富的学习材料,旨在帮助开发者深入理解并...通过分析《Spring Boot实战派》源码,读者不仅可以了解上述技术点,还能学习到如何在实际项目中应用这些技术,提升开发效率和代码质量。
基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 ...
下面,我们将深入探讨Spring Boot的源码,揭示其内部工作原理。 1. **自动配置**:Spring Boot的自动配置是其核心特性之一。在`spring-boot-autoconfigure`模块中,通过条件注解(如`@ConditionalOnClass`, `@...
java毕业设计——基于spring boot的音乐播放网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的音乐播放网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的音乐播放网站设计与...
基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring ...
Spring Boot是Java开发领域的一款非常流行的框架,它简化了基于Spring的应用程序开发流程。Spring Boot 2.6.2是该框架...通过分析源码,我们可以学习到Spring框架的最佳实践,以及如何设计和实现一个健壮的微服务架构。
通过深入阅读和分析Spring Boot 2.7.0的源码,我们可以了解到Spring Boot是如何实现其核心特性的,以及如何利用Spring Framework进行扩展和定制。同时,这也有助于我们更好地利用Spring Boot进行微服务开发,提高...
这本《Vue Spring Boot前后端分离开发实战》的源码提供了深入学习和实践这一技术栈的机会。以下是对其中涉及知识点的详细说明: 1. **Vue.js**:Vue.js是一个轻量级的前端JavaScript框架,以其易学易用、组件化和...
《Spring5 源码分析(第 2 版)》是针对Spring框架第五个主要版本的深度解析著作,由知名讲师倾心打造,旨在帮助读者深入理解Spring框架的内部工作机制,提升对Java企业级应用开发的专业技能。本书涵盖了Spring框架的...
在深入理解Spring Boot的源码时,我们首先要明白其核心设计理念——“约定优于配置”。Spring Boot通过预设默认配置,使得开发者能够快速启动并运行应用,同时提供了丰富的启动器(Starters)来简化依赖管理。 ...
本资源包"使用Gradle构建Spring Boot工程系列项目源码"是针对一系列教程的配套源代码,旨在帮助开发者深入理解如何利用Gradle有效地构建Spring Boot应用程序。通过分析这些源码,我们可以学习到以下关键知识点: 1....
本学习资源包“java maven工程 spring boot 学习源码”提供了一个可以直接运行的示例工程,有助于深入理解Spring Boot和Maven的结合使用。 首先,我们需要了解Spring Boot的核心特性。Spring Boot通过内嵌的Servlet...
java毕业设计——基于spring boot的就业信息管理网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的就业信息管理网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的就业信息管理...