spring中访问配置的几种方式
本地配置文件读取
- Environment#getProperty
环境变量包括:system properties、-D参数、application.properties(.yml)、通过@PropertySource引入的配置文件配置,它的原理其实就是从内存中取出PropertySource列表,遍历取配置,可以查看AbstractEnvironment.getProperty方法源码。
- 直接读取properties本地文件
- @Value,配合@PropertySource
- @ConfigurationProperties,配合@PropertySource
示例代码
针对上面的4种方式基于springboot项目做的示例代码。
配置注入原理分析
spring-beans包下面有一个AutowiredAnnotationBeanPostProcessor类,注入@Value,@Autowired属性值就是靠它来完成的。
SpringApplication.run
refresh
AbstractApplicationContext.refresh
finishBeanFactoryInitialization
DefaultListableBeanFactory.preInstantiateSingletons
AbstractBeanFactory.getBean
doGetBean
AbstractAutowireCapableBeanFactory.createBean
doCreateBean
createBeanInstance
applyMergedBeanDefinitionPostProcessors
AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition
findAutowiringMetadata
buildAutowiringMetadata
//这里通过反射的方式遍历所有的filed和method,检查是否打了@Autowired、@Value标签
//得到AutowiredFieldElement、AutowiredMethodElement的集合,最后都缓存到LinkedList<InjectionMetadata.InjectedElement>链表里面,然后包装到InjectionMetadata对象中
ReflectionUtils.doWithLocalFields
ReflectionUtils.doWithLocalMethods
//回到AbstractAutowireCapableBeanFactory.populateBean
AbstractAutowireCapableBeanFactory.populateBean
AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues
findAutowiringMetadata
InjectionMetadata.inject
//在这个方法里面通过反射的方式将值注入到@Autowired,@Value中
AutowiredAnnotationBeanPostProcessor.inject
//解析@Value("${XXX}")中的值的方法是通过PropertySourcesPlaceholderConfigurer.resolveStringValue
//最终还是通过PropertySourcesPropertyResolver.getPropertyAsRawString来获取配置的值的,然后反射注入到@Value字段中
本地配置文件刷新
1. 使用定时器自动刷新PropertySource
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
@Component
public class PropertyLoader {
@Autowired
private StandardEnvironment environment;
@Scheduled(fixedRate=1000)
public void reload() throws IOException {
MutablePropertySources propertySources = environment.getPropertySources();
String propertiesFilePropertySoureName = "applicationConfig: [classpath:/application.properties]";
Properties properties = new Properties();
InputStream inputStream = getClass().getResourceAsStream("/application.properties");
properties.load(inputStream);
inputStream.close();
propertySources.replace(propertiesFilePropertySoureName, new PropertiesPropertySource(propertiesFilePropertySoureName, properties));
}
}
@SpringBootApplication
@EnableScheduling
@PropertySource("classpath:/application.properties")
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class, args);
}
}
@RestController
public class DemoController {
@Autowired
private StandardEnvironment environment;
@Value("${demo.name}")
private String name;
@GetMapping("/getNameFromEnvironment")
public String getNameFromEnvironment(){
return environment.getProperty("demo.name");
}
/**
* 配置文件修改后这个方式无法获取到最新的配置值
* 因为@Value的字段值是在spring容器启动的时候注入,通过刷新PropertySource的方式无法重新注入值
* @return
*/
@GetMapping("/getNameFromValue")
public String getNameFromValue(){
return name;
}
}
这种方式有一个缺点就是,无法使用@Value的方式获取配置值,因为@Value的字段值注入之后并不会被刷新,只能通过environment.getProperty("key")
的方式。
2. EnvironmentPostProcessor
3. PropertySourceLocator
4. Archaius PolledConfigurationSource
5. 类似appolo的方式,反射@Value字段并定时更新值
springboot自动配置原理
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
通过@Import的方式导入了AutoConfigurationImportSelector这个核心类,那么@Import是什么时候被spring容器扫描执行的呢?
SpringApplication.run
SpringApplication.refreshContext
SpringApplication.refresh
AbstractApplicationContext.refresh()
AbstractApplicationContext#invokeBeanFactoryPostProcessors
PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
ConfigurationClassPostProcessor#processConfigBeanDefinitions
// 下面这个方法是判断配置类是full模式还是lite模式的bean
// 【full模式】:就是标记@Configuration的类,里面的@Bean都是交给容器管理的,默认是单例的
// 【lite模式】:就是标记@Component,@ComponentScan,@Import,@ImportResource的类
// 与完整的@Configuration不同,lite @Bean方法不能声明bean之间的依赖关系,【并且不会被CGLIB代理,**不是单例的**】
//如果bean标记了@Configuration,@Component,@ComponentScan,@Import,@ImportResource会被加入到`List<BeanDefinitionHolder> configCandidates`集合,都当做配置类来处理
ConfigurationClassUtils#checkConfigurationClassCandidate
//回到ConfigurationClassPostProcessor类
// 【核心方法】,这个方法里面处理了ImportSelector的实现类
ConfigurationClassParser#parse
// ************************************************************************
//处理配置类,这里标记了@Import的bean会被处理
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
// 最后才处理DeferredImportSelector的实现类
//就是在这里AutoConfigurationImportSelector.selectImports被执行
//老版本SpringBoot这个类的名字叫EnableAutoConfigurationImportSelector
processDeferredImportSelectors();
}
// ************************************************************************
AutoConfigurationImportSelector
这个AutoConfigurationImportSelector是实现自动配置的核心类,它实现了DeferredImportSelector,会被spring容器加载到容器里面,通过spring SPI的方式把我们自定义的自动配置类加载到容器中了。
//【注意,它实现了DeferredImportSelector接口】
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//1. 核心方法selectImports
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//2. 这里获取配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
//3. 这里使用spring的SPI机制,加载了EnableAutoConfiguration类对应的配置类,见下方图片
//所以在我们的自定义项目jar包的/META-INF/spring.factories里面配置了自己的类,就会被自动加载进来
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
//...
}
举个例子,我们使用的redis的自动配置类
@Configuration
@ConditionalOnClass(RedisOperations.class)
//RedisProperties是redis的配置类
//spring.redis开头的配置都会被加载到这个类里面来
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
参考
Spring Boot @ConfigurationProperties: Binding external configurations to POJO classes