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

相关推荐
程序员阿卢2 小时前
01-基于springboot框架调用ollama下的模型完成基本功能
spring boot·后端·ollama·通义千问模型qwen
烧饼Fighting3 小时前
Jenkins自动化编译部署Spring Boot项目
spring boot·自动化·jenkins
闪电悠米3 小时前
黑马点评-Redis 消息队列-01_why_redis_mq
java·数据库·spring boot·redis·缓存·junit·消息队列
我登哥MVP3 小时前
Spring Boot 从“会用”到“精通”:内容协商原理
java·spring boot·后端·spring·java-ee·maven·lua
Flittly4 小时前
【AgentScope Java新手村系列】(1)框架简介与环境搭建
java·spring boot·笔记·spring·ai
星辰徐哥12 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥12 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约12 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee12 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐12 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统