`
shinesuo
  • 浏览: 156130 次
  • 性别: Icon_minigender_1
  • 来自: 宇宙
社区版块
存档分类
最新评论

Spring Boot 源码分析 —— BeanDefinitionLoader

 
阅读更多

1. 概述

本文,我们来补充 《精尽 Spring Boot 源码分析 —— SpringApplication》 文章,并未详细解析的 BeanDefinitionLoader 。在 SpringApplication 中,我们可以看到 #load(ApplicationContext context, Object[] sources) 方法中,是如下一段代码:

// SpringApplication.java

protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
// <1> 创建 BeanDefinitionLoader 对象
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
// <2> 设置 loader 的属性
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
// <3> 执行 BeanDefinition 加载
loader.load();
}

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}

下面,我们来一起揭开它的面纱~

2. BeanDefinitionLoader

org.springframework.boot.BeanDefinitionLoader ,BeanDefinition 加载器(Loader),负责 Spring Boot 中,读取 BeanDefinition 。其类上的注释如下:

// BeanDefinitionLoader.java

/**
* Loads bean definitions from underlying sources, including XML and JavaConfig. Acts as a
* simple facade over {@link AnnotatedBeanDefinitionReader},
* {@link XmlBeanDefinitionReader} and {@link ClassPathBeanDefinitionScanner}. See
* {@link SpringApplication} for the types of sources that are supported.
*/

2.1 构造方法

// BeanDefinitionLoader.java

/**
* 来源的数组
*/
private final Object[] sources;

/**
* 注解的 BeanDefinition 读取器
*/
private final AnnotatedBeanDefinitionReader annotatedReader;
/**
* XML 的 BeanDefinition 读取器
*/
private final XmlBeanDefinitionReader xmlReader;
/**
* Groovy 的 BeanDefinition 读取器
*/
private BeanDefinitionReader groovyReader;
/**
* Classpath 的 BeanDefinition 扫描器
*/
private final ClassPathBeanDefinitionScanner scanner;

/**
* 资源加载器
*/
private ResourceLoader resourceLoader;

/**
* Create a new {@link BeanDefinitionLoader} that will load beans into the specified
* {@link BeanDefinitionRegistry}.
* @param registry the bean definition registry that will contain the loaded beans
* @param sources the bean sources
*/
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources; // <1>
// 创建 AnnotatedBeanDefinitionReader 对象
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
// 创建 XmlBeanDefinitionReader 对象
this.xmlReader = new XmlBeanDefinitionReader(registry);
// 创建 GroovyBeanDefinitionReader 对象
if (isGroovyPresent()) {
this.groovyReader = new GroovyBeanDefinitionReader(registry);
}
// 创建 ClassPathBeanDefinitionScanner 对象
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
  • <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());
    }
    }
    }

    @Override
    protected boolean matchClassName(String className) {
    return this.classNames.contains(className);
    }

    }
    • 如果不排除,则会出现重复读取 BeanDefinition 的情况。

2.2 setBeanNameGenerator

#setBeanNameGenerator(BeanNameGenerator beanNameGenerator) 方法,代码如下:

// BeanDefinitionLoader.java

/**
* Set the bean name generator to be used by the underlying readers and scanner.
* @param beanNameGenerator the bean name generator
*/
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
this.annotatedReader.setBeanNameGenerator(beanNameGenerator);
this.xmlReader.setBeanNameGenerator(beanNameGenerator);
this.scanner.setBeanNameGenerator(beanNameGenerator);
}

2.3 setResourceLoader

#setResourceLoader(ResourceLoader resourceLoader) 方法,代码如下:

// BeanDefinitionLoader.java

/**
* Set the resource loader to be used by the underlying readers and scanner.
* @param resourceLoader the resource loader
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
this.xmlReader.setResourceLoader(resourceLoader);
this.scanner.setResourceLoader(resourceLoader);
}

2.4 setEnvironment

#setEnvironment(ConfigurableEnvironment environment) 方法,代码如下:

// BeanDefinitionLoader.java

/**
* Set the environment to be used by the underlying readers and scanner.
* @param environment the environment
*/
public void setEnvironment(ConfigurableEnvironment environment) {
this.annotatedReader.setEnvironment(environment);
this.xmlReader.setEnvironment(environment);
this.scanner.setEnvironment(environment);
}

2.5 load

#load() 方法,执行 BeanDefinition 加载。代码如下:

// BeanDefinitionLoader.java

/**
* Load the sources into the reader.
* @return the number of loaded beans
*/
public int load() {
int count = 0;
// 遍历 sources 数组,逐个加载
for (Object source : this.sources) {
count += load(source);
}
return count;
}

private int load(Object source) {
Assert.notNull(source, "Source must not be null");
// <1> 如果是 Class 类型,则使用 AnnotatedBeanDefinitionReader 执行加载
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
// <2> 如果是 Resource 类型,则使用 XmlBeanDefinitionReader 执行加载
if (source instanceof Resource) {
return load((Resource) source);
}
// <3> 如果是 Package 类型,则使用 ClassPathBeanDefinitionScanner 执行加载
if (source instanceof Package) {
return load((Package) source);
}
// <4> 如果是 CharSequence 类型,则各种尝试去加载
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
// <5> 无法处理的类型,抛出 IllegalArgumentException 异常
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
  • 针对不同 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

private int load(Class<?> source) {
// Groovy 相关,暂时忽略
if (isGroovyPresent()
&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
load(loader);
}
// <1> 如果是 Component ,则执行注册
if (isComponent(source)) {
this.annotatedReader.register(source); // <2>
return 1;
}
return 0;
}
  • <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 
  • <2> 处,调用 AnnotatedBeanDefinitionReader#register(Class<?>... annotatedClasses) 方法,执行注册。

2.5.2 load(Resource source)

#load(Resource source) 方法,使用 XmlBeanDefinitionReader 执行加载。代码如下:

// BeanDefinitionLoader.java

private int load(Resource source) {
// Groovy 相关,暂时忽略
if (source.getFilename().endsWith(".groovy")) {
if (this.groovyReader == null) {
throw new BeanDefinitionStoreException("Cannot load Groovy beans without Groovy on classpath");
}
return this.groovyReader.loadBeanDefinitions(source);
}
// 使用 XmlBeanDefinitionReader 加载 BeanDefinition
return this.xmlReader.loadBeanDefinitions(source);
}
  • 调用 XmlBeanDefinitionReader#loadBeanDefinitions(Resource resource) 方法,从 XML 中加载 BeanDefinition 。

2.5.3 load(Package source)

#load(Package source) 方法,使用 ClassPathBeanDefinitionScanner 执行加载。代码如下:

// BeanDefinitionLoader.java

private int load(Package source) {
return this.scanner.scan(source.getName());
}

2.5.4 load(CharSequence source)

#load(CharSequence source) 方法,各种尝试去加载。代码如下:

按照 source 是 Class > Resource > Package 的顺序,尝试加载。

// BeanDefinitionLoader.java

private int load(CharSequence source) {
// <1> 解析 source 。因为,有可能里面带有占位符。
String resolvedSource = this.xmlReader.getEnvironment().resolvePlaceholders(source.toString());
// <2> 尝试按照 Class 进行加载
// Attempt as a Class
try {
return load(ClassUtils.forName(resolvedSource, null));
} catch (IllegalArgumentException | ClassNotFoundException ex) {
// swallow exception and continue
}
// <3> 尝试按照 Resource 进行加载
// Attempt as resources
Resource[] resources = findResources(resolvedSource); // <3.1>
int loadCount = 0;
boolean atLeastOneResourceExists = false;
for (Resource resource : resources) {
if (isLoadCandidate(resource)) { // <3.2>
atLeastOneResourceExists = true;
loadCount += load(resource); // <3.3>
}
}
if (atLeastOneResourceExists) { // <3.4> 有加载到,则认为成功,返回。
return loadCount;
}
// Attempt as package
// <4> 尝试按照 Package 进行加载
Package packageResource = findPackage(resolvedSource); // <4.1>
if (packageResource != null) {
return load(packageResource); // <4.2>
}
// <5> 无法处理,抛出 IllegalArgumentException 异常
throw new IllegalArgumentException("Invalid source '" + resolvedSource + "'");
}
  • <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 提供的单元测试。

参考和推荐如下文章:

 

分享到:
评论

相关推荐

    SpringBoot源码解析——BeanDefinitionLoader 源码分析.docx

    在Spring Boot的应用启动过程中,`BeanDefinitionLoader`扮演着至关重要的角色,它是Spring Boot自动配置的核心部分。在源码分析中,我们首先看到`prepareContext`方法被调用,这是`SpringApplication`类中的一个...

    spring boot整合JPA——demo

    本示例“spring boot整合JPA——demo”将演示如何在Spring Boot项目中配置和使用JPA。 首先,我们需要理解Spring Boot与JPA的关系。Spring Boot是基于Spring框架的快速开发工具,它通过自动化配置减少了常规设置...

    Spring Boot课件1 —— 创建和运行Spring Boot项目

    **Spring Boot创建与运行项目详解** Spring Boot是Java开发领域中的一个热门框架,它通过简化配置和自动装配,使得创建和运行Spring应用变得更加容易。在本篇内容中,我们将深入探讨如何利用Spring Boot来创建和...

    spring boot源码分析

    深入学习spring boot 懂得各个标签,注解的用途和原理

    果子学院Spring boot源码解析

    《果子学院Spring Boot源码解析》是一套深入学习Spring Boot源码的教程,旨在帮助开发者深入了解这个流行的Java开发框架的内部工作机制。Spring Boot简化了Java应用的初始搭建以及开发过程,它集成了大量常用的第三...

    第三节-springboot源码解析-王炸篇.pdf

    通过源码分析,开发者可以更好地理解Spring Boot的自动装配、启动流程以及如何自定义启动器。Spring Boot的自动装配原理涉及到Spring Boot的核心特性,它简化了基于Spring的应用开发,通过自动配置减少了大量的配置...

    Spring Boot源码(spring-boot-2.6.2.zip)

    在这个版本中,我们将深入探讨Spring Boot的核心特性、工作原理以及如何通过源码来理解其内部机制。 首先,Spring Boot的核心理念是“约定优于配置”,它通过预设许多默认配置,减少了开发者需要手动配置的繁琐工作...

    Spring Boot实战派(源码)

    《Spring Boot实战派》源码提供了丰富的学习材料,旨在帮助开发者深入理解并...通过分析《Spring Boot实战派》源码,读者不仅可以了解上述技术点,还能学习到如何在实际项目中应用这些技术,提升开发效率和代码质量。

    基于spring boot餐厅管理系统源码.zip

    基于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的音乐播放网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的音乐播放网站设计与...

    基于 Spring Boot + MySQL 开发的博客系统源码.zip

    基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring ...

    Spring Boot源码(spring-boot-2.6.2.tar.gz)

    Spring Boot是Java开发领域的一款非常流行的框架,它简化了基于Spring的应用程序开发流程。Spring Boot 2.6.2是该框架...通过分析源码,我们可以学习到Spring框架的最佳实践,以及如何设计和实现一个健壮的微服务架构。

    spring-boot-2.7.0.zip源码

    通过深入阅读和分析Spring Boot 2.7.0的源码,我们可以了解到Spring Boot是如何实现其核心特性的,以及如何利用Spring Framework进行扩展和定制。同时,这也有助于我们更好地利用Spring Boot进行微服务开发,提高...

    《Vue Spring Boot前后端分离开发实战》源码Vue+Spring Boot前后端分离开发实战教学课件(PPT)

    这本《Vue Spring Boot前后端分离开发实战》的源码提供了深入学习和实践这一技术栈的机会。以下是对其中涉及知识点的详细说明: 1. **Vue.js**:Vue.js是一个轻量级的前端JavaScript框架,以其易学易用、组件化和...

    Spring5 源码分析(第 2 版) .zip

    《Spring5 源码分析(第 2 版)》是针对Spring框架第五个主要版本的深度解析著作,由知名讲师倾心打造,旨在帮助读者深入理解Spring框架的内部工作机制,提升对Java企业级应用开发的专业技能。本书涵盖了Spring框架的...

    spring boot 深入浅出源码

    在深入理解Spring Boot的源码时,我们首先要明白其核心设计理念——“约定优于配置”。Spring Boot通过预设默认配置,使得开发者能够快速启动并运行应用,同时提供了丰富的启动器(Starters)来简化依赖管理。 ...

    使用Gradle 构建spring Boot工程系列项目源码(配合第五篇文章)

    本资源包"使用Gradle构建Spring Boot工程系列项目源码"是针对一系列教程的配套源代码,旨在帮助开发者深入理解如何利用Gradle有效地构建Spring Boot应用程序。通过分析这些源码,我们可以学习到以下关键知识点: 1....

    java maven工程 spring boot 学习源码

    本学习资源包“java maven工程 spring boot 学习源码”提供了一个可以直接运行的示例工程,有助于深入理解Spring Boot和Maven的结合使用。 首先,我们需要了解Spring Boot的核心特性。Spring Boot通过内嵌的Servlet...

    java毕业设计——基于spring boot的就业信息管理网站设计与实现(源码+数据库).zip

    java毕业设计——基于spring boot的就业信息管理网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的就业信息管理网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的就业信息管理...

Global site tag (gtag.js) - Google Analytics