SpringBoot的自动装配原理及应用

什么是SpringBoot自动装配

所谓的“SpringBoot自动装配”就是指:通过注解和一些简单的配置就能将某些组件载入Spring容器环境中,便于使用。
比如,很多spring-boot-starter组件只要简单引入,然后在SpringBoot的配置文件application.propertiesapplication.yml中添加对应的参数配置就可以使用了,非常方便。
实际上,“自动装配”机制是Spring Boot定义的一个规范:SpringBoot在启动时会扫描外部引用jar包中的META-INF/spring.factories文件,将文件中配置的类信息加载到Spring容器,并执行类中定义的各种操作。对于外部jar来说,只需要按照SpringBoot定义的标准,就能将自己的功能装置进SpringBoot。

那么,这种“自动装配”机制具体是如何实现的呢?

SpringBoot自动装配实现机制

其实,这一切都要从Spring Boot的核心注解@SpringBootApplication说起。

// 注解@SpringBootApplication的定义
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {}

// 注解@SpringBootConfiguration的定义,本质上就是@Configuration注解的包装
@Configuration
public @interface SpringBootConfiguration {}

// 注解@EnableAutoConfiguration的定义
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

如上,从注解@SpringBootApplication的定义来看,本质上它是@ConfigurationEnableAutoConfiguration@ComponentScan这三个注解的组合,它们的含义分别是:

  • @Configuration:用于在上下文中注册额外的Bean或导入其他配置类
  • @EnableAutoConfiguration:启用Spring Boot的自动装配机制
  • @ComponentScan:扫描被@Component@Service@Controller等注解的Bean,默认会扫描启动类所在包及其子包下的所有类,可以自定义不扫描某些类

所以,注解@EnableAutoConfiguration才是实现Spring Boot自动装配的关键。

在注解@EnableAutoConfiguration中通过@Import导入了AutoConfigurationImportSelector类,在这个类中真正实现了从外部jar包的META-INF/spring.factories文件中读取配置的类信息。
具体来说,是在getCandidateConfigurations()方法中实现读取配置类的。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 调用SpringFactoriesLoader.loadFactoryNames()方法从文件META-INF/spring.factories中读取配置信息类
    List<String> configurations = new ArrayList<>(
            SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations,
            "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

调用关系如下图:

那么,通过AutoConfigurationImportSelector读取的配置信息类在哪里使用了呢?
实际上,这个需要跟SpringBoot的启动流程关联起来,经过代码追踪可以知道,调用AutoConfigurationImportSelector.getCandidateConfigurations()方法读取外部文件中的配置类这个操作是在IoC容器的refresh()方法中触发的。也就是说,在IoC容器启动的时候通过调用getCandidateConfigurations()方法把外部文件中指定的类读取进来,然后再使用反射机制将它们实例化成为Bean对象载入到IoC容器中。
SpringBoot的启动时序图如下所示:

如何编写Starter组件

既然弄明白了SpringBoot的自动装配机制是什么,即可以很方便地实现一个starter组件了。
如下示例展示在一个自定义的starter组件中定义一个访问Redis的客户端组件RedissonClient

新建一个artifactId为redisson-spring-boot-starter的项目,添加依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.chench.extra.spring.boot</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
  <version>1.0.0</version>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.3.1.RELEASE</version>
      <!-- 禁止传递依赖 -->
      <optional>true</optional>
    </dependency>

    <dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>3.13.1</version>
    </dependency>

    <!-- 配置参数提示,需加此依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <version>2.3.1.RELEASE</version>
    </dependency>

  </dependencies>
</project>

新一个保存配置参数信息的类RedissonProperties,用于从Spring Boot配置文件中加载以auto.redisson为前缀的参数。

// 从SpringBoot配置文件中读取以`auto.redisson`为前缀的参数,如:auto.redisson.host=127.0.0.1
@ConfigurationProperties(prefix = "auto.redisson")
public class RedissonProperties {
    private String host = "localhost";
    private int port = 6379;
    private int timeout = 1000;
    private boolean ssl = false;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public boolean isSsl() {
        return ssl;
    }

    public void setSsl(boolean ssl) {
        this.ssl = ssl;
    }
}

新建一个创建RedissonClient对象的配置类:

@ConditionalOnClass(Redisson.class) // 使用条件注解,只有当依赖了Redisson时才加载到容器
@EnableConfigurationProperties(RedissonProperties.class) // 加载配置参数类
@Configuration // 这是一个配置类
public class RedissionAutoConfiguration {
    @Bean // 实例化RedissonClient对象
    public RedissonClient redissonClient(RedissonProperties redissonProperties) {
        Config config = new Config();
        String prefix = redissonProperties.isSsl() ? "rediss://" : "redis://";
        String host = redissonProperties.getHost();
        int port = redissonProperties.getPort();
        int timeout = redissonProperties.getTimeout();
        config.useSingleServer()
                .setAddress(prefix + host + ":" + port)
                .setTimeout(timeout);
        return Redisson.create(config);
    }
}

根据SpringBoot自动装配的规范要求,需要在文件META-INF/spring.factories文件添加需要自动装配的类。
所以新建文件src\main\resources\META-INF\spring.factories,在文件中添加自动装配的类信息:

# 自动装配的类可以是多个,用英文逗号分隔,使用\进行换行
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.chench.extra.spring.boot.redisson.RedissionAutoConfiguration

至此,一个支持在SpringBoot中进行自动装配的starter组件基本功能就开发完毕了,执行mvn clean install将项目打包安装到本地Maven仓库,然后就可以在SpringBoot项目中引入该starter组件进行使用了。
但是,如果希望在SpringBoot配置文件中添加配置参数时能进行提示,如下图:
参数提示

还需要在src\main\resources\META-INF\路径下添加一个配置文件additional-spring-configuration-metadata.json,内容如下:

{
  "properties": [
    {
      "name": "auto.redisson.host",
      "type": "java.lang.String",
      "description": "redis服务器地址.",
      "defaultValue": "localhost"
    },{
      "name": "auto.redisson.port",
      "type": "java.lang.Integer",
      "description": "redis服务器端口.",
      "defaultValue": 6379
    },{
      "name": "auto.redisson.ssl",
      "type": "java.lang.Boolean",
      "description": "是否使用ssl协议.",
      "defaultValue": false
    }, {
      "name": "auto.redission.timeout",
      "type": "java.lang.Integer",
      "description": "超时时间.",
      "defaultValue": 1000
    }
  ]
}

再另一个项目中直接引入这个自定义的starter组件使用其中定义的RedissonClient组件即可。

 <!-- 引入自定义的starter组件 -->
 <dependency>
    <groupId>org.chench.extra.spring.boot</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

在SpringBoot配置文件application.properties中添加配置参数:

auto.redisson.host=192.168.2.24
auto.redisson.port=6379
auto.redission.timeout=1000
auto.redisson.ssl=false

直接引用自定义组件中的RedissonClient组件:

// 直接依赖一个在starter中定义的Bean
@Autowired
RedissonClient redissonClient;

private void execute() {
    long now = new Date().getTime();
    this.redissonClient.getBucket("now").set(now);
}

【参考】
淘宝一面:“说一下 Spring Boot 自动装配原理呗?
Spring和SpringBoot自动装配原理
Spring Boot自动装配原理与启动过程详解

posted @ 2023-07-27 00:35  nuccch  阅读(801)  评论(0编辑  收藏  举报