聊聊springboot的ConfigurationProperties的绑定

本文主要研究一下springboot的ConfigurationProperties的绑定

ConfigurationPropertiesBindingPostProcessor

org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java

/**
 * {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with
 * {@link ConfigurationProperties @ConfigurationProperties}.
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @author Christian Dupuis
 * @author Stephane Nicoll
 * @author Madhura Bhave
 * @since 1.0.0
 */
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");
		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)) {
			BeanDefinition definition = BeanDefinitionBuilder
					.genericBeanDefinition(ConfigurationPropertiesBindingPostProcessor.class,
							ConfigurationPropertiesBindingPostProcessor::new)
					.getBeanDefinition();
			definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN_NAME, definition);
		}
		ConfigurationPropertiesBinder.register(registry);
	}

}

ConfigurationPropertiesBindingPostProcessor实现了BeanPostProcessor、PriorityOrdered、ApplicationContextAware、InitializingBean四个接口;其getOrder方法返回的是Ordered.HIGHEST_PRECEDENCE + 1即仅次于最高的优先级;其postProcessBeforeInitialization方法主要是执行bind方法(先通过ConfigurationPropertiesBean.get获取ConfigurationPropertiesBean,再通过binder进行bind);其afterPropertiesSet主要是获取BeanDefinitionRegistry与ConfigurationPropertiesBinder

ConfigurationPropertiesBean.get

org/springframework/boot/context/properties/ConfigurationPropertiesBean.java

	public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
		Method factoryMethod = findFactoryMethod(applicationContext, beanName);
		return create(beanName, bean, bean.getClass(), factoryMethod);
	}

	private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory) {
		ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
		if (annotation == null) {
			return null;
		}
		Validated validated = findAnnotation(instance, type, factory, Validated.class);
		Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
				: new Annotation[] { annotation };
		ResolvableType bindType = (factory != null) ? ResolvableType.forMethodReturnType(factory)
				: ResolvableType.forClass(type);
		Bindable<Object> bindTarget = Bindable.of(bindType).withAnnotations(annotations);
		if (instance != null) {
			bindTarget = bindTarget.withExistingValue(instance);
		}
		return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
	}

get方法主要是获取工厂方法,之后获取annotation,获取bindTarget,最后创建ConfigurationPropertiesBean

ConfigurationPropertiesBean

org/springframework/boot/context/properties/ConfigurationPropertiesBean.java

/**
 * Provides access to {@link ConfigurationProperties @ConfigurationProperties} bean
 * details, regardless of if the annotation was used directly or on a {@link Bean @Bean}
 * factory method. This class can be used to access {@link #getAll(ApplicationContext)
 * all} configuration properties beans in an ApplicationContext, or
 * {@link #get(ApplicationContext, Object, String) individual beans} on a case-by-case
 * basis (for example, in a {@link BeanPostProcessor}).
 *
 * @author Phillip Webb
 * @since 2.2.0
 * @see #getAll(ApplicationContext)
 * @see #get(ApplicationContext, Object, String)
 */
public final class ConfigurationPropertiesBean {

	private final String name;

	private final Object instance;

	private final ConfigurationProperties annotation;

	private final Bindable<?> bindTarget;

	private final BindMethod bindMethod;

	//......
}	

ConfigurationPropertiesBean用于代表一个标注了@ConfigurationProperties注解的bean的信息

ConfigurationPropertiesBinder

org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java

/**
 * Internal class used by the {@link ConfigurationPropertiesBindingPostProcessor} to
 * handle the actual {@link ConfigurationProperties @ConfigurationProperties} binding.
 *
 * @author Stephane Nicoll
 * @author Phillip Webb
 */
class ConfigurationPropertiesBinder {

	private static final String BEAN_NAME = "org.springframework.boot.context.internalConfigurationPropertiesBinder";

	private static final String FACTORY_BEAN_NAME = "org.springframework.boot.context.internalConfigurationPropertiesBinderFactory";

	private static final String VALIDATOR_BEAN_NAME = EnableConfigurationProperties.VALIDATOR_BEAN_NAME;

	private final ApplicationContext applicationContext;

	private final PropertySources propertySources;

	private final Validator configurationPropertiesValidator;

	private final boolean jsr303Present;

	private volatile Validator jsr303Validator;

	private volatile Binder binder;

	ConfigurationPropertiesBinder(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources();
		this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext);
		this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext);
	}

	BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
		Bindable<?> target = propertiesBean.asBindTarget();
		ConfigurationProperties annotation = propertiesBean.getAnnotation();
		BindHandler bindHandler = getBindHandler(target, annotation);
		return getBinder().bind(annotation.prefix(), target, bindHandler);
	}

	private Binder getBinder() {
		if (this.binder == null) {
			this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(),
					getConversionService(), getPropertyEditorInitializer(), null,
					ConfigurationPropertiesBindConstructorProvider.INSTANCE);
		}
		return this.binder;
	}

	//......
}	

ConfigurationPropertiesBinder的bind方法根据ConfigurationPropertiesBean的target与annotation取获取bindHandler,然后通过binder去执行bind方法
binder的构造器依赖了propertySources、placeholdersResolver、conversionService、propertyEditorInitializer、defaultBindHandler、constructorProvider

