springboot自动配置原理以及spring.factories文件的作用详解

目录
  • 一、springboot 自动配置原理
  • 二、spring.factories文件的作用
  • spring.factories 的妙用
    • 什么是 SPI 机制?
    • Spring Boot 中的 SPI 机制
    • Spring Factories 实现原理是什么?
    • Spring Factories 在 Spring Boot 中的应用

一、springboot 自动配置原理

先说说我们自己的应用程序中Bean加入容器的办法:

package com.ynunicom.dc.dingdingcontractapp;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @author jinye.Bai
 */
@SpringBootApplication(
        scanBasePackages ={"com.ynunicom.dc.dingdingcontractapp"}
)
public class DingdingContractAppApplication {
    public static void main(String[] args) {
        SpringApplication.run(DingdingContractAppApplication.class, args);
    }
}

我们在应用程序的入口设置了 @SpringBootApplication标签,默认情况下他会扫描所有次级目录。

如果增加了 scanBasePackages属性,就会扫描所有被指定的路径及其次级目录。

那么它在扫描的是什么东西呢?

是这个:@Component

所有被扫描到的 @Component,都会成为一个默认的singleton(单例,即一个容器里只有一个对象实体)加入到容器中。

认识到以上这点,便于我们理解springboot自动配置的机制。

接下来让我们看看在自己的应用程序中实现配置的方法。

如图:

package com.ynunicom.dc.dingdingcontractapp.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
 * @author: jinye.Bai
 * @date: 2020/5/22 15:51
 */
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(){
        return  new RestTemplate();
    }
}

这里我们设置了一个配置,往容器中加入了一个RestTemplate。

首先说 @Configuration,这个标签继承了 @Component标签,我们可以在标签内容看到:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

可以看到其中是有 @Component标签的,所以,@Configuration会被 @SpringBootApplication扫描到,进而把它和它下面的 @Bean加入容器,于是我们 RestTemplate的内容就配置完成了,在后续的使用中,我们就可以直接从容器中拿出RestTemplate使用它。

对于在maven中引用的其他外部包加入容器的过程,需要用到spring.factories。

二、spring.factories文件的作用

在springboot运行时,SpringFactoriesLoader 类会去寻找

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

我们以mybatis-plus为例。

首先我们引入:

  <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>

然后去maven的依赖里看它的自动配置类MybatisPlusAutoConfiguration

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {

可以看到有上文提到的 @Configuration,还有从application.yml载入自动配置的 @EnableConfigurationProperties({MybatisPlusProperties.class})

这个注解的具体内容请查看我另一篇博文,对其进行了解释:

迅速学会@ConfigurationProperties的使用

也就是说,springboot只要能扫描到MybatisPlusAutoConfiguration类的 @Configuration注解,其中的所有配置就能自动加入到容器中,这一过程由上面提到的SpringFactoriesLoader 起作用,它会去寻找 “META-INF/spring.factories” 文件,我们可以在 mybatis-plus的依赖中找到它:

SpringFactoriesLoader为什么要读取它呢?因为它内部是这样的

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

spring.factories用键值对的方式记录了所有需要加入容器的类,EnableAutoConfigurationImportSelector的selectImports方法返回的类名,来自spring.factories文件内的配置信息,这些配置信息的key等于EnableAutoConfiguration,因为spring boot应用启动时使用了EnableAutoConfiguration注解,所以EnableAutoConfiguration注解通过import注解将EnableAutoConfigurationImportSelector类实例化,并且将其selectImports方法返回的类名实例化后注册到spring容器。

以上内容是springboot获得这些类的方式,如果你想要实现自己的自动配置,就将你的类通过键值对的方式写在你的spring.factories即可,注意,值是你的自动配置类,键必须是org.springframework.boot.autoconfigure.EnableAutoConfiguration

spring.factories 的妙用

现象

在阅读 Spring-Boot 相关源码时,常常见到 spring.factories 文件,里面写了自动配置(AutoConfiguration)相关的类名,因此产生了一个疑问:“明明自动配置的类已经打上了 @Configuration 的注解,为什么还要写 spring.factories 文件?

用过 Spring Boot 的都知道

@ComponentScan 注解的作用是扫描 @SpringBootApplication 所在的 Application 类所在的包(basepackage)下所有的 @component 注解(或拓展了 @component 的注解)标记的 bean,并注册到 spring 容器中。

那么问题来了

在 Spring Boot 项目中,如果你想要被 Spring 容器管理的 bean 不在 Spring Boot 包扫描路径下,怎么办?

解决 Spring Boot 中不能被默认路径扫描的配置类的方式,有 2 种:

(1)在 Spring Boot 主类上使用 @Import 注解

(2)使用 spring.factories 文件

以下是对 使用 spring.factories 文件的简单理解

Spring Boot 的扩展机制之 Spring Factories

Spring Boot 中有一种非常解耦的扩展机制:Spring Factories。这种扩展机制实际上是仿照Java中的SPI扩展机制来实现的。

什么是 SPI 机制?

SPI 的全名为 Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。

简单的总结下 java SPI 机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

Spring Boot 中的 SPI 机制

在 Spring 中也有一种类似与 Java SPI 的加载机制。它在 resources/META-INF/spring.factories 文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。

在 Spring 中也有一种类似与 Java SPI 的加载机制。它在 resources/META-INF/spring.factories 文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。

这种自定义的SPI机制是 Spring Boot Starter 实现的基础。

Spring Factories 实现原理是什么?

spring-core 包里定义了 SpringFactoriesLoader 类,这个类实现了检索 META-INF/spring.factories 文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表。 loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表。

上面的两个方法的关键都是从指定的 ClassLoader 中获取 spring.factories 文件,并解析得到类名列表,具体代码如下

public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
    private SpringFactoriesLoader() {}
    public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList(factoryNames.size());
        Iterator var5 = factoryNames.iterator();
        while(var5.hasNext()) {
            String factoryName = (String)var5.next();
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;
                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
    private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
        try {
            Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
            if (!factoryClass.isAssignableFrom(instanceClass)) {
                throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
            } else {
                return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();
            }
        } catch (Throwable var4) {
            throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);
        }
    }
}

