精尽 Spring Boot 源码分析 —— @ConfigurationProperties

1. 概述

本文我们来分享 @ConfigurationProperties 注解,如何将配置文件自动设置到被注解的类。代码如下:

 

// ConfigurationProperties.java/*** Annotation for externalized configuration. Add this to a class definition or a* {@code @Bean} method in a {@code @Configuration} class if you want to bind and validate* some external Properties (e.g. from a .properties file).* <p>* Note that contrary to {@code @Value}, SpEL expressions are not evaluated since property* values are externalized.** @author Dave Syer* @see ConfigurationPropertiesBindingPostProcessor* @see EnableConfigurationProperties*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {/*** The name prefix of the properties that are valid to bind to this object. Synonym* for {@link #prefix()}. A valid prefix is defined by one or more words separated* with dots (e.g. {@code "acme.system.feature"}).** @return the name prefix of the properties to bind*/@AliasFor("prefix")String value() default "";/*** The name prefix of the properties that are valid to bind to this object. Synonym* for {@link #value()}. A valid prefix is defined by one or more words separated with* dots (e.g. {@code "acme.system.feature"}).** @return the name prefix of the properties to bind*/@AliasFor("value")String prefix() default "";/*** Flag to indicate that when binding to this object invalid fields should be ignored.* Invalid means invalid according to the binder that is used, and usually this means* fields of the wrong type (or that cannot be coerced into the correct type).** @return the flag value (default false)*/boolean ignoreInvalidFields() default false;/*** Flag to indicate that when binding to this object unknown fields should be ignored.* An unknown field could be a sign of a mistake in the Properties.** @return the flag value (default true)*/boolean ignoreUnknownFields() default true;}

 

@ConfigurationProperties 注解有两种使用方法,可见 《关与 @EnableConfigurationProperties 注解》 文章。总结来说:

  • 第一种,@Component + @ConfigurationProperties 。
  • 第二种,@EnableConfigurationProperties + ConfigurationProperties 。

实际情况下,更多的是使用第一种。当然,第二种的 @EnableConfigurationProperties 的效果,也是将指定的类,实现和 @Component 被注解的类是一样的,创建成 Bean 对象。
这样,@ConfigurationProperties 就可以将配置文件自动设置到该 Bean 对象咧。

2. @EnableConfigurationProperties

org.springframework.boot.context.properties.@EnableConfigurationProperties 注解,可以将指定带有 @ConfigurationProperties 的类,注册成 BeanDefinition ,从而创建成 Bean 对象。代码如下:

 

// EnableConfigurationProperties.java/*** Enable support for {@link ConfigurationProperties} annotated beans.* {@link ConfigurationProperties} beans can be registered in the standard way (for* example using {@link Bean @Bean} methods) or, for convenience, can be specified* directly on this annotation.** @author Dave Syer*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {/*** 指定的类们** Convenient way to quickly register {@link ConfigurationProperties} annotated beans* with Spring. Standard Spring Beans will also be scanned regardless of this value.* @return {@link ConfigurationProperties} annotated beans to register*/Class<?>[] value() default {};}

 

  • 从 @Import 注解上,可以看到使用 EnableConfigurationPropertiesImportSelector 处理。详细的解析,见 「2.2 EnableConfigurationPropertiesImportSelector」 。

2.1 ConfigurationPropertiesAutoConfiguration

默认情况下,@EnableConfigurationProperties 会通过 org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration 类,进行开启。代码如下:

 

// ConfigurationPropertiesAutoConfiguration.java/*** {@link EnableAutoConfiguration Auto-configuration} for {@link ConfigurationProperties}* beans. Automatically binds and validates any bean annotated with* {@code @ConfigurationProperties}.** @author Stephane Nicoll* @since 1.3.0* @see EnableConfigurationProperties* @see ConfigurationProperties*/
@Configuration
@EnableConfigurationProperties // <X>
public class ConfigurationPropertiesAutoConfiguration {
}

 

  • 看,看看,看看看,<X> 哟~

2.2 EnableConfigurationPropertiesImportSelector

org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector ,实现 ImportSelector 接口,处理 @EnableConfigurationProperties 注解。代码如下:

 

// EnableConfigurationPropertiesImportSelector.javaprivate static final String[] IMPORTS = {ConfigurationPropertiesBeanRegistrar.class.getName(),ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };@Override
public String[] selectImports(AnnotationMetadata metadata) {return IMPORTS;
}

 

  • 返回的 IMPORTS 的是两个 ImportBeanDefinitionRegistrar 实现类。分别是:
    • ConfigurationPropertiesBeanRegistrar ,在 「2.3 ConfigurationPropertiesBeanRegistrar」 中详细解析。
    • ConfigurationPropertiesBindingPostProcessorRegistrar ,在 「2.4 ConfigurationPropertiesBindingPostProcessorRegistrar」 中详细解析。

2.3 ConfigurationPropertiesBeanRegistrar

