1.簡單示例: SpringBoot中的的配置簡單屬性類支持ConfigurationProperties方式,看一個簡單的示例。 1 @ConfigurationProperties(prefix = "org.dragonfei.demo") 2 public class DemoPropert ...
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 }定義Properties類
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 }註入Properties
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 }View Code
註意@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 }View Code
然後,會調用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 }ConfigurationPropertiesBeanRegistar之registerBeanDefinitions
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 }ConfigurationPropertiesBindingPostProcessorRegistar之registerBeanDefinition
註意這裡註入了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);View Code
請註意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 }View Code
getPropertyValues會獲取到滿足上述條件的propertyValues,最後調用spring框架提供數據綁定策略進行數據綁定。