Binder

org/springframework/boot/context/properties/bind/Binder.java

	private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
			Context context, boolean allowRecursiveBinding) {
		ConfigurationProperty property = findProperty(name, context);
		if (property == null && context.depth != 0 && containsNoDescendantOf(context.getSources(), name)) {
			return null;
		}
		AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
		if (aggregateBinder != null) {
			return bindAggregate(name, target, handler, context, aggregateBinder);
		}
		if (property != null) {
			try {
				return bindProperty(target, context, property);
			}
			catch (ConverterNotFoundException ex) {
				// We might still be able to bind it using the recursive binders
				Object instance = bindDataObject(name, target, handler, context, allowRecursiveBinding);
				if (instance != null) {
					return instance;
				}
				throw ex;
			}
		}
		return bindDataObject(name, target, handler, context, allowRecursiveBinding);
	}

	private AggregateBinder<?> getAggregateBinder(Bindable<?> target, Context context) {
		Class<?> resolvedType = target.getType().resolve(Object.class);
		if (Map.class.isAssignableFrom(resolvedType)) {
			return new MapBinder(context);
		}
		if (Collection.class.isAssignableFrom(resolvedType)) {
			return new CollectionBinder(context);
		}
		if (target.getType().isArray()) {
			return new ArrayBinder(context);
		}
		return null;
	}

	private <T> Object bindAggregate(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
			Context context, AggregateBinder<?> aggregateBinder) {
		AggregateElementBinder elementBinder = (itemName, itemTarget, source) -> {
			boolean allowRecursiveBinding = aggregateBinder.isAllowRecursiveBinding(source);
			Supplier<?> supplier = () -> bind(itemName, itemTarget, handler, context, allowRecursiveBinding, false);
			return context.withSource(source, supplier);
		};
		return context.withIncreasedDepth(() -> aggregateBinder.bind(name, target, elementBinder));
	}

	private <T> Object bindProperty(Bindable<T> target, Context context, ConfigurationProperty property) {
		context.setConfigurationProperty(property);
		Object result = property.getValue();
		result = this.placeholdersResolver.resolvePlaceholders(result);
		result = context.getConverter().convert(result, target);
		return result;
	}

bindObject方法先通过findProperty获取ConfigurationProperty,然后执行bindAggregate或者bindProperty;AggregateBinder主要是处理Map、Collection、Array类型;bindProperty方法这里从property获取绑定的值,然后resolvePlaceholders,最后通过converter的convert方法把值绑定到target上

BindConverter

org/springframework/boot/context/properties/bind/BindConverter.java

	<T> T convert(Object value, ResolvableType type, Annotation... annotations) {
		if (value == null) {
			return null;
		}
		return (T) this.conversionService.convert(value, TypeDescriptor.forObject(value),
				new ResolvableTypeDescriptor(type, annotations));
	}

BindConverter的convert方法则是通过conversionService进行

小结

ConfigurationPropertiesBindingPostProcessor实现了BeanPostProcessor、PriorityOrdered、ApplicationContextAware、InitializingBean四个接口;其getOrder方法返回的是Ordered.HIGHEST_PRECEDENCE + 1即仅次于最高的优先级;其postProcessBeforeInitialization方法主要是执行bind方法(先通过ConfigurationPropertiesBean.get获取ConfigurationPropertiesBean,再通过binder进行bind);其afterPropertiesSet主要是获取BeanDefinitionRegistry与ConfigurationPropertiesBinder

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot中的@ConfigurationProperties注解用于将外部配置文件中的属性值绑定到一个Java类的属性上。通过@ConfigurationProperties注解,可以方便地将配置文件中的属性值注入到Spring Bean中。 使用@ConfigurationProperties注解时,首先需要在Java类上添加@Component或@Configuration注解,以将其作为一个Spring Bean进行管理。然后,使用@ConfigurationProperties注解标注在类级别上,并使用prefix属性指定属性的前缀。 例如,假设有一个配置文件application.properties,其中定义了以下属性: ``` myapp.username=admin myapp.password=secret ``` 可以创建一个Java类来绑定这些属性: ```java @Component @ConfigurationProperties(prefix = "myapp") public class MyAppProperties { private String username; private String password; // 省略getter和setter方法 } ``` 注意,Java类的属性名需要与配置文件中的属性名保持一致,否则绑定将不成功。在类级别上使用@ConfigurationProperties注解时,需要指定prefix属性,它表示配置文件中的属性前缀。 然后,在其他类中可以通过@Autowired将MyAppProperties注入,并直接使用其中的属性值: ```java @Component public class MyService { @Autowired private MyAppProperties myAppProperties; public void doSomething() { String username = myAppProperties.getUsername(); String password = myAppProperties.getPassword(); // 使用属性值进行操作 } } ``` 这样就实现了将配置文件中的属性值注入到Java类中的过程。在应用启动时,Spring Boot会自动加载并绑定配置文件中的属性值,并将其注入到对应的Bean中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值