SpringBoot自动装配原理+SpringBoot配置功能两个注解@PropertySources、@ConfigurationProperties

SpringBoot引入新的组件

①、引入依赖xxx-starter

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

②、增加一个@EnableXXX注解

@SpringBootApplication
@EnableFeignClients
public class SampleApplication{
	
	public static void main(String[] args){
		SpringApplication.run(SampleApplication.class, args);
	}
}

只需要这样,就可以将 Feign 引入项目中了,接下来根据自己的需要定义对应的 Feign client 即可

自动装配原理

Spring 从 4.x 版本开始支持 JavaConfig,让开发者可以免去繁琐的 xml 配置形式,而是使用熟悉的 Java 代码加注解,通过 @Configuration、@Bean 等注解可以直接向 Spring 容器注入 Bean 信息

自动装配的过程:

  1. 引入 META-INF/spring.factories 配置文件,在 EnableAutoConfiguration 对应的 value 中配置需要引入的配置类
  2. 启动类增加 @EnableAutoConfiguration 注解,@SpringBootApplication 已经自带
  3. @EnableAutoConfiguration 注解中通过 @Import 标注了 AutoConfigurationImportSelector 类
  4. AutoConfigurationImportSelector 继承了 DeferredImportSelector 接口,在 Spring 生命周期处理 BeanFactoryPostProcessors 的时候会对配置信息进行后置处理,这时会调用到 AutoConfigurationImportSelector.process 方法。
  5. process 方法中会读取 META-INF/spring.factories 配置文件中的内容为 Key-Value 形式,读取完后值返回 key = EnableAutoConfiguration 对应的配置类信息,保存到 autoConfigurationEntries 中
  6. AutoConfigurationGroup.selectImports 方法返回排序、筛选后的配置类信息,然后依次遍历,递归调用 processImports, 根据这些配置类的全路径名读取并注册在 Spring 容器中

每个SpringBoot启动类中,都会有一个复合注解@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Document
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration //开启自动配置
@ComponentScan(excludeFilters={@Filter(type = FilterType.CUSTOM,classes = TypeExcludeFilter.class),
	@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)	
})
public @interface SpringBootApplication{
	
}
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) //导入一些配置类
public @interface EnableAutoConfiguration{

}

ImportSelector

导入选择器

public interface ImportSelector{
	//返回需要导入的配置类的全路径名
	String[] selectImport(AnnotationMetadata importingClassMetadata);
}

在 Spring 容器启动的过程中会调用:

  • invokeBeanFactoryPostProcessors,然后会执行一个重要的后置处理器
  • ConfigurationClassPostProcessor ,完成配置类的解析,这里会处理 ImportSelector 返回的这些类,将其加载到容器中

DeferredImportSelector

继承了ImportSelector,用于延迟导入。
在有多个 DeferredImportSelector 实现类的情况下,可以继承 Ordered 实现排序的效果。

public interface DeferredImportSelector extends ImportSelector {

   @Nullable
   default Class<? extends Group> getImportGroup() {
      return null;
   }

   interface Group {
   }
}

AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,所

在执行 ConfigurationClassParser.processImports()方法的时候,最终会调用到下面这段逻辑。一般继承 ImportSelector 会执行其 selectImport 方法。但是这里不同的是,它还继承了 DeferredImportSelector 接口,对 ImportSelector 只是间接继承。在 processImports() 方法中有这样的额外判断,如果是 DeferredImportSelector 的子类,将会执行 deferredImportSelectorHandler.handle(),最终会回调 AutoConfigurationImportSelector 的 process 方法。具体的调用过程请见下图。
在这里插入图片描述

读取配置

在 process() 方法中主要做了一件事,读取并解析 spring.factories 配置文件中的信息,将这些配置文件对应的全路径类名都放入 AutoConfigurationEntry 集合中。

