springboot启动bean加载处理器ConfigurationClassPostProcessor 二(@PropertySource注解)

我们继续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启动之配置文件秘密》。
看下它的处理流程

  1. 判断当前类的是否有@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;

}
  1. 如果存在进入processPropertySource方法进行解析,获取我们定义的value值,如果是${xxx}类型,会先对值进行替换(从我们的配置文件中获取,也就是目前已经存在的propertysources)
  2. 加载资源文件Resource
  3. 添加到当前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;
	}
}

赋值流程总结下来就是

  1. 获取当前类中的有@Autowired 、@Value、javax.inject.Inject注解的字段
  2. 针对@Value 进行解析,如@Value(“${name}”) 获取到属性key=name
  3. 从当前环境的propertysources中循环查询key并进行赋值,还是propertysources
  4. 最后再利用反射进行字段赋值

附上一张时序图,有点小~~
在这里插入图片描述

如果我们一个个在字段上面加注解,那不累死了,我想平常大家都不会这么搞。这个时候就要用到**@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”) 绑定到属性中

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值