背景介绍:SDK开发时,常常需要读取一些默认的配置文件,这些配置文件是由调用SDK工具包的
应用方配置。因而我们需要对这些配置文件进行读取后对SDK中的一些Bean进行初始化,体现了Bean加载顺序的重要性。
1、实体类
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.LinkedHashMap;
import java.util.Map;
@ConfigurationProperties(prefix = "zxl.test")
public class PropertiesList {
private Map<String, Properties> properties = new LinkedHashMap<>();
private String adc;
private String time1 = "60"; // 如果没有就赋默认值
private String time2 = "900"; // 如果没有就赋默认值
......
public Map<String, HccStorageProperties> getProperties() {
return properties;
}
public void setProperties(Map<String, HccStorageProperties> properties) {
this.properties = properties;
}
......
}
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Properties {
private String a;
private String b;
private String c;
......
}
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
@Configuration // 相当于xml中的Beans标签
@ConfigurationProperties(prefix = "url") // 读取配置文件中url开头的参数
@PropertySource(value = "classpath:Url.properties", ignoreResourceNotFound = true) // 读取Url.properties文件,如果没有则忽略,不报错
@Primary // 当有多个相同类型的bean时,使用@Primary来赋予bean更高的优先级
@Getter
@Setter
public class UrlProperties {
private String url1 = "地址......";
}
2、配置类
import java.util.Map;
import java.util.Objects;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import zxl.test.it.hcc.storage.TestManager;
import zxl.test.properties.Properties;
import zxl.test.properties.PropertiesList;
import zxl.test.TestImpl;
// 1、ImportBeanDefinitionRegistrar类只能通过其他类@Import的方式来加载,通常是启动类或配置类。
// 2、使用@Import,如果括号中的类是ImportBeanDefinitionRegistrar的实现类,则会调用接口方法,将其中要注册的类注册成bean。
// 3、实现该接口的类拥有注册bean的能力。
public class ImportConfig implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private PropertiesList propertiesList;
/**
* 三、服务启动时,第三个调用方法,给TestImpl类实例化注入到Bean容器中,此时propertiesList已经有值
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Properties> properties = propertiesList.getProperties();
properties.forEach((key, value) -> {
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition(); // 注册Bean的类
genericBeanDefinition.setBeanClass(TestImpl.class); // 设置自定义Bean的类
ConstructorArgumentValues cav = new ConstructorArgumentValues();
cav.addGenericArgumentValue(value);
genericBeanDefinition.setConstructorArgumentValues(cav); // 设置自定义Bean的值
String beanName = TestManager.getName(key); // 设置自定义Bean在Bean容器中的唯一名称
registry.registerBeanDefinition(beanName, genericBeanDefinition); // 注册自定义Bean到Bean容器中
});
}
/**
* 一、服务启动时,最先调用这个方法,给propertiesList绑定值
*/
@Override
public void setEnvironment(Environment environment) {
propertiesList = Binder.get(environment) // 获取到环境的Bingder
// 读取配置文件中对应前缀的值,并绑定到PropertiesList中
.bind(getPropertiesPrefix(PropertiesList.class), PropertiesList.class)
.orElse(new PropertiesList()); // 绑定失败则取默认值,重新new实例
}
// 获得PropertiesList配置类中ConfigurationProperties注解中读取的参数前缀
private String getPropertiesPrefix(Class<?> tClass) {
return Objects.requireNonNull(AnnotationUtils.getAnnotation(tClass, ConfigurationProperties.class)).prefix();
}
}
import java.util.Objects;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotatedTypeMetadata;
import zxl.test.properties.PropertiesList;
// 实现了Condition接口必定重现matches方法,目的是为了进行Bean注入时的条件判断
public class TestCondition implements Condition {
/**
* 二、随后调用这个方法,确定@Conditional(value = TestCondition.class)是否注入Bean
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return Binder.get(context.getEnvironment()) // 获取到环境的Bingder
.bind(getPropertiesPrefix(PropertiesList.class), PropertiesList.class) // 将PropertiesList绑定到环境中
.isBound(); // 绑定成功则为true,失败为false
}
// 获得PropertiesList配置类中ConfigurationProperties注解中读取的参数前缀
private String getPropertiesPrefix(Class<?> tClass) {
return Objects.requireNonNull(AnnotationUtils.getAnnotation(tClass, ConfigurationProperties.class)).prefix();
}
}
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import zxl.test.TestManager;
import zxl.test.properties.PropertiesList;
import zxl.test.properties.UrlProperties;
import zxl.test.TestService;
@Configuration // 相当于xml中的Beans标签
// 让使用了 @ConfigurationProperties 注解的类生效,并且将该类注入到 IOC 容器中,交由 IOC 容器进行管理
@EnableConfigurationProperties({PropertiesList.class, UrlProperties.class})
@Import(ImportConfig.class) // 此处作用为导入ImportBeanDefinitionRegistrar的实现类
public class ManagerConfiguration {
/**
* 四、将TestService注入Bean容器
*/
@Bean
@Conditional(value = TestCondition.class) // 只有实现了Condition接口并重写的matches方法返回为true才注入Bean容器
public TestService testService() {
return new TestService();
}
/**
* 五、HccStorageManager注入Bean容器
*/
@Bean
@Primary
@Conditional(value = TestCondition.class) // 只有实现了Condition接口并重写的matches方法返回为true才注入Bean容器
@ConditionalOnBean(value = TestService.class) // 只有Bean容器中已经存在TestService的Bean实例,才会注入Bean容器
public TestManager testManager(PropertiesList propertiesList) {
return new TestManager(propertiesList);
}
}
3、整体Bean初始化顺序
(1)ImportConfig类实现了EnvironmentAware接口
首先调用setEnvironment()方法,利用Binder类将配置文件中的zxl.test开头的数据绑定到PropertiesList类中。
(2)TestCondition类实现了Condition接口
再调用matches()方法,利用Binder类判断是否将配置文件中的zxl.test开头的数据绑定到PropertiesList类中,绑定成功则返回true,失败则返回false。
(3)ImportConfig类实现了ImportBeanDefinitionRegistrar接口
再调用registerBeanDefinitions()方法实现自定义Bean注入到IOC容器中。
(4)ManagerConfiguration类
有**@Configuration注解(自定义Bean注册完后,开始执行该注解相关的Bean注册),@EnableConfigurationProperties({PropertiesList.class, UrlProperties.class})注解(让使用了@ConfigurationProperties** 注解的类生效,并且将该类注入到 IOC 容器中,交由 IOC 容器进行管理),**@Import(ImportConfig.class)**注解(导入ImportBeanDefinitionRegistrar的实现类)。
标有**@Bean注解的进行Bean注册到IOC容器,根据@Conditional(value = TestCondition.class)注解和@ConditionalOnBean(value = TestService.class)注解判断该Bean是否注入,@Primary注解则表示当有多个相同类型的Bean**时,使用@Primary来赋予bean更高的优先级。