我们继续ConfigurationClassPostProcessor 处理@PropertySource(也会对@ConfigurationProperties、@Value进行分析)注解的流程,之前《springboot启动bean加载处理器ConfigurationClassPostProcessor 一(@ComponentScan注解)》简绍了这个类会处理**@PropertySource、 @ComponentScan、@Import、@ImportResource、@Bean**注解,本文就看下是怎么处理@PropertySource的,先看下源代码
class ConfigurationClassParser {
...
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
// Process any @Import annotations
// Process any @ImportResource annotations
// Process individual @Bean methods
// Process default methods on interfaces
// Process superclass, if any
// No superclass -> processing is complete
return null;
}
}
很简单,这个注解的作用是往propertysources添加配置文件,之前我们简绍过《springboot启动之配置文件秘密》。
看下它的处理流程
- 判断当前类的是否有@PropertySources或@PropertySources注解,看下它的属性。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
//定义属性文件名称
String name() default "";
//定义扩展文件的路径
String[] value();
boolean ignoreResourceNotFound() default false;
String encoding() default "";
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
- 如果存在进入processPropertySource方法进行解析,获取我们定义的value值,如果是${xxx}类型,会先对值进行替换(从我们的配置文件中获取,也就是目前已经存在的propertysources)
- 加载资源文件Resource
- 添加到当前environment的propertySources中,注意是addLast,添加到最后。
看下具体的例子
@Configuration
@PropertySource("${property.additional.path}")
public class TestProperty {
private String name;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
在application.properties定义的property.additional.path = classpath:system.yml,里面定义了属性。
#这里定义了test开头有其它作用
test.name: wang
test.address: hanzhou
name: zhang
address: beijin
这时我们就能获取到这些属性了
@SpringBootApplication(exclude = {ElasticsearchDataAutoConfiguration.class, ElasticsearchRestClientAutoConfiguration.class})
public class Testmain {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(new Class[]{Testmain.class, CustomerTypeFilter.class}, args);
ConfigurableEnvironment environment = run.getEnvironment();
for (String beanDefinitionName : run.getBeanDefinitionNames()) {
if (beanDefinitionName.startsWith("test")){
// 能获取到testProperty名称的bean,说明被加到spring容器了
System.out.println(beanDefinitionName);
}
}
BindResult<TestProperty> bind = Binder.get(environment).bind("test", Bindable.of(TestProperty.class), new BindHandler() {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
return BindHandler.super.onStart(name, target, context);
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Exception error) throws Exception {
error.printStackTrace();
return null;
}
});
//输出 hanzhou
System.out.println(bind.get().getAddress());
//输出 beijin
System.out.println(environment.getProperty("address"));
TestProperty bean = run.getBean(TestProperty.class);
// 输出空,说明TestProperty 属性没有赋值
System.out.println(bean.getAddress());
}
}
上面说到TestProperty 属性没有赋值,那么很容易么,加个注解@Value就搞定了。
@Configuration
@PropertySource("${property.additional.path}")
public class TestProperty {
@Value("${name}")
private String name;
@Value("${address}")
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
那@Value注解是怎么给字段进行赋值的呢?是它AutowiredAnnotationBeanPostProcessor搞的。我们看下它的类结构信息,实际上也是个BeanPostProcessor,从它的名字也看的出。
然后再看下它的构造信息,会处理@Autowired 、@Value、javax.inject.Inject注解。
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
....
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Autowired.class);
this.autowiredAnnotationTypes.add(Value.class);
try {
this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
// 因为实现了接口InstantiationAwareBeanPostProcessor,在bean创建的时候,赋值属性时会调用
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
}
赋值流程总结下来就是
- 获取当前类中的有@Autowired 、@Value、javax.inject.Inject注解的字段
- 针对@Value 进行解析,如@Value(“${name}”) 获取到属性key=name
- 从当前环境的propertysources中循环查询key并进行赋值,还是propertysources
- 最后再利用反射进行字段赋值
附上一张时序图,有点小~~
如果我们一个个在字段上面加注解,那不累死了,我想平常大家都不会这么搞。这个时候就要用到**@ConfigurationProperties**注解了,实现就会变成这样
@Configuration
@PropertySource("${property.additional.path}")
@ConfigurationProperties(prefix = "test")
public class TestProperty {
private String name;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
这样属性就会绑定test下对应的key,那它又是怎么实现的呢! ConfigurationPropertiesBindingPostProcessor对就是它,也是个BeanPostProcessor。实现原理实际上前面已经提供了例子了,就是通过Binder来进行绑定的
BindResult<TestProperty> bind = Binder.get(environment).bind("test", Bindable.of(TestProperty.class), new BindHandler() {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
return BindHandler.super.onStart(name, target, context);
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Exception error) throws Exception {
error.printStackTrace();
return null;
}
});
public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
/**
* The bean name that this post-processor is registered with.
*/
public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class.getName();
private ApplicationContext applicationContext;
private BeanDefinitionRegistry registry;
private ConfigurationPropertiesBinder binder;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
// We can't use constructor injection of the application context because
// it causes eager factory bean initialization
this.registry = (BeanDefinitionRegistry) this.applicationContext.getAutowireCapableBeanFactory();
this.binder = ConfigurationPropertiesBinder.get(this.applicationContext);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
return bean;
}
private void bind(ConfigurationPropertiesBean bean) {
if (bean == null || hasBoundValueObject(bean.getName())) {
return;
}
Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '"
+ bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean");
//最终调用的是Binder方法
try {
this.binder.bind(bean);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(bean, ex);
}
}
private boolean hasBoundValueObject(String beanName) {
return this.registry.containsBeanDefinition(beanName) && this.registry
.getBeanDefinition(beanName) instanceof ConfigurationPropertiesValueObjectBeanDefinition;
}
/**
* Register a {@link ConfigurationPropertiesBindingPostProcessor} bean if one is not
* already registered.
* @param registry the bean definition registry
* @since 2.2.0
*/
public static void register(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "Registry must not be null");
if (!registry.containsBeanDefinition(BEAN_NAME)) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN_NAME, definition);
}
ConfigurationPropertiesBinder.register(registry);
}
}
所以要加载额外的资源的正确姿势是:@Configuration(要被spring识别加到容器)+ @PropertySource(加载额外的配置文件) + @ConfigurationProperties(prefix = “test”) 绑定到属性中