ConfigurationPropertiesBeanRegistrar ,是 EnableConfigurationPropertiesImportSelector 的内部静态类,实现 ImportBeanDefinitionRegistrar 接口,将 @EnableConfigurationProperties 注解指定的类,逐个注册成对应的 BeanDefinition 对象。代码如下:

 

// EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.java@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {getTypes(metadata) // <1>.forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type)); // <2>
}

 

  • <1> 处,调用 #getTypes(AnnotationMetadata metadata) 方法,获得 @EnableConfigurationProperties 注解指定的类的数组。代码如下:

    // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.javaprivate List<Class<?>> getTypes(AnnotationMetadata metadata) {// 获得 @EnableConfigurationProperties 注解MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);// 获得 value 属性return collectClasses((attributes != null) ? attributes.get("value"): Collections.emptyList());
    }private List<Class<?>> collectClasses(List<?> values) {return values.stream().flatMap((value) -> Arrays.stream((Object[]) value)).map((o) -> (Class<?>) o).filter((type) -> void.class != type).collect(Collectors.toList());
    }
    

     

    • ~
  • <2> 处,遍历,逐个调用 #register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, Class<?> type) 方法,注册每个类对应的 BeanDefinition 对象。代码如下:

     

    // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.javaprivate void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, Class<?> type) {// <2.1> 通过 @ConfigurationProperties 注解,获得最后要生成的 BeanDefinition 的名字。格式为 prefix-类全名 or 类全名String name = getName(type);// <2.2> 判断是否已经有该名字的 BeanDefinition 的名字。没有,才进行注册if (!containsBeanDefinition(beanFactory, name)) {registerBeanDefinition(registry, name, type); // <2.3> }
    }
    

     

    • <2.1> 处,调用 #getName(Class<?> type) 方法,通过 @ConfigurationProperties 注解,获得最后要生成的 BeanDefinition 的名字。代码如下:

      // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.javaprivate String getName(Class<?> type) {ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class);String prefix = (annotation != null) ? annotation.prefix() : "";return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
      }
      

       

      • 格式为 prefix-类全名 or 类全名。
    • <2.2> 处,调用 #containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) 方法,判断是否已经有该名字的 BeanDefinition 的名字。代码如下:

      // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.javaprivate boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) {// 判断是否存在 BeanDefinition 。如果有,则返回 trueif (beanFactory.containsBeanDefinition(name)) {return true;}// 获得父容器,判断是否存在BeanFactory parent = beanFactory.getParentBeanFactory();if (parent instanceof ConfigurableListableBeanFactory) {return containsBeanDefinition((ConfigurableListableBeanFactory) parent, name);}// 返回 false ,说明不存在return false;
      }
      

       

      • 如果不存在,才执行后续的注册 BeanDefinition 逻辑。
    • <2.3> 处,调用 #registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) 方法,注册 BeanDefinition 。代码如下:

      // EnableConfigurationPropertiesImportSelector#ConfigurationPropertiesBeanRegistrar.javaprivate void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) {// 断言,判断该类有 @ConfigurationProperties 注解assertHasAnnotation(type);// 创建 GenericBeanDefinition 对象GenericBeanDefinition definition = new GenericBeanDefinition();definition.setBeanClass(type);// 注册到 BeanDefinitionRegistry 中registry.registerBeanDefinition(name, definition);
      }private void assertHasAnnotation(Class<?> type) {Assert.notNull(AnnotationUtils.findAnnotation(type, ConfigurationProperties.class),() -> "No " + ConfigurationProperties.class.getSimpleName()+ " annotation found on  '" + type.getName() + "'.");
      }
      

       

      • ~

2.4 ConfigurationPropertiesBindingPostProcessorRegistrar

org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar ,实现 ImportBeanDefinitionRegistrar 接口,代码如下:

 

// ConfigurationPropertiesBindingPostProcessorRegistrar.java@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {// <1> 注册 ConfigurationPropertiesBindingPostProcessor BeanDefinitionregisterConfigurationPropertiesBindingPostProcessor(registry);// <2> 注册 ConfigurationBeanFactoryMetadata BeanDefinitionregisterConfigurationBeanFactoryMetadata(registry);}
}

 

  • <1> 处,调用 #registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) 方法,注册 ConfigurationPropertiesBindingPostProcessor BeanDefinition 。代码如下:

    // ConfigurationPropertiesBindingPostProcessorRegistrar.javaprivate void registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) {// 创建 GenericBeanDefinition 对象GenericBeanDefinition definition = new GenericBeanDefinition();definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);// 注册registry.registerBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);
    }
    

     

    • 关于 ConfigurationPropertiesBindingPostProcessor 类,我们在 「4. ConfigurationPropertiesBindingPostProcessor 相关」 中,详细解析。
  • <2> 处,调用 #registerConfigurationBeanFactoryMetadata(BeanDefinitionRegistry registry) 方法,注册 ConfigurationBeanFactoryMetadata BeanDefinition 。代码如下:

    // ConfigurationPropertiesBindingPostProcessorRegistrar.javaprivate void registerConfigurationBeanFactoryMetadata(BeanDefinitionRegistry registry) {// 创建 GenericBeanDefinition 对象GenericBeanDefinition definition = new GenericBeanDefinition();definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);// 注册registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME, definition);
    }
    

     

    • 关于 ConfigurationBeanFactoryMetadata 类,我们在 「3. ConfigurationBeanFactoryMetadata」 中,详细解析。