getAutoConfigurationMetadata() 方法读取并解析了 spring-autoconfigure-metadata.properties 文件,用于控制自动装配条件。关于这个路径信息,追踪方法可以找到,比较简单。

protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

getAutoConfigurationEntry() 方法会获取需要自动装配的类和需要排除的类,读取的文件是 META-INF/spring.factories

loadSpringFactories 方法中,会加载 META-INF/spring.factories 路径下的配置内容,并且这个路径是硬编码写死的。在全部读取完毕之后,会放在一个 Map 中,key 为类名,value 为对应的自定义配置类。getSpringFactoriesLoaderFactoryClass() 方法会固定返回 EnableAutoConfiguration.class,所以这里只会返回 EnableAutoConfiguration 对应的配置内容,配置文件内容如下图。
在这里插入图片描述

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
   ……
   // getAutoConfigurationMetadata() 方法读取并解析了 spring-autoconfigure-metadata.properties 文件,用于控制自动装配条件
   // AutoConfigurationEntry 方法会获取需要自动装配的类和需要排除的类
   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
   // 添加到 AutoConfigurationEntry集合中等待加载
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}

// 返回自动配置的类名,加载Spring.factories中的配置信息
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   // loadFactoryNames 会读取对应的配置文件,位置在META-INF/spring.factories中
  // getSpringFactoriesLoaderFactoryClass 返回 EnableAutoConfiguration.class
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
  ……
   return configurations;
}

具体执行方法的调用链路如下:
在这里插入图片描述

加载配置

回到之前执行过程中的processGroupImports 方法(在前面的图片已用红框标注了出来),这里会调用 getImports 拿到配置类信息,然后再次调用类信息,然后递归调用 processImports,这个方法之前的文章已经解释过了,如果是配置类会解析并注册

public void processGroupImports() {
   for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
     // getImports 得到解析后的类名
      grouping.getImports().forEach(entry -> {
         ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
         try {
           // 再次调用 processImports 递归处理,对配置类解析,注册为 Bean
            processImports(configurationClass, asSourceClass(configurationClass),
                  asSourceClasses(entry.getImportClassName()), false);
         }
         catch (BeanDefinitionStoreException ex) {
           ……
         }
      });
   }
}

另外,再额外说一下 getImports 方法。之前 process 方法并没有返回值,而是把配置信息都保存在了 autoConfigurationEntries 中,所以在执行完 process 之后会紧接着执行 selectImports()。它的功能主要是排除需要排除的类信息,并且在这里按照 spring-autoconfigure-metadata.properties 中指定的顺序排序,然后再返回类信息。

public Iterable<Group.Entry> getImports() {
   for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
   // 调用 process 逻辑
      this.group.process(deferredImport.getConfigurationClass().getMetadata(),
            deferredImport.getImportSelector());
   }
   return this.group.selectImports();
}

public Iterable<Entry> selectImports() {
 if (this.autoConfigurationEntries.isEmpty()) {
  return Collections.emptyList();
 }
 // 获取所有需要排除的类集合
 Set<String> allExclusions = this.autoConfigurationEntries.stream()
   .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
 // 获取所有需要装配的类集合
 Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
   .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
   .collect(Collectors.toCollection(LinkedHashSet::new));
 // 移除所有排除类
 processedConfigurations.removeAll(allExclusions);
 // 将需要加载的类排序返回,排序规则按照 spring-autoconfigure-metadata.properties 中指定的顺序
 return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
   .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
   .collect(Collectors.toList());
}

SpringBoot 的强大配置功能

一、@ConfigurationProperties

用于将配置文件中的属性值绑定到 Java Bean 上,通常与 @Configuration 或 @Component 一起使用

①、该类的属性将映射到配置,存储和操作配置属性的值

@Configuration
@ConfigurationProperties(prefix = "myapp")//将以 myapp 前缀开头的属性映射到 MyAppProperties 类的字段中
public class MyAppProperties{
	
	private String name;
	private String version

	//get、set省略
}

②、配置类,加载配置属性,将其绑定到POJO类

