springboot 自动装配

思考

springboot 项目启动时,会自动扫描启动类所在包,将对应的 bean 加载到 ioc 容器。
如果配置的 bean 不在启动类的目录,或者在别的jar包中那?
在项目中创建配置类,导入 bean 。
这种的方式当然可以啦,可是如果我们有大量的 bean 要配置,且有多个项目的话,就需要每个项目都写对应的 bean 的配置类,造就大量无用功。怎么才能减少这部分无用功那?
项目在使用外部 bean 时需要配置类才能导入 ioc 容器,如果我们能把这部分 bean 配置类抽取出来,不就可以减少无用功了嘛。
因此,项目提供 bean 的配置类接口,bean 的配置类实现部分可以放在 bean 的提供者,即 bean 的提供者既提供 bean,也提供 bean 的配置类。(Java SPI 机制)
springboot 自动配置就实现了这些操作

springboot 自动配置原理

springboot 的启动类上的配置了 SpringBootApplication 注解,该注解组合了EnableAutoConfiguration 注解,EnableAutoConfiguration 注解又组合了 Import 注解,Import 注解指定 AutoConfigurationImportSelector 类,当容器运行时,会执行该类的 selectImpots 方法,读取 META-INF/spring.factories 文件(springboot 2.7版本后使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports),获取自动配置类的全限定类名列表,加载符合 condition 注解和其子注解条件的 bean 到 ioc 容器

源码分析(基于 springboot 2.7.15)

AutoConfigurationImportSelector 部分代码

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		// 如果没有启用自动配置,返回空的数组
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

ioc 容器运行时调用 selectImports 方法,获取自动配置类数组。该方法执行时调用了 getAutoConfigurationEntry 方法

	protected 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 AutoConfigurationEntry(configurations, exclusions);
        }
    }

getAutoConfigurationEntry 方法内部调用 getCandidateConfigurations 方法,获取自动配置类的全限定类名列表,并进行去重和排除

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		// 通过SpringFactoriesLoader读取META-INF/spring.factories文件
		List<String> configurations = new ArrayList<>(
				SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
		// 通过ImportCandidates读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
		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;
	}

spingboot 2.7版本以前,getCandidateConfigurations 方法通过 SpringFactoriesLoader 类的 loadFactoryNames 方法读取类路径下的所有 META-INF/spring.factories 文件,获取自动配置类。2.7版本以后虽然不推荐使用了,但也没有舍弃,而是对其作了兼容,2.7版本后使用ImportCandidates 类的 load 方法读取类路径下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件

spring.factories 和 org.springframework.boot.autoconfigure.AutoConfiguration.imports 相当于 springboot 对外提供的自动配置接口。因此,当外部的 bean 需要在 springboot 项目运行时自动加载到 ioc 容器中,只需把其配置类放到 spring.factories 或 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中即可

创建自定义 starter (基于 spring.factories 实现)

  1. 创建 auto-configuration 模块,引入 spring-boot-autoconfigure 依赖
    pom.xml 部分依赖
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
  1. 编写 MyUser 类和 MyUserConfig 类
    MyUser 类
package com.wal.bean;

public class MyUser {

    private String id;

    private String name;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "MyUser{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

MyUserConfig 类

package com.wal.config;

import com.wal.bean.MyUser;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

@AutoConfiguration // 声明为自动配置类
@ConditionalOnClass(MyUser.class) // 条件判断,存在MyUSer的字节码时才加载
public class MyUserConfig {

    @Bean
    @ConditionalOnMissingBean(MyUser.class) // 条件判断,不存在MyUser的bean时才加载
    public MyUser myUser() {
        MyUser user = new MyUser();
        user.setId("1001");
        user.setName("wang");
        return user;
    }
}

  1. 在 resources 目录创建 META-INF/spring.factories 文件,指定配置类MyUserConfig
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wal.config.MyUserConfig
  1. 创建 starter 模块,引入 spring-boot-starter 和 自定义的 auto-configuration 模块依赖(starter 模块主要用来依赖控制)
    pom.xml 部分依赖
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- 引入 auto-configuration  -->
        <dependency>
            <groupId>com.wal</groupId>
            <artifactId>auto-configuration</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- 引入 auto-configuration 模块导入的全部依赖 -->
         <dependency>
            <groupId>xxx</groupId>
            <artifactId>xxx</artifactId>
            <version>xxx</version>
        </dependency>
  1. 在所需项目引入自定义 stater 依赖,启动项目,查看 MyUser 是否被自动注入 ioc 容器
    pom.xml 部分依赖
 <dependency>
            <groupId>com.wal</groupId>
            <artifactId>my-spring-boot-starter</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

启动类部分代码

@SpringBootApplication
public class UserServiceApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(UserServiceApplication.class, args);
        // 获取 MyUser 的 bean,看是否注入成功
        Object myUser = context.getBean("myUser");
        System.out.println(myUser);
    }
}

效果
在这里插入图片描述
成功从 ioc 容器获取到 MyUser 实例
证明自定义的 stater 成功将 MyUser 注入到 ioc 容器中,而我们所做的只是在项目引入自定义 starter 的依赖

若有不足,欢迎指出,开发之路,与君共勉!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值