乐趣区

关于springboot:聊聊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

退出移动版