Spring 配置解析之Properties
1.简单示例:
SpringBoot中的的配置简单属性类支持ConfigurationProperties方式,看一个简单的示例。
1 @ConfigurationProperties(prefix = "org.dragonfei.demo") 2 public class DemoProperties { 3 private String name; 4 private String password; 5 private String test; 6 7 public String getName() { 8 return name; 9 } 10 11 public void setName(String name) { 12 this.name = name; 13 } 14 15 public String getPassword() { 16 return password; 17 } 18 19 public void setPassword(String password) { 20 this.password = password; 21 } 22 23 public String getTest() { 24 return test; 25 } 26 27 public void setTest(String test) { 28 this.test = test; 29 } 30 31 @Override 32 public String toString() { 33 return "DemoProperties{" + 34 "name='" + name + '\'' + 35 ", password='" + password + '\'' + 36 ", test='" + test + '\'' + 37 '}'; 38 } 39 }
1 org.dragonfei.demo.name=dragonfei 2 org.dragonfei.demo.password=password 3 org.dragonfei.demo.test=test
1 @Configuration 2 @EnableConfigurationProperties({DemoProperties.class}) 3 public class DemoConfiguration { 4 }
1 @RunWith(SpringJUnit4ClassRunner.class) 2 @SpringApplicationConfiguration(classes = DemoConfiguration.class) 3 @EnableAutoConfiguration 4 public class DemoPropertiesTest { 5 6 @Autowired 7 private DemoProperties properties; 8 @Test 9 public void testProperties(){ 10 System.out.println(properties.toString()); 11 } 12 }
1 DemoProperties{name='dragonfei', password='password', test='test'}
DemoProperties神奇的注入到Spring容器中了。有没有跟我一样很兴奋,这样的 一大好处,将配置文件的属性以类的形式展现,在需要使用的时候只需要,autowire需要的类就可以了,避免大片重复的的${a.b.c}
2.Properties属性自动装配实现
DemoProperties这么神奇注入到容器中,天下没有什么是莫名奇妙的,引出了两个关键问题:
- DemoProperties是怎样注入到容器中?
- DemoProperties中的各个属性是怎么被赋值的呢?
要回答上面的问题,必须对@Configuration如何注入bean做一个简单的回顾:
- 在解析@Congiguraion的时候,会调用@Import中引入的类
- 如果@Import中是ImportBeanDefinitionegistar的子类,会直接调用registerBeanDefinitions
- 如果@Import中是ImportSelector类型,会调用selectImports()返回的bean的registerBeanDefinitions方法。
- registerBeanDefinitions方法会向BeanFactory中添加新的bean。
回到正题。打开EnableConfigurationProperties
1 @Target(ElementType.TYPE) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Import(EnableConfigurationPropertiesImportSelector.class) 5 public @interface EnableConfigurationProperties { 6 7 /** 8 * Convenient way to quickly register {@link ConfigurationProperties} annotated beans 9 * with Spring. Standard Spring Beans will also be scanned regardless of this value. 10 * @return {@link ConfigurationProperties} annotated beans to register 11 */ 12 Class<?>[] value() default {}; 13 14 }
注意@Imoport里面的类
1 public String[] selectImports(AnnotationMetadata metadata) { 2 MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes( 3 EnableConfigurationProperties.class.getName(), false); 4 Object[] type = attributes == null ? null 5 : (Object[]) attributes.getFirst("value"); 6 if (type == null || type.length == 0) { 7 return new String[] { 8 ConfigurationPropertiesBindingPostProcessorRegistrar.class 9 .getName() }; 10 } 11 return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(), 12 ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; 13 }
然后,会调用ConfigurationPropertiesBeanRegistar和ConfigurationPropertiesBindingPostProcessorRegistar的registerBeanDefinitions方法,前者是为了注入配置properties类,后者为属性绑定值
1 @Override 2 public void registerBeanDefinitions(AnnotationMetadata metadata, 3 BeanDefinitionRegistry registry) { 4 MultiValueMap<String, Object> attributes = metadata 5 .getAllAnnotationAttributes( 6 EnableConfigurationProperties.class.getName(), false); 7 List<Class<?>> types = collectClasses(attributes.get("value")); 8 for (Class<?> type : types) { 9 String prefix = extractPrefix(type); 10 String name = (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() 11 : type.getName()); 12 if (!registry.containsBeanDefinition(name)) { 13 registerBeanDefinition(registry, type, name); 14 } 15 } 16 }
1 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 2 BeanDefinitionRegistry registry) { 3 if (!registry.containsBeanDefinition(BINDER_BEAN_NAME)) { 4 BeanDefinitionBuilder meta = BeanDefinitionBuilder 5 .genericBeanDefinition(ConfigurationBeanFactoryMetaData.class); 6 BeanDefinitionBuilder bean = BeanDefinitionBuilder.genericBeanDefinition( 7 ConfigurationPropertiesBindingPostProcessor.class); 8 bean.addPropertyReference("beanMetaDataStore", METADATA_BEAN_NAME); 9 registry.registerBeanDefinition(BINDER_BEAN_NAME, bean.getBeanDefinition()); 10 registry.registerBeanDefinition(METADATA_BEAN_NAME, meta.getBeanDefinition()); 11 } 12 }
注意这里注入了ConfigurationPropertiesBindingPostProcessor,这才是属性赋值的关键。查看类图
注意到ConfigurationPropertiesBindingPostProcessor继承自BeanPostProcessor,他会在bean初始化前后调用before和after后置处理,这里,在Properties属性初始化完成后,会对绑定属性,
1 private void postProcessBeforeInitialization(Object bean, String beanName, 2 ConfigurationProperties annotation) { 3 Object target = bean; 4 PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>( 5 target); 6 if (annotation != null && annotation.locations().length != 0) { 7 factory.setPropertySources( 8 loadPropertySources(annotation.locations(), annotation.merge())); 9 } 10 else { 11 factory.setPropertySources(this.propertySources); 12 } 13 factory.setValidator(determineValidator(bean)); 14 // If no explicit conversion service is provided we add one so that (at least) 15 // comma-separated arrays of convertibles can be bound automatically 16 factory.setConversionService(this.conversionService == null 17 ? getDefaultConversionService() : this.conversionService); 18 if (annotation != null) { 19 factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields()); 20 factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields()); 21 factory.setExceptionIfInvalid(annotation.exceptionIfInvalid()); 22 factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties()); 23 if (StringUtils.hasLength(annotation.prefix())) { 24 factory.setTargetName(annotation.prefix()); 25 } 26 } 27 try { 28 factory.bindPropertiesToTarget(); 29 } 30 catch (Exception ex) { 31 String targetClass = ClassUtils.getShortName(target.getClass()); 32 throw new BeanCreationException(beanName, "Could not bind properties to " 33 + targetClass + " (" + getAnnotationDetails(annotation) + ")", ex); 34 } 35 }
至于真实的数据绑定,会从propertySources中获取,敬请期待....Spring 的数据绑定,这简单提一下关键的地方:
1 Set<String> names = getNames(relaxedTargetNames); 2 PropertyValues propertyValues = getPropertyValues(names, relaxedTargetNames);
请注意getNames,是获取prefix+属性构成的key值,prefix_property和prefix.property都会获取到
1 private Set<String> getNames(Iterable<String> prefixes) { 2 Set<String> names = new LinkedHashSet<String>(); 3 if (this.target != null) { 4 PropertyDescriptor[] descriptors = BeanUtils 5 .getPropertyDescriptors(this.target.getClass()); 6 for (PropertyDescriptor descriptor : descriptors) { 7 String name = descriptor.getName(); 8 if (!name.equals("class")) { 9 RelaxedNames relaxedNames = RelaxedNames.forCamelCase(name); 10 if (prefixes == null) { 11 for (String relaxedName : relaxedNames) { 12 names.add(relaxedName); 13 } 14 } 15 else { 16 for (String prefix : prefixes) { 17 for (String relaxedName : relaxedNames) { 18 names.add(prefix + "." + relaxedName); 19 names.add(prefix + "_" + relaxedName); 20 } 21 } 22 } 23 } 24 } 25 } 26 return names; 27 }
getPropertyValues会获取到满足上述条件的propertyValues,最后调用spring框架提供数据绑定策略进行数据绑定。