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文件。

相关推荐
swg32132120 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
gelald20 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
@yanyu66620 小时前
07-引入element布局及spring boot完善后端
javascript·vue.js·spring boot
程序猿_极客1 天前
SpringBoot 三大参数注解详解:@RequestParam @RequestBody @PathVariable 区别及常用开发注解
java·spring boot·后端·面试八股文·springboot注释
小胖java1 天前
校园通衢公告枢纽系统
java·spring boot
Hadoop_Liang1 天前
构建Spring Boot项目Docker镜像
spring boot·后端·docker
Flittly1 天前
【SpringAIAlibaba新手村系列】(14)MCP 本地服务与工具集成
java·spring boot·笔记·spring·ai
Flittly1 天前
【SpringAIAlibaba新手村系列】(13)Tool Calling 函数工具调用技术
java·spring boot·spring·ai
my_styles1 天前
linux系统下安装 tengine / 宝兰德等国产信创中间件和闭坑
linux·运维·服务器·spring boot·nginx·中间件
coder阿龙1 天前
基于SpringAI+Qdrant+Ollama本地模型和向量数据库开发问答和RAG检索
java·数据库·spring boot·ai·数据库开发