3. ConfigurationBeanFactoryMetadata

org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata ,初始化配置类创建 Bean 的每个方法的元数据。

3.1 postProcessBeanFactory

实现 #postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 方法,代码如下:

 

// ConfigurationBeanFactoryMetadata.javaprivate ConfigurableListableBeanFactory beanFactory;/*** FactoryMetadata 的映射** KEY :Bean 的名字*/
private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// <1> 初始化 beanFactory 属性this.beanFactory = beanFactory;// <2> 遍历所有的 BeanDefinition 的名字们for (String name : beanFactory.getBeanDefinitionNames()) {// <2.1> 获得 BeanDefinition 对象BeanDefinition definition = beanFactory.getBeanDefinition(name);// <2.2> 获得 method、bean 属性String method = definition.getFactoryMethodName();String bean = definition.getFactoryBeanName();// <2.3> 添加到 beansFactoryMetadata 中if (method != null && bean != null) {this.beansFactoryMetadata.put(name, new FactoryMetadata(bean, method));}}
}

 

  • <1> 处,初始化 beanFactory 属性。

  • <2> 处,遍历所有的 BeanDefinition 的名字们,初始化 beansFactoryMetadata 属性。

  • <2.1> 处,获得 BeanDefinition 对象。

  • <2.2> 处,获得 BeanDefinition 的 factoryMethodNamefactoryBeanName 属性。

    • factoryBeanName 属性,是创建该 Bean 的工厂 Bean 的名字。

    • factoryMethodName 属性,是创建 Bean 的工厂 Bean 的方法名。

    • 以如下的 Configuration 类,举个例子:

      @Configuration
      public class TestConfiguration {@Beanpublic Object testObject() {return new Object();}}
      

       

      • 每个 @Bean 注解的方法,都是一个 factoryBeanName + factoryMethodName 。
      • factoryBeanName 属性,为 "testConfiguration" 。
      • factoryMethodName 属性,为 "testObject" 。
  • <2.3> 处,都非空的情况下,添加到 beansFactoryMetadata 中。

  • FactoryMetadata 是 ConfigurationBeanFactoryMetadata 的内部静态类。代码如下:

    // ConfigurationBeanFactoryMetadata#FactoryMetadata.javaprivate static class FactoryMetadata {/*** Bean 的名字*/private final String bean;/*** Bean 的方法名*/private final String method;// ... 省略 setting / getting  方法}
    

     

3.2 findFactoryMethod

#findFactoryMethod(String beanName) 方法,获得指定 Bean 的创建方法。代码如下:

 

// ConfigurationBeanFactoryMetadata.javapublic Method findFactoryMethod(String beanName) {// 如果不存在,则返回 nullif (!this.beansFactoryMetadata.containsKey(beanName)) {return null;}AtomicReference<Method> found = new AtomicReference<>(null);// 获得 beanName 对应的 FactoryMetadata 对象FactoryMetadata metadata = this.beansFactoryMetadata.get(beanName);// 获得对应的工厂类Class<?> factoryType = this.beanFactory.getType(metadata.getBean());if (ClassUtils.isCglibProxyClass(factoryType)) {factoryType = factoryType.getSuperclass();}// 获得对应的工厂类的方法String factoryMethod = metadata.getMethod();ReflectionUtils.doWithMethods(factoryType, (method) -> {if (method.getName().equals(factoryMethod)) {found.compareAndSet(null, method);}});return found.get();
}

 

3.3 findFactoryAnnotation

#findFactoryAnnotation(String beanName, Class<A> type) 方法,获得指定 Bean 的创建方法上的注解。代码如下:

 

// ConfigurationBeanFactoryMetadata.javapublic <A extends Annotation> A findFactoryAnnotation(String beanName, Class<A> type) {// 获得方法Method method = findFactoryMethod(beanName);// 获得注解return (method != null) ? AnnotationUtils.findAnnotation(method, type) : null;
}

 

3.4 getBeansWithFactoryAnnotation

#getBeansWithFactoryAnnotation(Class<A> type) 方法,获得 beansFactoryMetadata 中的每个 Bean 的方法上的指定注解。代码如下:

 

// ConfigurationBeanFactoryMetadata.javapublic <A extends Annotation> Map<String, Object> getBeansWithFactoryAnnotation(Class<A> type) {Map<String, Object> result = new HashMap<>();// 遍历 beansFactoryMetadatafor (String name : this.beansFactoryMetadata.keySet()) {// 获得每个 Bean 的创建方法上的注解if (findFactoryAnnotation(name, type) != null) {result.put(name, this.beanFactory.getBean(name));}}return result;
}

 

