SpringBoot自动配置原理

springBoot是如何实现自动装配的?

SpringBootApplication注解

SpringBoot 的诞生就是为了简化 Spring 中繁琐的 XML 配置

springBoot一切来源于SpringBoot的启动类,main方法上会有一个注解@SpringBootApplication

/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @since 1.2.0
 */
@Target(ElementType.TYPE)  //SpringBootApplication注解可以在类、接口(包括注释类型)或枚举声明
@Retention(RetentionPolicy.RUNTIME) //指定该注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Documented //这个注解只是用来标注生成javadoc的时候是否会被记录
@Inherited //指定该注解在类上使用时,可以被子类继承
@SpringBootConfiguration //套壳的Configuration,类似Service和Component的区别 
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
//ComponentScan默认会扫描当前包和所有子包,和xml配置自动扫描效果一样,@Filter是排除了两个系统类
public @interface SpringBootApplication {}

@SpringBootApplication是一个复合注解,主要包含三个重要元注解

  • @SpringBootConfiguration: 里面就是@Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已

    • @Configuration : 标注在某个类上,表示这是一个 springboot的配置类。可以向容器中注入组件。

  • @ComponentScan: 配置用于 Configuration 类的组件扫描指令。

  • @EnableAutoConfiguration : springBoot自动配置原理依赖于该注解,引入了AutoConfigurationImportSelector,该类中 的方法会扫描所有存在META-INF/spring.factories的jar包。

如上就是springBoot基本的自动配置原理。

SpringBootApplication注意事项

  • @SpringBootApplication 注解应该放在主类上,也就是启动类上,通常位于项目的根包下,这样可以保证 @ComponentScan 注解能够扫描到所有的组件和配置类。

  • @SpringBootApplication 注解可以通过 exclude 或 excludeName 属性来排除一些不需要的自动配置类,从而减少启动时间和内存占用

  • @SpringBootApplication 注解可以通过 scanBasePackages 或 scanBasePackageClasses 属性来指定需要扫描的包或类,从而覆盖默认的扫描规则。

导入自动配置类

深入@EnableAutoConfiguration

之前我们了解了spirngBoot依赖于@EnableAutoConfiguration注解进行自动配置的基本信息,接下来我们对该注解注解继续深入

/**
 * Enable auto-configuration of the Spring Application Context, attempting to guess and
 * configure beans that you are likely to need. Auto-configuration classes are usually
 * applied based on your classpath and what beans you have defined. For example, if you
 * have {@code tomcat-embedded.jar} on your classpath you are likely to want a
 * {@link TomcatServletWebServerFactory} (unless you have defined your own
 * {@link ServletWebServerFactory} bean).
 * <p>
 * When using {@link SpringBootApplication @SpringBootApplication}, the auto-configuration
 * of the context is automatically enabled and adding this annotation has therefore no
 * additional effect.
 * <p>
 * Auto-configuration tries to be as intelligent as possible and will back-away as you
 * define more of your own configuration. You can always manually {@link #exclude()} any
 * configuration that you never want to apply (use {@link #excludeName()} if you don't
 * have access to them). You can also exclude them via the
 * {@code spring.autoconfigure.exclude} property. Auto-configuration is always applied
 * after user-defined beans have been registered.
 * <p>
 * The package of the class that is annotated with {@code @EnableAutoConfiguration},
 * usually via {@code @SpringBootApplication}, has specific significance and is often used
 * as a 'default'. For example, it will be used when scanning for {@code @Entity} classes.
 * It is generally recommended that you place {@code @EnableAutoConfiguration} (if you're
 * not using {@code @SpringBootApplication}) in a root package so that all sub-packages
 * and classes can be searched.
 * <p>
 * Auto-configuration classes are regular Spring {@link Configuration @Configuration}
 * beans. They are located using the {@link SpringFactoriesLoader} mechanism (keyed
 * against this class). Generally auto-configuration beans are
 * {@link Conditional @Conditional} beans (most often using
 * {@link ConditionalOnClass @ConditionalOnClass} and
 * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @since 1.0.0
 * @see ConditionalOnBean
 * @see ConditionalOnMissingBean
 * @see ConditionalOnClass
 * @see AutoConfigureAfter
 * @see SpringBootApplication
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage  //2.向springBoot自动注入配置类
@Import(AutoConfigurationImportSelector.class) //1.扫描需要被自动配置的类
public @interface EnableAutoConfiguration {}

@EnableAutoConfiguration的作用是什么?

查看顺序:AutoConfigurationImportSelector类 > DeferredImportSelector接口 > ImportSelector接口 > selectImports方法 >

AutoConfigurationImportSelector类的selectImport具体实现方法

该注解主要用于扫描和配置可能需要的 bean。先提一嘴,这个可能的bean在spring.factories中配置。

  1. 可以看到,该注解使用@Import导入了一个类文件AutoConfigurationImportSelector,该类实现了DeferredImportSelector接口

  2. 而DeferredImportSelector接口继承于ImportSelector类

selectImports方法

ImportSelector接口定义了一个重要方法selectImports

public interface ImportSelector {
    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     * @return the class names, or an empty array if none
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

我们主要观看ImportSelector接口的下级DeferredImportSelector接口,但由于DeferredImportSelector为接口,所以真正的逻辑代码在DeferredImportSelector的下级AutoConfigurationImportSelector的selectImports方法中实现。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
            
    private static final String[] NO_IMPORTS = {};
     
    /**
     * 如果在springBoot配置文件中开启了自动配置
     * 那么就获取需要被引入的的自动配置
     */    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
                     
}

