SpringBoot中自动配置原理解析

 更新时间:2023年11月08日 10:52:22   作者:F & F  
SpringBoost是基于Spring框架开发出来的功能更强大的Java程序开发框架,本文将以广角视觉来剖析SpringBoot自动配置的原理,涉及部分Spring、SpringBoot源码,需要的可以参考下

前言

SpringBoot作为当前Java开发的热门框架,有绞手架之称。“约定大于配置”也一直是SpringBoot的标签,那么,SpringBoot要实现自身优势,自动配置功不可没。

一、SpringBoot自动配置是什么

SpringBoot自动配置是指在应用程序启动时,SpringBoot根据classpath路径下的jar包自动配置应用程序所需的一系列bean和组件,从而减少开发者的配置工作,提高开发效率。

二、@Import注解

在剖析SpringBoot自动配置原理之前,我们先了解一下@Import注解的使用

1. 方式一: .class方式

定义两个类A、B,并将其加入到Spring IOC容器中:

@Data
public class A{
	private Integer id=0;
	private String name="classA";
	public void print(){
		System.out.println(this.name);
	}
}

@Data
public class B{
	private Integer id=1;
	private String name="classB";
	public void print(){
		System.out.println(this.name);
	}
}

创建一个配置类,并使用@Import注解将类A、B添加到 IOC 容器中:

@Import({A.class,B.class})
@Configuration
public class ClassConfig{
​​​​​​​}

2.方式二:ImportSelector方式

该方法需要定义类来实现ImportSelector接口,并重写其中的selectImports( )方法,该方法的返回值是需要添加到IOC容器中的类的全限定类名数组:

@Data
public class C{
    private Integer id=2;
    private String name="classC";
    public void print(){
        System.out.println(this.name);
    }
}

编写类来实现ImportSelector接口:

public class ImportSelectorTest implements ImportSelector{
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetada){
        return new String[]{"C.class"};
    }
}

使用该类:

@Import({ImportSelectorTest.class})
@Configuration
public class ImportSelectorConfig{
​​​​​​​}

3.方式三:ImportBeanDefinitionRegistrar方式

定义一个类并实现ImportBeanDefinitionRegistrar接口,重写其中的registerBeanDefinitions( )方法,此方式可以自定义Bean在容器中的名称:

@Data
public class D{
	private Integer id=3;
	private String name="ClassD";
	public void print(){
		System.out.println(this.name);
	}
}

//定义类实现ImPortBeanDefinitionRegistrar接口,重写其中的registerBeanDefinitions()方法
public class ImportBeanDefinitionRegistrarTest{
	@Override
	public void registerBeanDefninitions(AnnotationMetadata annotationMetadata,BeanDefinitionRegistry registry{
		RootBeanDefinition rootBeanDefinition=new RootBeanDefinition(D.class);
		registry.registerBeanDefinition("自定义名称",rootBeanDefinition);
	}
}

//使用上面的类进行导入
@Import({ImportBeanDefinitionRegistrarTest})
@Configuration
public class ImportBeanDefinitionRegistrarConfig{

}

三、SpringBoot自动配置原理解析

为了容易分析和理解,我们在IDEA中创建一个SpringBoot项目,创建过程省略,直接跳到该项目的主配置类进行分析:

@SpringBootApplication
public class SpringBootTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootTestApplication.class, args);
    }

}

其中@SpringBootApplication注解是SpringBoot项目的重点,按住ctrl键进入其中,看到它由以下部分组成:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    //内容省略
}

我们主要关注以下几个注解:

  • @SpringBootConfiguration:标记当前类为配置类
  • @EnableAutoConfiuration:开启自动配置
  • @ComponentScan:扫描主类所在包及其子包、同级包中的Bean

1、@SpringBootConfiguration注解:标记当前类为配置类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

根据其源码可以知道,@SpringBootConfiguration注解包含@Configuration,所以其拥有@Configuration注解相似的功能,而@Configuration注解又包含@Companent注解,所以配置类也存在于IOC容器中。

2、@EnableAutoConfiguration注解:开启自动配置

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

根据其源码得出其主要由@AutoConfigurationPackages注解和@Import注解组成

@AutoConfigurationPackages注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

//其中的@Import注解导入了Registrar类,该类是AutoConfigurationPackages的静态类部类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

		/**
			根据传入的元注解信息获取所在的包,将包中组件类封装为数组进行注册
		*/
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
    }