@Configuration
@EnableConfigurationProperties("MyAppProperties.class")//启用对 MyAppProperties 类的配置属性绑定
public class AppConfig{

}

③、配置文件

在 application.properties 或 application.yml 文件中,添加与您的配置属性相关的属性键和值。确保它们以与 @ConfigurationProperties 注解中指定的前缀匹配的方式进行命名。

myapp:
	name: My Spring Boot App
	version: 1.0

④、透过构造函数或使用@Autowired注解,注入配置属性

@Service
public class MyService{
	
	private final MyAppProperties myAppProperties;

	@Autowired
	public MyService(MyAppProperties myAppProperties) {
        this.myAppProperties = myAppProperties;
    }

	public void doSomething(){
	
		String appName = myAppProperties.getName();
        String appVersion = myAppProperties.getVersion();
        
        // 使用配置属性进行操作
        System.out.println("App Name: " + appName);
        System.out.println("App Version: " + appVersion);
	}
}

二、@PropertySources

  • 指定多个属性源: @PropertySources 允许您在一个配置类中指定多个属性源,每个属性源对应一个属性文件。这些属性源可以包含应用程序的配置信息,如数据库连接参数、日志级别、服务端口等。
  • 模块化配置: 通过将配置信息分散在多个属性文件中,您可以将应用程序的配置模块化,使每个模块负责自己的配置。这使得代码更易于维护和理解,特别是在大型应用程序中。
  • 环境适应性: 您可以为不同的环境(如开发、测试、生产)或不同的配置需求定义不同的属性源。这使得应用程序能够在不同环境中使用不同的配置,而不必修改代码。
  • 属性源优先级: 如果存在多个属性源,Spring 将按照它们在 @PropertySources 注解中的顺序来解析属性。这意味着后面的属性源可以覆盖前面的属性源中的属性值。

通常与 @Configuration 一起使用,以将多个属性源引入 Spring 应用程序的环境中。

①、创建配置类

@Configuration
@PropertySources(
	{
		@PropertySource("classPath:application.properties"),// 默认属性源
		@PropertySource("classpath:custom.properties")// 自定义属性源
	}
)
public class AppConfig{
	
	// 这里可以注入和使用从属性文件加载的配置属性
}

②、配置文件 default.properties 和 custom.properties

app.name=My Spring Boot App (Default)
app.version=1.0 (Default)
app.name=My Custom Spring Boot App

③、业务服务类

@Service
public class MyService{
	
	@Value("${app.name}")
	private String appName;

	@Value("${app.version}")
    private String appVersion;

    public String getAppInfo() {
        return "App Name: " + appName + ", App Version: " + appVersion;
    }
}

④、业务接口

@RestController
public class MyController {

    private final MyService myService;

    @Autowired
    public MyController(MyService myService) {
        this.myService = myService;
    }

    @GetMapping("/app-info")
    public String getAppInfo() {
        return myService.getAppInfo();
    }
}

启动应用程序,并访问 /app-info 路径,您将看到根据不同属性源获取的应用程序信息。
在 Spring Boot 中,通常更常见的做法是使用默认的 application.properties(或 application.yml)文件以及 @ConfigurationProperties 注解来管理配置属性。

两者区别:

  • @PropertySources 主要用于定义属性源,将配置信息存储在不同的属性文件中,并在不同环境或模块之间共享配置。它不直接与 Java Bean 绑定。
  • @ConfigurationProperties 主要用于将配置属性绑定到 Java Bean 上。它通常与 @Configuration 或 @Component 注解一起使用,以将外部属性文件中的属性值映射到一个特定的 Java Bean。
  • 通常情况下,您会使用 @PropertySources 来定义属性源,然后使用 @ConfigurationProperties 来将属性源中的属性值绑定到 Java Bean,以便在整个应用程序中使用这些属性。这两个注解可以协同工作,但它们的功能不同,各自有其用途。
  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值