😈 至此,我们基本能够明白,ConfigurationBeanFactoryMetadata 就是提供一些元数据的。

4. ConfigurationPropertiesBindingPostProcessor 相关

艿艿:因为 ConfigurationPropertiesBindingPostProcessor 涉及到好几个类,所以一起放在本小节来看看。

4.1 ConfigurationPropertiesBindingPostProcessor

org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor ,实现 BeanPostProcessor、PriorityOrdered、ApplicationContextAware、InitializingBean 接口,将配置文件注入到 @ConfigurationProperties 注解的 Bean 的属性中。

4.1.1 基本属性

 

// ConfigurationPropertiesBindingPostProcessor.java/*** The bean name of the configuration properties validator.*/
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";private ConfigurationBeanFactoryMetadata beanFactoryMetadata;private ApplicationContext applicationContext;private ConfigurationPropertiesBinder configurationPropertiesBinder;@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext; // <1>
}@Override
public void afterPropertiesSet() throws Exception {// We can't use constructor injection of the application context because// it causes eager factory bean initializationthis.beanFactoryMetadata = this.applicationContext.getBean(ConfigurationBeanFactoryMetadata.BEAN_NAME, ConfigurationBeanFactoryMetadata.class); // <2>this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(this.applicationContext, VALIDATOR_BEAN_NAME); // <3>
}

 

  • <1> 处,设置 applicationContext 属性。
  • <2> 处,设置 beanFactoryMetadata 属性。即,我们在 「3. ConfigurationBeanFactoryMetadata」 中看到的。
  • <3> 处,创建 ConfigurationPropertiesBinder 对象,设置到 configurationPropertiesBinder 属性。TODO ConfigurationPropertiesBinder

4.1.2 postProcessBeforeInitialization

实现 #postProcessBeforeInitialization(Object bean, String beanName) 方法,代码如下:

 

// ConfigurationPropertiesBindingPostProcessor.java@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// <1> 获得 Bean 上的 @ConfigurationProperties 属性ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);if (annotation != null) {// <2> 将配置文件注入到 `@ConfigurationProperties` 注解的 Bean 的属性中bind(bean, beanName, annotation);}return bean;
}

 

  • <1> 处,调用 #getAnnotation(Object bean, String beanName, Class<A> type) 方法,获得 Bean 上的 @ConfigurationProperties 属性。代码如下:
    // ConfigurationPropertiesBindingPostProcessor.javaprivate <A extends Annotation> A getAnnotation(Object bean, String beanName, Class<A> type) {// 获得 Bean 上的注解A annotation = this.beanFactoryMetadata.findFactoryAnnotation(beanName, type);// 如果获得不到,则获得 Bean 对应的 Class 上的注解if (annotation == null) {annotation = AnnotationUtils.findAnnotation(bean.getClass(), type);}return annotation;
    }
    
  • <2> 处,调用 #bind(Object bean, String beanName, ConfigurationProperties annotation) 方法,将配置文件注入到 @ConfigurationProperties 注解的 Bean 的属性中。代码如下:

    // ConfigurationPropertiesBindingPostProcessor.javaprivate void bind(Object bean, String beanName, ConfigurationProperties annotation) {// <2.1> 解析 Bean 的类型ResolvableType type = getBeanType(bean, beanName);// <2.2> 获得 Bean 上的 @Validated 注解Validated validated = getAnnotation(bean, beanName, Validated.class);// <2.3> 创建 Annotation 数组Annotation[] annotations = (validated != null)? new Annotation[] { annotation, validated }: new Annotation[] { annotation };// <2.4> 创建 Bindable 对象Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);try {// <2.5> 将配置文件注入到 `@ConfigurationProperties` 注解的 Bean 的属性中this.configurationPropertiesBinder.bind(target);} catch (Exception ex) {throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex);}
    }
    

     

    • <2.1> 处,调用 #getBeanType(Object bean, String beanName) 方法,解析 Bean 的类型。代码如下:

      // ConfigurationPropertiesBindingPostProcessor.javaprivate ResolvableType getBeanType(Object bean, String beanName) {// 获得 beanName 对应的工厂方法Method factoryMethod = this.beanFactoryMetadata.findFactoryMethod(beanName);// 情况一:如果是,说明是 Configuration 类创建的 Bean 对象if (factoryMethod != null) {return ResolvableType.forMethodReturnType(factoryMethod);}// 情况二:如果否,说明是普通的类创建的 Bean 对象return ResolvableType.forClass(bean.getClass());
      }
      

       

      • 两种情况,见注释。
    • <2.2> 处,调用 #getAnnotation(Object bean, String beanName, Class<A> type) 方法,获得 Bean 上的 @Validated 属性。@ConfigurationProperties 注解,可以配合 @Validated 注解,一起使用,从而实现校验的功能。具体可以看看 《Enable ConfigurationProperties validation with @Validated on the factory method》 文章。

    • <2.3> 处,创建 Annotation 数组。

    • <2.4> 处,创建 Bindable 对象。我们先不用去理解 Bindable 是个锤子,至少我们看到了 withExistingValue(bean) 设置了 Bean 对象,withAnnotations(annotations) 设置了 Annotation 注解数组。

    • <2.5> 处,调用 ConfigurationPropertiesBinder#bind(Bindable<?> target) 方法,将配置文件注入到 @ConfigurationProperties 注解的 Bean 的属性中。详细解析,见 「4.2 ConfigurationPropertiesBinder」 。