@Import({AutoConfigurationImportSelector.class})注解

这一步就用到了@Import注解使用方式中的第二中:实现ImportSelector接口

那么AutoConfigurationImportSelector接口何时如何被执行呢?

SpringBoot 启动时会使用 ConfigurationClassParser 来解析被 @Configuration 标识的配置类, 然后再处理这个类内部被其他注解修饰的情况, 比如 @Import 注解, @ComponentScan 注解,@Bean 注解等

若发现注解中存在 @Import(ImportSelector) 的情况下,就会创建一个相应的 ImportSelector 对象,并调用其 process 方法

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
                return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
            });
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();

            while(var4.hasNext()) {
                String importClassName = (String)var4.next();
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }

        }

process方法又调用了getAutoConfigurationEntry方法:

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

getAutoConfigurationEntry方法调用了getCandidateConfigurations方法:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }

getCandidateConfigurations方法中使用了Spring Cor中的类SpringFactoriesLoader,该类的loadFactoryNames方法可以根据接口获取接口类的名称,这个方法返回的是类名的列表,loadFactoryNames方法会遍历整个springboot项目的classpath下的ClassLoader中所有jar包下的spring.factories文件。至此自动配置结束

总结

SpringBoot自动配置是SpringBoot的核心,所以了解SpringBoot的自动配置是非常有必要的,大家可以自行查找资料解释以下为什么不使用@ComponentScan注解替换@Import注解来进行类的导入

以上就是SpringBoot中自动配置原理解析的详细内容,更多关于SpringBoot自动配置的资料请关注脚本之家其它相关文章!

相关文章

  • Spring Security过滤器链加载执行流程源码解析

    Spring Security过滤器链加载执行流程源码解析

    Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。那么这个过滤器链是怎么加载和实现拦截的呢,对Spring Security过滤器链加载执行流程感兴趣的朋友一起看看吧
    2021-12-12
  • java mybatis框架配置详解

    java mybatis框架配置详解

    在本篇文章里小编给大家整理的是一篇关于java mybatis框架配置详解内容,对此有兴趣的朋友们可以参考下。
    2021-02-02
  • Spring中的PathVariable注释解析

    Spring中的PathVariable注释解析

    这篇文章主要介绍了Spring中的PathVariable注释用法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 浅析从同步原语看非阻塞同步以及Java中的应用

    浅析从同步原语看非阻塞同步以及Java中的应用

    非阻塞同步是基于冲突检测的乐观并发策略,这种乐观的并发策略使得很多线程不需要因为竞争失败直接挂起,这种同步措施称为非阻塞同步。下面我们就从硬件原语开始了解非阻塞同步,并看一看在Java中非阻塞同步的一些应用
    2021-06-06
  • springboot使用单元测试实战

    springboot使用单元测试实战

    这篇文章主要介绍了springboot使用单元测试实战,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • kafka安装部署超详细步骤

    kafka安装部署超详细步骤

    这篇文章主要介绍了kafka安装部署的详细步骤,主要应用场景是:日志收集系统和消息系统,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-11-11
  • Kotlin内存陷阱inline使用技巧示例详解

    Kotlin内存陷阱inline使用技巧示例详解

    这篇文章主要为大家介绍了Kotlin内存陷阱inline使用技巧示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • 在Spring中利用@Order注解对bean和依赖进行排序

    在Spring中利用@Order注解对bean和依赖进行排序

    在Spring框架中,@Order是一个经常被忽视但非常重要的注解,在项目开发中,当我们需要维护bean的特定顺序或者存在许多相同类型的bean时,这个注解就发挥了作用,这篇文章讲的就是如何利用@Order注解对bean和依赖进行排序,需要的朋友可以参考下
    2023-11-11
  • SpringBoot集成内存数据库Sqlite的实践

    SpringBoot集成内存数据库Sqlite的实践

    sqlite这样的内存数据库,小巧可爱,做小型服务端演示程序,非常好用,本文主要介绍了SpringBoot集成Sqlite,具有一定的参考价值,感兴趣的可以了解一下
    2021-09-09
  • java与js代码互调示例代码

    java与js代码互调示例代码

    用到java和js方法互调,在用HTML5做跨平台应用开发时经常会用到,在这里分享一些自己在实际开发过程中的用法,希望对初学者有所帮助
    2013-07-07

最新评论