SpringBoot自动配置原理

文章目录

问题引入

SpringBoot最核心的一个东西就是自动配置,自动配置的原理是什么?

我们知道Spring最开始是通过xml文件配置,来告诉Spring要创建哪些类、哪些Bean。

后来进化为可以通过注解来配置,这样就不用去写xml文件了,只需要告诉Spring去扫描哪些包就可以了。

那么问题来了,如果是一个三方服务框架包,我怎么知道要去扫描哪些包呢?

很多时候我们还是只能通过@Bean的方式去创建,一些复杂的服务可能需要创建很多Bean,我们怎么知道要创建哪些Bean呢?这些Bean的关联关系又是怎样的呢?

用起来太麻烦了,很多时候,我们并不想知道实现的具体细节,我们只希望拿来就能用,怎么办呢?

于是:SpringBoot来了,它可以自动完成配置,创建并注入需要的Bean,不需要我们自己去创建了,若有的创建Bean的工作都由提供服务的服务者去创建,他们自己实现的,当然更清楚怎么用,怎么创建系列Bean。

那么,问题又来了,SpringBoot是怎么做到自动配置的呢?

答案是:类Java SPI机制。

Java SPI机制

一个好的语言、框架设计时候一定要考虑好扩展,做好抽象层。

例如Java的数据库驱动就是一个好的例子,我们使用的都是java.sql包下的抽象层接口,根本不用关心是哪种数据库,实现细节是什么。

一个不好的例子,就是Java的日志功能,所以抽象做得更好的slf4j-api基本就成了共用接口标准。

当然,技术在不断发展,很多时候,我们再设计的时候并不能考虑到那么多,例如响应式编程,那也是发展了很多年,才有projectreactor

Java的数据库驱动为什么能让我们不用关心具体实现呢,答案就是:SPI(Service Provider Interface)。

Java SPI核心类是:java.util.ServiceLoader

它会去扫描classpath下的META-INF/services目录下的文件,文件名是抽象层的全限定名,例如java.sql.Driver,文件内容是服务提供的具体实现类。

如:com.mysql.cj.jdbc.Driver

@EnableAutoConfiguration

Java SPI的核心类是ServiceLoader,SpringBoot自动配置的核心类是@EnableAutoConfiguration。

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

EnableAutoConfiguration它通过@Import(AutoConfigurationImportSelector.class)自动装配了AutoConfigurationImportSelector类。

java 复制代码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}

AutoConfigurationImportSelector实现了DeferredImportSelector接口,DeferredImportSelector又实现了ImportSelector。

ImportSelector#selectImports就可以告诉Spring这些类需要被导入。

那AutoConfigurationImportSelector会去哪里找需要自动装配的类呢?

顺着接口selectImports找呗,有兴趣可以自己去跟源码,这里简单总结一下:

  1. ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
  2. 找的是AutoConfiguration类
  3. META-INF/spring/要找的类的全限定名称.imports文件
  4. 所以:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  5. 上面的文件配置的类,每行一个全部导入

还有一个ImportAutoConfigurationImportSelector,是处理ImportAutoConfiguration。

除了直接导入的,还有通过org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames导入,找的是META-INF/spring.factories文件。

spring.factories这个文件中的内容类似properties,是key-value形式,key是接口全限定名,value是具体实现类全限定名,多个逗号分隔。

对于自动导入来说,具体的接口是org.springframework.boot.autoconfigure.EnableAutoConfiguration。

例如:

java 复制代码
@Import({DruidSpringAopConfiguration.class,
        DruidStatViewServletConfiguration.class,
        DruidWebStatFilterConfiguration.class,
        DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {}

对过对于EnableAutoConfiguration,还是推荐使用新的规范,仍在META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中,这样不用配置key,也不用考虑多行的问题,直接每行一个就行。

实现自己的EnableMyAutoConfiguration

知道了原理,我们可以动手实现一个我们自己的EnableMyAutoConfiguration。

首先是Enable注解:

java 复制代码
import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyAutoConfigurationImportSelector.class)
public @interface EnableMyAutoConfiguration {
}

然后是,我们需要处理什么注解呢?我们自定义的MyAutoConfiguration

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutoConfiguration {
}

接下来,我们需要一个DeferredImportSelector来加载配置的MyAutoConfiguration类。

java 复制代码
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;

import java.util.List;

public class MyAutoConfigurationImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        List<String> configurations = ImportCandidates.load(MyAutoConfiguration.class, this.getClass().getClassLoader())
                .getCandidates();
        return configurations.toArray(new String[0]);
    }
}

使用了@MyAutoConfiguration的配置类:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;

@MyAutoConfiguration
@Import(BzClass.class)
public class ConfigurationA {

    @Bean
    public String name(){
        return "ConfigurationA";
    }
}

配置类中导入了具体的业务类:

java 复制代码
public class BzClass {
}

然后我们把配置类vip.oschool.custom.ConfigurationA,配置到META-INF\spring\vip.oschool.custom.MyAutoConfiguration.imports文件中。

然后,来一个启动类:

java 复制代码
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;

@EnableMyAutoConfiguration
public class Main {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("main", Main.class);
        // ConfigurationClassPostProcessor处理DeferredImportSelector
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.refresh();
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
    }
}

结果,我们可以看到,所有的配置类都被加载了。

starter

知道了自动配置原理,再来看starter就想吃饭喝水一样简单了。

通常我们自己的state人的artifactId是xxxx-spring-boot-starter,spring自己的stater是spring-boot-starter-xxxx

druid-spring-boot-starter就是把配置类添加到了spring.factories

DruidDataSourceAutoConfigure创建了DruidDataSourceWrapper,又通过@Import导入DruidSpringAopConfiguration等实际业务类。

所以,我们什么都不用管,只需要把druid-spring-boot-starter添加入依赖就可以用了,因为DruidSpringAopConfiguration都帮我们创建好了。

当然,关联的依赖也在druid-spring-boot-starter的pom.xml中导入了。

还有一些更符合Spring模式的,例如,会单独有一个autoconfigure项目来管理自动配置。

自动配置的类配置在META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。

相关推荐
Java水解3 小时前
springboot: Spring Boot 启动流程详解
spring boot·后端
Craaaayon3 小时前
深入浅出 Spring Event:原理剖析与实战指南
java·spring boot·后端·spring
Kings904 小时前
Spring-AI 结合自定义 mcp server 实现飞书智能机器人
spring boot·ai编程·mcp
qq_12498707534 小时前
基于springboot的智能任务管理助手小程序设计与实现(源码+论文+部署+安装)
spring boot·后端·信息可视化·微信小程序·小程序·毕业设计·计算机毕业设计
计算机学姐5 小时前
基于SpringBoot的智能家教服务平台【2026最新】
java·spring boot·后端·mysql·spring·java-ee·intellij-idea
ZLZQ_Yuan5 小时前
Spring Boot JPA
spring boot
SadSunset6 小时前
(44)Spring6集成MyBatis3.5(了解即可,大部分用springboot)
java·spring boot·后端
fanruitian6 小时前
SpringBoot 调用springai ollama
java·spring boot·后端