4.2 ConfigurationPropertiesBinder

org.springframework.boot.context.properties.ConfigurationPropertiesBinder ,处理 @ConfigurationProperties 注解的 Bean 的属性的注入。其类上的注释如下:

 

// ConfigurationPropertiesBinder.java/*** Internal class by the {@link ConfigurationPropertiesBindingPostProcessor} to handle the* actual {@link ConfigurationProperties} binding.*/

 

4.2.1 构造方法

 

// ConfigurationPropertiesBinder.javaprivate 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, String validatorBeanName) {this.applicationContext = applicationContext; // <1>this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources(); // <2>this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext, validatorBeanName); // <3> this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext); // <4>
}

 

  • <1> 处,设置 applicationContext 属性。

  • <2> 处,创建 org.springframework.boot.context.properties.PropertySourcesDeducer 对象,然后调用 PropertySourcesDeducer#getPropertySources() 方法,获得 PropertySource 数组,之后设置给 propertySources 属性。关于 PropertySourcesDeducer.java 类,胖友点击链接,自己看看即可。

  • <3> 处,调用 #getConfigurationPropertiesValidator(ApplicationContext applicationContext, String validatorBeanName) 方法,获得配置的 Validator 对象。代码如下:

    // ConfigurationPropertiesBinder.javaprivate Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext, String validatorBeanName) {if (applicationContext.containsBean(validatorBeanName)) {return applicationContext.getBean(validatorBeanName, Validator.class);}return null;
    }
    

     

    • 从上面的文章,可以知道 validatorBeanName 为 "configurationPropertiesValidator" 。即,创建的 Validator Bean 的对象。
    • 一般情况下,我们不会配置该 Bean 对象,所以返回 null 。因此吧,可以暂时无视这个 configurationPropertiesValidator 属性~。
  • <4> 处,调用 ConfigurationPropertiesJsr303Validator#isJsr303Present(ApplicationContext applicationContext) 方法,是否有引入 Jsr 303 Validator 相关的依赖。关于它,详细解析见 「4.3 ConfigurationPropertiesJsr303Validator」 中。

4.2.2 bind

#bind(Bindable<?> target) 方法,处理 @ConfigurationProperties 注解的 Bean 的属性的注入。代码如下:

 