什么是如果在springBoot配置文件中开启了自动配置?

这个就是我们常用的application.yml

server:
  port: 9091
​
spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
#  datasource:
#    url: jdbc:mysql://localhost:3306/mall_ams?useUnicode=true&characterEncoding
#    username: root
#    password: root

这样在导入jdbc后不需要配置datasource,但mybatis依赖于datasource,仍然报错。

开启DataSourceAutoConfiguration后报错为

  • Error creating bean with name 'adminMapper' defined in

关闭DataSourceAutoConfiguration后报错为

  • Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured

同理也可以在@SpringBootApplication上关闭

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

当我们没有配置的时候,默认就是开启自动配置的

查看顺序:this.getAutoConfigurationEntry >>> this.getCandidateConfigurations >>> SpringFactoriesLoader.loadFactoryNames >>> this.loadFactoryNames >>> this.loadSpringFactories

核心方法getAutoConfigurationEntry

接下里继续解析selectImports方法里面的this.getAutoConfigurationEntry,这个是核心方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 判断是否开启自动配置
   if (!isEnabled(annotationMetadata)) {
       //如果没有返回一个空的AutoConfigurationEntry
      return EMPTY_ENTRY;
   }
   // 获取@EnableAutoConfiguration注解的属性
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 重点:使用SpringFactoriesLoader从所有的spring.factories文件中获取需要配置的bean全限定名列表,
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 去重
   configurations = removeDuplicates(configurations);
   // 获取注解中exclude或excludeName排除的类集合
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   // 检查被排除类是否可以实例化,是否被自动配置所使用,否则抛出异常
   checkExcludedClasses(configurations, exclusions);
   // 去除被排除的类
   configurations.removeAll(exclusions);
   // 使用spring.factories配置文件中配置的过滤器对自动配置类进行过滤
   configurations = getConfigurationClassFilter().filter(configurations);
   // 抛出事件
   fireAutoConfigurationImportEvents(configurations, exclusions);
    //将需要的自动配置 和 被排除的自动配置,一同封装到AutoConfigurationEntry对象
   return new AutoConfigurationEntry(configurations, exclusions);
}

List<String> configurations 包含所有需要被自动配置的类的,是一个全限定路径名称。

configurations 包含的类在随后都将被注入到IOC容器,完成自动配置。

List<String> configurations结构 如图:

 

继续深入,很明显,该方法getAutoConfigurationEntry的重点方法是this.getCandidateConfigurations方法,我们来分析一下

    /**
     * Return the auto-configuration class names that should be considered. By default
     * this method will load candidates using {@link SpringFactoriesLoader} with
     * {@link #getSpringFactoriesLoaderFactoryClass()}.
     * @param metadata the source metadata
     * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
     * attributes}
     * @return a list of candidate configurations
     */
    protected List<String> getCandidateConfigurations(
        AnnotationMetadata metadata,AnnotationAttributes attributes) {
        //调用SpringFactoriesLoader.loadFactoryNames,获得自动配置的信息
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
            getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());
        
        //如果自动配置的信息是否为空,则抛出异常
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct.");
        
        return configurations;
    }

什么是AnnotationMetadata?

AnnotationMetadata用来访问指定类上的注解。

AnnotationMetadata 存在两个实现类分别为 StandardAnnotationMetadataAnnotationMetadataReadingVisitor。前者主要使用 Java 反射原理获取元数据,而 后者 使用 ASM 框架获取元数据。

 

FactoryNames

继续深入SpringFactoriesLoaderload类的FactoryNames方法

    /**
     * Load the fully qualified class names of factory implementations of the
     * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
     * class loader.
     * <p>As of Spring Framework 5.3, if a particular implementation class name
     * is discovered more than once for the given factory type, duplicates will
     * be ignored.
     * @param factoryType the interface or abstract class representing the factory
     * @param classLoader the ClassLoader to use for loading resources; can be
     * {@code null} to use the default
     * @throws IllegalArgumentException if an error occurs while loading factory names
     * @see #loadFactories
     */
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

该方法大概是:使用指定的类加载器从所有 “META-INF/spring.factory”路径下把EnableAutoConfiguration对应的的Bean值添加到容器中

具体的实现在下方的this.loadSpringFactories。

总之AutoConfigurationEntry类用于返回导入@Configuration类的AnnotationMetadata。

从 Spring Framework 5.3 开始,如果多次发现给定工厂类型的特定实现类名,则重复项将被忽略。

什么是spring.factories?