从代码中我们可以知道,在这个方法中会遍历整个 spring-boot 项目的 classpath 下 ClassLoader 中所有 jar 包下的 spring.factories文件。也就是说我们可以在自己的 jar 中配置 spring.factories 文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。

Spring Factories 在 Spring Boot 中的应用

在 Spring Boot 的很多包中都能够找到 spring.factories 文件,接下来我们以 spring-boot-autoconfigure 包为例进行介绍

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration

结合前面的内容,可以看出 spring.factories 文件可以将 spring-boot 项目包以外的 bean(即在 pom 文件中添加依赖中的 bean)注册到 spring-boot 项目的 spring 容器。

由于@ComponentScan 注解只能扫描 spring-boot 项目包内的 bean 并注册到 spring 容器中,因此需要 @EnableAutoConfiguration 注解来注册项目包外的bean。

而 spring.factories 文件,则是用来记录项目包外需要注册的bean类名。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 全面解析SpringBoot自动配置的实现原理

    之前一直在用SpringBoot框架,一直感觉SpringBoot框架自动配置的功能很强大,但是并没有明白它是怎么实现自动配置的,现在有空研究了一下,大概明白了SpringBoot框架是怎么实现自动配置的功能,我们编写一个最简单的自动配置功能,大概的总结一下. 一,配置属性类 其实就是值对象注入的方式去配置一些Spring常用的配置,我们编写一个最简单的配置对象. @ConfigurationProperties(prefix = "hello") //@Component //如果这

  • SpringBoot启动及自动装配原理过程详解

    一.servlet2(老spring-mvc) 配置文件: web.xml:主要配置项目启动项 application-context.xml:主要配置项目包扫描.各种bean.事务管理 springMVC.xml:主要配置controller包扫描.视图解析器.参数解析器 启动过程: 每一个spring项目启动时都需要初始化spring-context,对于非web项目可以在程序main方法中触发这个context的初始化过程. 由于web项目的启动入口在容器,所以开发者不能直接触发sprin

  • SpringBoot自动配置原理,你真的懂吗?(简单易懂)

    概述 上面博文(SpringBoot简介与快速搭建)我们简单的介绍了什么是SpringBoot,以及如何使用SpringBoot,但是我们对于SpringBoot的基本原理并没有介绍,这篇博文我们重点介绍SpringBoot是如何实现的自动配置. 依赖管理 在我们的pom文件中最核心的依赖就一个: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-star

  • SpringBoot自动配置的实现原理

    一.运行原理 Spring Boot的运行是由注解@EnableAutoConfiguration提供的. @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAuto

  • springboot自动配置原理以及spring.factories文件的作用详解

    目录 一.springboot 自动配置原理 二.spring.factories文件的作用 spring.factories 的妙用 什么是 SPI 机制? Spring Boot 中的 SPI 机制 Spring Factories 实现原理是什么? Spring Factories 在 Spring Boot 中的应用 一.springboot 自动配置原理 先说说我们自己的应用程序中Bean加入容器的办法: package com.ynunicom.dc.dingdingcontract

  • springboot自动配置原理解析

    前言 小伙伴们都知道,现在市面上最流行的web开发框架就是springboot了,在springboot开始流行之前,我们都用的是strust2或者是springmvc框架来开发web应用,但是这两个框架都有一个特点就是配置非常的繁琐,要写一大堆的配置文件,spring在支持了注解开发之后稍微有些改观但有的时候还是会觉得比较麻烦,这个时候springboot就体现出了它的优势,springboot只需要一个properties或者yml文件就可以简化springmvc中在xml中需要配置的一大堆

  • SpringBoot 自动配置原理及源码解析

    初始化一个Springboot项目,在主启动类会有这么一个注解:@SpringBootApplication,自动装配的秘密全在主启动类这个注解里面了 点进去一层会发现有三个子注解组成,分别是 @SpringBootConfiguration.@ComponentScan和@EnableAutoConfiguration 接下来分别解释这三个注解在整个自动装配过程中的作用 1.@SpringBootConfiguration 点进去发现它是@Configure,代表当前是一个配置类,意思就是当前

  • 深入浅析SpringBoot自动配置原理

    SpringBoot2.3.1版本源码 一.SpringBoot启动的时候加载主配置类,通过@EnableAutoConfiguration注解开启了自动配置功能 . 二.@EnableAutoConfiguration作用: 1. 点击该注解进入可以发现,它利用AutoConfigurationImportSelector.class 选择器给SpringBoot导入一些组件.导入哪些组件呢?可以点击选择器进入查看selectImports()方法的内容,该方法最终会返回一个configurati

  • SpringBoot自动配置原理详解

    目录 阅读收获 一.SpringBoot是什么 二.SpringBoot的特点 三.启动类 3.1 @SpringBootApplication 四.@EnableAutoConfiguration 4.1 @AutoConfigurationPackage 4.2  @Import({AutoConfigurationImportSelector.class}) 五.流程总结图 六.常用的Conditional注解 七.@Import支持导入的三种方式 阅读收获 理解SpringBoot自动配

  • Java SpringBoot自动配置原理详情

    目录 SpringBoot的底层注解 配置绑定 自动配置原理入门 SpringBoot的底层注解 首先了解一些SpringBoot的底层注解,是如何完成相关的功能的 @Configuration 告诉SpringBoot被标注的类是一个配置类,以前Spring xxx.xml能配置的内容,它都可以做,spring中的Bean组件默认是单实例的 #############################Configuration使用示例###############################

  • SpringBoot自动配置原理分析

    目录 前言 一.启动类 1.1.@SpringBootConfiguration 1.2.@EnableAutoConfiguration 1.3.@ComponentScan 1.4.探究方向 二.@SpringBootConfiguration 三.@EnableAutoConfiguration 3.1.@AutoConfigurationPackage 3.2.@Import(AutoConfigurationImportSelector.class) 3.2.1.getCandidat

  • Python中__init__.py文件的作用详解

    __init__.py 文件的作用是将文件夹变为一个Python模块,Python 中的每个模块的包中,都有__init__.py 文件. 通常__init__.py 文件为空,但是我们还可以为它增加其他的功能.我们在导入一个包时,实际上是导入了它的__init__.py文件.这样我们可以在__init__.py文件中批量导入我们所需要的模块,而不再需要一个一个的导入. # package # __init__.py import re import urllib import sys impo

  • 浅谈springboot自动配置原理

    从main函数说起 一切的开始要从SpringbootApplication注解说起. @SpringBootApplication public class MyBootApplication { public static void main(String[] args) { SpringApplication.run(MyBootApplication.class); } } @SpringBootConfiguration @EnableAutoConfiguration @Compon

随机推荐