// ConfigurationPropertiesBinder.javapublic void bind(Bindable<?> target) {// <1> 获得 @ConfigurationProperties 注解的属性ConfigurationProperties annotation = target.getAnnotation(ConfigurationProperties.class);Assert.state(annotation != null, () -> "Missing @ConfigurationProperties on " + target);// <2> 获得 Validator 数组List<Validator> validators = getValidators(target);// <3> 获得 BindHandler 对象BindHandler bindHandler = getBindHandler(annotation, validators);// <4> 获得 Binder 对象,然后执行绑定逻辑,处理 `@ConfigurationProperties` 注解的 Bean 的属性的注入getBinder().bind(annotation.prefix(), target, bindHandler);
}

 

  • <1> 处,获得 @ConfigurationProperties 注解的属性。

  • <2> 处,调用 #getValidators(Bindable<?> target) 方法,获得 Validator 数组。代码如下:

    // ConfigurationPropertiesBinder.javaprivate List<Validator> getValidators(Bindable<?> target) {List<Validator> validators = new ArrayList<>(3);// 来源一,configurationPropertiesValidatorif (this.configurationPropertiesValidator != null) {validators.add(this.configurationPropertiesValidator);}// 来源二,ConfigurationPropertiesJsr303Validator 对象if (this.jsr303Present && target.getAnnotation(Validated.class) != null) {validators.add(getJsr303Validator());}// 来源三,自己实现了 Validator 接口if (target.getValue() != null && target.getValue().get() instanceof Validator) {validators.add((Validator) target.getValue().get());}return validators;
    }// 返回 ConfigurationPropertiesJsr303Validator 对象
    private Validator getJsr303Validator() {if (this.jsr303Validator == null) {this.jsr303Validator = new ConfigurationPropertiesJsr303Validator(this.applicationContext);}return this.jsr303Validator;
    }
    

     

    • 三个来源。
  • <3> 处,调用 #getBindHandler(ConfigurationProperties annotation, List<Validator> validators) 方法,获得 BindHandler 对象。代码如下:

    // ConfigurationPropertiesBinder.javaprivate BindHandler getBindHandler(ConfigurationProperties annotation, List<Validator> validators) {BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();// 如果有 ignoreInvalidFields 属性,进一步包装成 IgnoreErrorsBindHandler 类if (annotation.ignoreInvalidFields()) {handler = new IgnoreErrorsBindHandler(handler);}// 如果否 ignoreUnknownFields 属性,进一步包装成 NoUnboundElementsBindHandler 类if (!annotation.ignoreUnknownFields()) {UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();handler = new NoUnboundElementsBindHandler(handler, filter);}// <X> 如果 Validator 数组非空,进一步包装成 ValidationBindHandler 对象if (!validators.isEmpty()) {handler = new ValidationBindHandler(handler, validators.toArray(new Validator[0]));}// <Y> 如果有 ConfigurationPropertiesBindHandlerAdvisor 元素,则进一步处理 handler 对象for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {handler = advisor.apply(handler);}return handler;
    }private List<ConfigurationPropertiesBindHandlerAdvisor> getBindHandlerAdvisors() {return this.applicationContext.getBeanProvider(ConfigurationPropertiesBindHandlerAdvisor.class).orderedStream().collect(Collectors.toList());
    }
    

     

    • <X> 处,通过将 handler 包装成 ValidationBindHandler 对象,从而实现 Validator 功能的提供。
    • <Y> 处,此处的 org.springframework.boot.context.properties.ConfigurationPropertiesBindHandlerAdvisor 接口,通过实现它,并注册到 Spring 容器中,可以对 handler 进一步处理。😈 当然,大多数情况下,包括 Spring Boot 也并未提供其实现,我们不需要这么做。所以呢,这块我们又可以无视落。
    • 🙂 另外,关于 BindHandler 是什么,我们先不用去研究。后续,我们放在另外的文章,来慢慢讲解~
  • <4> 处,调用 #getBinder() 方法,获得 Binder 对象。代码如下:

    // ConfigurationPropertiesBinder.javaprivate Binder getBinder() {if (this.binder == null) {// 创建 Binder 对象this.binder = new Binder(getConfigurationPropertySources(),getPropertySourcesPlaceholdersResolver(),getConversionService(),getPropertyEditorInitializer());}return this.binder;
    }private Iterable<ConfigurationPropertySource> getConfigurationPropertySources() {return ConfigurationPropertySources.from(this.propertySources);
    }private PropertySourcesPlaceholdersResolver getPropertySourcesPlaceholdersResolver() {return new PropertySourcesPlaceholdersResolver(this.propertySources);
    }private ConversionService getConversionService() {return new ConversionServiceDeducer(this.applicationContext).getConversionService(); // <X>
    }private Consumer<PropertyEditorRegistry> getPropertyEditorInitializer() {if (this.applicationContext instanceof ConfigurableApplicationContext) {return ((ConfigurableApplicationContext) this.applicationContext).getBeanFactory()::copyRegisteredEditorsTo;}return null;
    }
    

     

    • <X> 处,创建 ConversionServiceDeducer 创建,然后调用 ConversionServiceDeducer#getConversionService() 方法,获得 ConversionService 对象。ConversionService 是 Spring 中,用来作为类型转换器的。关于 org.springframework.boot.context.properties.ConversionServiceDeducer 类,胖友点击链接,简单看看即可。当然,也可以不看~
  • <4> 处,调用 Binder#bind(String name, Bindable<T> target, BindHandler handler) 方法,执行绑定逻辑,处理 @ConfigurationProperties 注解的 Bean 的属性的注入。😈 至此,撒花~

4.3 ConfigurationPropertiesJsr303Validator

org.springframework.boot.context.properties.ConfigurationPropertiesJsr303Validator ,实现 Validator 接口,@ConfigurationProperties + @Validated 注解的 Bean 的 JSR303 的 Validator 实现类。其类上的注释如下:

 

// ConfigurationPropertiesJsr303Validator.java/*** Validator that supports configuration classes annotated with* {@link Validated @Validated}.*/

 

4.3.1 构造方法

 