spring.factories是一个Spring Boot提供的SPI机制,用于在类路径中的多个JAR文件中加载和实例化特定类型的工厂类。spring.factories文件必须是Properties格式,其中键是接口或抽象类的完全限定名,值是实现类名的逗号分隔列表。

=后面的路径都作为容器中的一个组件,被添加到IoC 容器中,从而实现Spring Boot 的自动配置。

列如:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration是一个类的全限定名称,可直接查看

MybatisAutoConfiguration这个相当于我们以前手动配置的mybatisConf配置类

// spring配置mybatis简单示例
public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource source){
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setTypeAliasesPackage("SpringQuickStart.MybatiesBean.domain"); //扫描路径
        sessionFactoryBean.setDataSource(source);
        return sessionFactoryBean;
}

也就是说我们以前在spring中需要编写的mybatis配置类,在我们导入mybatis依赖的时候,mybatis已经编写好,不用我们重复编写

  1. mybatis将自己编写的配置类的一个路径放入到spring.factories

  2. springBoot扫描spring.factories文件,就能找到mybatis编写的配置类具体路径

  3. 然后springBoot自动装配这些具体的配置类

但业务千变万化,配置不是完全一样的,如数据源是不确定的,还需要手动配置。

这就是Spring Boot 的约定优于配置,通俗的说,SpringBoot提供了一套默认的配置,可以直接使用,这个默认的配置不合适就自己覆盖

自动配文件一般在spring-boot-autoconfigure包下

 

如何创建spring.factories?

创建spring.factories的方法是:

  1. 在你的项目中创建一个类,用@Configuration注解标记,并定义你想要自动配置的bean。例如:

@Configuration
public class MyAutoConfiguration {
​
  @Bean
  public MyService myService() {
    return new MyService();
  }
}
  1. 在你的项目的资源目录下,创建一个META-INF/spring.factories文件,用Properties格式指定你的配置类的完全限定名,以EnableAutoConfiguration为键。例如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration
  1. 将你的项目打包成一个jar文件,并发布到Maven仓库或者其他地方。

  2. 在其他需要使用你的自动配置的项目中,添加你的jar文件作为依赖。Spring Boot会在启动时扫描spring.factories文件,并加载其中配置的类

如上,springBoot就扫描完了并且使用类加载器加载了需要被自动配置的配置类,接下里需要注入到springIOC容器

小总结:

  1. AutoConfigurationImportSelector类是ImportSelector的实现类,实现了selectImports()方法。selectImports()方法又调用getAutoConfigurationEntry()方法从spring.factories文件中读取配置类的全限定名列表,并进行过滤,最终得到需要自动配置的类全限定名列表。

  2. 随后spring根据需要自动配置的类全限定名列表,将这些自动注入到容器,完成自动配置

注册自动配置包

而@EnableAutoConfiguration注解的核心是@AutoConfigurationPackage,也就是将之前扫描的类注入到容器

/**
 * Registers packages with {@link AutoConfigurationPackages}. When no {@link #basePackages
 * base packages} or {@link #basePackageClasses base package classes} are specified, the
 * package of the annotated class is registered.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see AutoConfigurationPackages
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

该AutoConfigurationPackage注解上的前四个都是java的基本注解,不在过多解释

该注解的重点是@Import(AutoConfigurationPackages.Registrar.class)

自动注册包就在这里实现

AutoConfigurationImportSelector和AutoConfigurationPackages的区别

AutoConfigurationImportSelector 和 AutoConfigurationPackages 是 Spring Boot 自动配置的两个重要的类,它们分别用于导入自动配置类和注册自动配置包。

  • AutoConfigurationImportSelector 是一个实现了 ImportSelector 接口的类,它会根据类路径中的依赖和条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean 等)来选择合适的配置类,并将它们返回给 @Import 注解,从而导入到容器中。这些配置类都是以 AutoConfiguration 结尾的,位于 org.springframework.boot.autoconfigure 包下,或者在 META-INF/spring.factories 文件中声明。

  • AutoConfigurationPackages 是一个抽象类,它有一个内部类 Registrar,实现了 ImportBeanDefinitionRegistrar 接口,它会根据 @EnableAutoConfiguration 注解所在的类的包名,来注册一个名为 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的 BeanDefinition,该 BeanDefinition 的属性值就是包名的集合。这样,其他的自动配置类就可以通过这个 BeanDefinition 来获取需要扫描的包名。

总结1:

  1. spirngBoot依赖于@SpringBootApplication注解完成自动配置

  2. @SpringBootApplication依赖于@EnableAutoConfiguration

  3. @EnableAutoConfiguration依赖于AutoConfigurationImportSelector类,该类需要扫描所有需要被自动配置的类

    • 扫描完成后自然需要注入到ioc容器

  4. @EnableAutoConfiguration依赖于@AutoConfigurationPackage

  5. @AutoConfigurationPackage依赖于AutoConfigurationPackages.Registrar内部内

总结2:

  1. springBoot使用AutoConfigurationImportSelector类扫描在spring.factories文件中的配置类

  2. springBoot使用AutoConfigurationPackages类将之前扫描的类注入到ioc容器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值