美文网首页
聊聊springboot的ConfigurationProper

聊聊springboot的ConfigurationProper

作者: go4it | 来源:发表于2023-09-06 09:19 被阅读0次

    本文主要研究一下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

    相关文章

      网友评论

          本文标题:聊聊springboot的ConfigurationProper

          本文链接:https://www.haomeiwen.com/subject/yzdkvdtx.html