// ConfigurationPropertiesJsr303Validator.javaprivate final Delegate delegate;ConfigurationPropertiesJsr303Validator(ApplicationContext applicationContext) {this.delegate = new Delegate(applicationContext);
}private static class Delegate extends LocalValidatorFactoryBean {Delegate(ApplicationContext applicationContext) {// 设置 applicationContext 属性setApplicationContext(applicationContext);// 设置 messageInterpolator 属性setMessageInterpolator(new MessageInterpolatorFactory().getObject());// 回调 afterPropertiesSet 方法afterPropertiesSet();}}

 

4.3.2 isJsr303Present

#isJsr303Present(ApplicationContext applicationContext) 方法,校验是否支持 JSR303 。代码如下:

 

// ConfigurationPropertiesJsr303Validator.javaprivate static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator","javax.validation.ValidatorFactory","javax.validation.bootstrap.GenericBootstrap" };public static boolean isJsr303Present(ApplicationContext applicationContext) {ClassLoader classLoader = applicationContext.getClassLoader();for (String validatorClass : VALIDATOR_CLASSES) {if (!ClassUtils.isPresent(validatorClass, classLoader)) {return false;}}return true;
}

 

  • 通过判断,是否引入了相关的依赖。

4.3.3 supports

实现 #supports(Class<?> type) 方法,判断是否支持指定类的校验。代码如下:

 

// ConfigurationPropertiesJsr303Validator.java@Override
public boolean supports(Class<?> type) {return this.delegate.supports(type);
}

 

4.3.4 validate

实现 #validate(Object target, Errors errors) 方法,执行校验。代码如下:

 

// ConfigurationPropertiesJsr303Validator.java@Override
public void validate(Object target, Errors errors) {this.delegate.validate(target, errors);
}

 

666. 彩蛋

呼呼,终于写了一篇相对短一点的文章,舒服~关于本文看到的 Binder、BinderHandler、Bindable 等等类,属于 org.springframework.boot.context.properties.bind 包,后续我们根据需要,会对这块在进行详细的解析~

参考和推荐如下文章:

  • oldflame-Jm 《Spring boot源码分析-ConfigurationProperties》
  • 梦想2018 《spring @EnableConfigurationProperties 实现原理》
  • 一个努力的码农
    • 《spring boot 源码解析13-@ConfigurationProperties是如何生效的》
    • 《spring boot 源码解析14-默认错误页面处理流程, 自定义,及EnableAutoConfigurationImportSelector处理》
Yestar123456
发布了27 篇原创文章 · 获赞 0 · 访问量 88
私信关注
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 构建业务基础镜像

    tomcat 镜像#构建tomcat 基础镜像 mkdir /tmp/tomcat #将apache-tomcat-8.0.46.tar.gz 包放在路径下cat >> /tmp/tomcat/Dockerfile << EOF FROM centos:7 MAINTAINER mysql.gift/test/tomcat:v1RUN yum install unzip iproute -yENV JAVA_HOME /usr/local/jdkADD …...

    2024/4/25 14:05:43
  2. linux切换普通用户遇bash-4.1解决办法

    linux切换普通用户遇bash-4.1解决办法修改vi /etc/passwd 将登陆环境变更为/bin/bash复制配置 cp -a /etc/skel/. /home/nice再次su nice既可解决问题点赞收藏分享文章举报小尬鸟发布了2 篇原创文章 获赞 0 访问量 3私信关注...

    2024/4/24 1:27:11
  3. 玩坏docker笔记(八):Docker容器+常用指令

    容器启动时执行命令的三种方式:1)CMD命令,2)ENTRYPOINT指令 ,3)在docker run命令中指定 docker run ubuntu pwd //容器启动时,执行pwd命令执行docker ps或docker container ls 可以查看docker host中当前运行的容器;执行docker ps -a 或dokcer container ls -a可以查看…...

    2024/4/26 22:04:34
  4. docker-compose 基础

    安装#安装 curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose#yum yum install docker-compose说明#编写YAML文件 #https:/…...

    2024/4/23 4:58:19
  5. StringBuffer/StringBuilder

    1、字符串缓冲区\字符串建造者,存放了一个String(char数组),但是可以修改(没有被final修饰) 2、Buffer是线程安全的,Builder是线程不安全的 3、常用方法:1.构造方法:StringBuilder()/StringBuilder(String str) 2.append(String str):在字符串缓冲区后追加一个字符串 …...

    2024/4/19 13:47:51
  6. 开心一记 (vue页面css)优先级

    在应用element的时候遇到需要自己写样式的button实现效果上传前文字为蓝色,背景图标为同色按钮,点击后文字颜色改变,图标更换。HTML:<el-button :disabled="is_upload" :class="{after_upload:is_upload}" @click="submit">请上传.pdf…...

    2024/4/23 4:35:18
  7. 详解Python迭代器,生成器,装饰器

    文章目录迭代器生成器装饰器 迭代器简介:迭代器是python里面可以记住遍历位置的对象,迭代器只能往前不能往后,使用iter()创建一个迭代器,使用next()返回一个迭代器里面的元素。 应用场景:数列的数据规模巨大,或者数列有规律,但是通过列表推导式推导不出来#!/usr/local/b…...

    2024/4/26 1:21:14
  8. 精尽 Spring 学习指南

    精尽 Spring 学习指南1. 视频😈 记得艿艿当年学习 Spring ,还是看的马士兵老师的。有握爪的同学么?嘻嘻。《Spring XML 视频教程全集》 一共有 36P 。推荐~记得 b 站【收藏 + 点赞 + 投币】素质三连噢。虽然版本是 Spring4 ,和 Spring5 实际差距没这么大。和《尚硅谷 Spri…...

    2024/4/23 4:34:55
  9. MySQL 8.0.11免安装版配置步骤

    需要该版本的童鞋可以直接在我这里下载,下载地址为https://download.csdn.net/download/xj627141903/10457871 1.下载MySQL 8.0.11版本的mysql,官方地址 2.下载后,将解压出来的文件放到你想放到的磁盘处(不想遇到管理员权限问题的童鞋那就尽量不要放到C盘),我下载解压后放…...

    2024/4/23 4:35:07
  10. 银行卡,IC卡,接触/非接触卡,CPU卡,NFC小结

    https://blog.csdn.net/kangear/article/details/50924601点赞收藏分享文章举报TomWang-0214发布了21 篇原创文章 获赞 8 访问量 1万+私信关注...

    2024/4/25 11:51:57
  11. Java 版本升级频繁Java 版本升级频繁

    Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁Java 版本升级频繁J…...

    2024/4/24 20:50:46
  12. 架构设计028 画图 实战三

    pass点赞收藏分享文章举报ailinyingai发布了295 篇原创文章 获赞 6 访问量 4万+他的留言板关注...

    2024/4/24 0:33:20
  13. 服务器常用的操作系统如何选择?116.211.143.X

    服务器常用的操作系统如何选择? 建站需要用服务器,在服务器选购过程中,除了常见的CPU、内存、硬盘、带宽等配置以外,用户还需要自行选择操作系统,大多数服务器都使用Windows Server或Linux操作系统 Windows系统 Windows Server是专为服务器设计的专有操作系统,包括Micros…...

    2024/4/26 23:15:10
  14. 框架day11-VUE.JS

    点赞收藏分享文章举报饿饿饿魔发布了16 篇原创文章 获赞 1 访问量 122私信关注...

    2024/4/20 2:00:53
  15. web前端开发就业趋势是什么?前端这两个岗位很有前途

    web前端开发就业趋势是什么?下面就跟着小编一起来看看吧! 随着互联网的飞速发展和人们对于网页简便性要求,越来越多的大型企业开始使用web前端技术。目前还没有一个前端的开发语言能取代 html5的位置,所以说,无论你是做手机网站还是在手机app应用,前端的样式都是html5开发…...

    2024/4/25 10:10:39
  16. CSDN-markdown编辑器

    Markdown编辑 欢迎使用Markdown编辑 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdow…...

    2024/4/23 21:54:35
  17. 我有故事,你有酒吗??

    自从上班以后,一直过着一种长期单一模式的生活。早上6点睁开眼睛,在床上翻滚着叫嚣着好困呀,不想上班不想上班,但是最后还是不得不乖乖起床,然后洗脸刷牙抹BB。6点45左右开始出发,冲忙的赶往地铁,如果幸运的话,有时候刚好可以赶上7点03分的地铁,这时候的地铁还是很宽松…...

    2024/4/23 4:36:12
  18. server_name指令用法

    点赞收藏分享文章举报albert48发布了34 篇原创文章 获赞 1 访问量 9405私信关注...

    2024/4/26 11:34:26
  19. 精尽 Spring Boot 源码分析 —— Condition

    精尽 Spring Boot 源码分析 —— Condition1. 概述在前面的文章,我们已经看过 Spring Boot 如何实现自动配置的功能,但是,实际场景下,这显然不够。为什么呢?因为每个框架的配置,需要满足一定的条件,才应该进行自动配置。这时候,我们很自然就可以想到 Spring Boot 的 Co…...

    2024/4/23 4:35:56
  20. 微信小程序获取元素宽高、窗口高度

    微信小程序获取元素宽高、窗口高度//窗口的高度wx.getSystemInfo({success: (res, rect) => {this.setData({winH: res.windowHeight})}})const query = wx.createSelectorQuery()query.select(.lpsw).boundingClientRect()query.selectViewport().scrollOffset()query.exec…...

    2024/4/20 14:29:06

最新文章

  1. 【C语言必刷题】7. 百钱百鸡

    &#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…...

    2024/4/26 23:36:55
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 如何转行成为产品经理?

    转行NPDP也是很合适的一条发展路径&#xff0c;之后从事新产品开发相关工作~ 一、什么是NPDP&#xff1f; NPDP 是产品经理国际资格认证&#xff0c;美国产品开发与管理协会&#xff08;PDMA&#xff09;发起的&#xff0c;是目前国际公认的唯一的新产品开发专业认证&#xff…...

    2024/4/22 16:14:13
  4. Oracle 正则表达式

    一、Oracle 正则表达式相关函数 (1) regexp_like &#xff1a;同 like 功能相似&#xff08;模糊 匹配&#xff09; (2) regexp_instr &#xff1a;同 instr 功能相似&#xff08;返回字符所在 下标&#xff09; (3) regexp_substr &#xff1a; 同 substr 功能相似&…...

    2024/4/26 12:58:22
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/26 20:12:18
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/25 18:39:23
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/25 18:39:22
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/25 18:39:22
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/26 21:56:58
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/25 16:48:44
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/26 16:00:35
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/26 22:01:59
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/25 18:38:58
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/25 18:38:57
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57