SpringBoot中yml与properties配置文件及bean取值赋值

① SpringBoot的配置文件

SpringBoot使用一个全局的配置文件,配置文件名是固定的 :

  • application.properties
  • application.yml

配置文件的作用 : 修改SpringBoot自动配置的默认值(SpringBoot在底层已经配置好的属性)。


② yml 是什么?

YAML(YAML Ain’t Markup Language)
YAML A Markup Language:是一个标记语言
YAML isn’t Markup Language:不是一个标记语言;

YAML:以数据为中心,比json、xml等更适合做配置文件。

配置例子-更改Tomcat默认端口 :

server:
  port:8081

③ YAML语法

YAML使用缩进表示层级关系,缩进时不允许使用Tab键,只允许使用空格。缩进的空格的数目不重要,只要相同层级的元素左侧对齐即可。

YAML支持三种数据结构 :

  • 对象 : 键值对的集合;
  • 数组 : 一组按次序排列的值;
  • 字面量 : 单个的不可分割的值。

语法格式如下 :

k:(空格)v;表示一对键值对,空格必须有。

server:
  port: 8082

④ YAML值的写法

一般值有三种类型 : 字面量,即普通的值(数字,字符串,布尔);对象、map(键值对);数组(list set)。

(4.1)字面量

k: v:字面直接来写;

字符串默认不用加上单引号或者双引号;

“”:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思

name: "zhangsan \n lisi";输出:zhangsan 换行 lisi;

‘’:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据

name: ‘zhangsan \n lisi’; 输出:zhangsan \n lisi

(4.2)对象、map

k: v:在下一行来写对象的属性和值的关系;注意缩进
对象还是k: v的方式

friends:
	lastName: zhangsan
	age: 20

行内写法:

friends: {lastName: zhangsan,age: 18}

(4.3)数组

用- 值表示数组中的一个元素

pets:
‐ cat
‐ dog
‐ pig

行内写法

pets: [cat,dog,pig]

⑤ 使用yml为bean赋值

(5.1) 添加文件处理器依赖

官网如下 :
https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/reference/html/configuration-metadata.html#configuration-metadata-annotation-processor

<!‐‐导入配置文件处理器,配置文件进行绑定就会有提示‐‐>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
	<optional>true</optional>
</dependency>

这里写图片描述


(5.2)JavaBean注解配置

@ConfigurationProperties:告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定。

prefix = "person":配置文件中哪个下面的所有属性进行一一映射。

该注解默认从全局配置文件中取值。

/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
* @ConfigurationProperties:告诉SpringBoot将本类中的所有属性
* 和配置文件中相关的配置进行绑定;
* prefix = "person":配置文件中哪个下面的所有属性进行一一映射
* 只有这个组件是容器中的组件,才能容器提供的@ConfigurationProperties功能;
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
	private String lastName;
	private Integer age;
	private Boolean boss;
	private Date birth;
	private Map<String,Object> maps;
	private List<Object> lists;
	private Dog dog;
	//...
	// 注意,@ConfigurationProperties该注解时不用再使用@Value!!!
}

(5.3)编写yml配置文件

server:
  port: 8082

person:
  lastName: hello
  age: 18
  boss: false
  birth: 2017/12/12
  maps: {k1: v1,k2: 12}
  lists:
    ‐ lisi
    ‐ zhaoliu
  dog:
    name: 小狗
    age: 12

(5.4)使用SpringBoot 测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbooHelloworldQuickApplicationTests {

	@Autowired
	Person person;

	@Test
	public void contextLoads() {
		System.out.println(person);
	}

}

测试结果如下所示 :

这里写图片描述

Spring容器中的bean被yml中的配置正确赋值!


(5.5)使用@ConfigurationProperties注解的同时使用@Value

@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    @Value("${person.age}")
    private String lastName;
    //...
}

如上所示,使用@Value为属性赋值别的变量的值。测试结果表明仍然为@ConfigurationProperties该注解为lastName赋值,@Value注解此时不会起作用。

这里写图片描述


⑥ properties配置文件(仍旧使用@ConfigurationProperties)

properties配置文件是以前项目中常用的配置文件,SpringBoot同样保留了该种类型的配置文件。

如下所示,在properties文件中为person赋值:

# idea
person.age=21
person.birth=2018/11/11
person.last-name=小明
person.boss=true
person.dog.name=dog
person.dog.age=1
person.maps.k1=v1
person.maps.k2=v2
person.lists=a,b,c

运行测试 :

这里写图片描述

正常获取到值,但是中文乱码,为什么?

应该项目中都遇到这样的例子,读取properties文件中的中文乱码。以前解决办法就是将properties文件中的中文转换为Unicode形式,如\u822a\u6bcd。或者将其重新编码再解码。

idea中解决方法如下图:

  • 将其转换为ASCII,其与Unicode是可以相互转换的。

这里写图片描述

再次测试:

这里写图片描述


⑦ @Value为bean赋值

在以前的项目中常用为bean赋值(从properties文件中获取值)的方法有两种。

第一种-xml配置

示例如下

 <bean id="urlModel" class="com.hh.core.model.UrlModel" >
    	<property name="url" value="${url}"></property>
  </bean>  	

第二种-@Value

@Component
public class Person {

    /**
     * <bean class="Person">
     *      <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property>
     * <bean/>
     */

   //lastName必须是邮箱格式
   // @Email// 不支持JSR303校验
    @Value("${person.last-name}")//键必须与properties文件中的一致
    private String lastName;
    @Value("#{11*2}")//支持SpELl语法
    private Integer age;
    @Value("true")
    private Boolean boss;

    private Date birth;
   // @Value("${person.maps}")//不支持复杂类型封装
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    //...
}

@Value默认从系统环境中加载属性变量。比如application.yml中配置了person.lastName属性,那么就可以使用@Value直接为Person的lastName赋值。


@Value注入map或者list

配置文件如下:


list: topic1,topic2,topic3
maps: "{key1: 'value1', key2: 'value2'}"

注入实例如下:

@Value("#{'${list}'.split(',')}")
private List<String> list;
 
@Value("#{${maps}}")  
private Map<String,String> maps;

⑧ @Value获取值和@ConfigurationProperties获取值比较

松散绑定 :

–person.firstName:使用标准方式
–person.first-name:大写用-
–person.first_name:大写用_
–PERSON_FIRST_NAME:
.推荐系统属性使用这种写法

上面几种写法在@ConfigurationProperties环境下都可以对应到person对象的firstName属性。@Value则必须保证取的键与properties文件中一致。


JSR303校验:

@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {

    //lastName必须是邮箱格式
//    @Value("${person.last-name}")
    @Email
    private String lastName;
    //...
}

@Value 不支持JSR3030校验,但是支持SpELl语法,@ConfigurationProperties则相反。


对比总结如下:

这里写图片描述

总结如下:

配置文件yml还是properties他们都能获取到值;

如果说,我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;

如果说,我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties;

那么能否在业务逻辑类中使用@ConfigurationProperties呢?

不建议使用,@ConfigurationProperties会将本类中的所有属性和配置文件中相关的配置进行绑定。尤其@ConfigurationProperties支持松散语法。


⑨ @PropertySource加载额外的配置文件

将一切配置全部写在全局配置文件中,是不可想象的。项目中不可避免存在多个配置文件。

@PropertySource就可以根据需要加载指定的配置文件(@ConfigurationProperties 默认从全局配置文件获取配置),将配置文件中的属性注入到系统环境中。

这里将person的属性配置单独写在person.properties文件中,并从全局配置文件中注释掉person的属性配置。

这里写图片描述


Person中使用@Value为属性赋值:

@PropertySource(value = {"classpath:person.properties"})
@Component
public class Person {

    /**
     * <bean class="Person">
     *      <property name="lastName" value="字面量/${key}从环境变量、配置文件中获取值/#{SpEL}"></property>
     * <bean/>
     */

   //lastName必须是邮箱格式
   // @Email
    @Value("${person.last-name}")
    private String lastName;
    @Value("${person.age}")
    private Integer age;
    @Value("true")
    private Boolean boss;
	@Value("${person.birth}")
    private Date birth;
    @Value("${person.maps}")
    private Map<String,Object> maps;
    @Value("${person.lists}")
    private List<Object> lists;
    @Value("${person.dog}")
    private Dog dog;

为了对比person的属性从不同配置文件赋值,这里将全局配置文件中保留person.lastName属姓配置。

测试如下:

这里写图片描述

分析可知,默认从全局配置文件中为person赋值,这里为lastName赋值小明。person的其他属性从person.properties文件中获取。

同时使用@PropertySource和@ConfigurationProperties注解,则默认属性仍旧从全局配置文件寻找,其次从@PropertySource指定的配置文件寻找。而且Person中的属性 不用 再使用@Value为其赋值。

@PropertySource(value = {"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String lastName;
    private Integer age;
    private Boolean boss;
	//...
}

application.properties如下图:
这里写图片描述
person.properties如下图:
这里写图片描述
测试结果如下图:

这里写图片描述


⑩ 多个配置文件同属性配置@Value取值

如果项目中不同配置文件中配置同属性,使用@Value该如何取值呢?这就涉及到了配置文件加载优先级的问题。

如图一:

这里写图片描述

application.properties与person.properties同时配置了属性person.last-name,如图中所示,使用@Value取值,此时取到的值为application.properties中的值。

若将application.properties中的person.last-name注释掉,则取的为person.properties(Person类配置了@PropertySource)值。


那么是否说明,默认从全局配置文件取还是按照上下顺序依次检查呢?

如图二所示:

这里写图片描述

修改application.properties为tapplication.properties文件,将会从application.yml到tapplication.properties依次查找,如找到该属性则取其值。

那么是否能够说明了配置文件的加载次序呢?

参考博客:SpringBoot配置文件加载位置与优先级


(11)@ImportResource导入Spring配置xml文件

Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,也不能自动识别。那么如何使用我们自己编写的配置文件呢?

在主配置类添加@ImportResource注解,如下图:

这里写图片描述

配置文件内容如下:

 <bean id="helloService" class="com.web.service.HelloService"/>

测试如下:

这里写图片描述

在主配置类使用ImportResource引入自定义Spring配置文件,即可获取helloService bean。


(12)SpringBoot推荐给容器中添加组件的方式

SpringBoot推荐使用配置类的方式来给容器中添加组件。如下所示:

@Configuration
public class MyAppConfig {

    //将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
    @Bean
    public HelloService helloService(){
        System.out.println("配置类@Bean给容器中添加组件了...");
        return new HelloService();
    }
}

测试结果如下图:
这里写图片描述


(13)配置文件占位符

除了前面说的几种方式,还可以使用占位符的方式在配置文件中为属性赋值。

① 随机数

示例如下:

person.last‐name=张三${random.uuid}

② 占位符获取之前配置的值,如果没有可以是用:指定默认值

示例如下:

person.last‐name=张三${random.uuid}
person.age=${random.int}
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.hello:hello}_dog
person.dog.age=15

${person.hello:hello}意思为如果person.hello没有值则默认赋值为hello。${person.hello}如果有值,则取值,无值则会当作字面量解析–${person.hello}原样赋值。


(14)@PropertySource和@ConfigurationProperties以及@Value

这三个注解究竟是什么,做了什么?

① @Value

源码如下:

/**
 * Annotation at the field or method/constructor parameter level
 * that indicates a default value expression for the affected argument.
 *
 * <p>Typically used for expression-driven dependency injection. Also supported
 * for dynamic resolution of handler method parameters, e.g. in Spring MVC.
 *
 * <p>A common use case is to assign default field values using
 * "#{systemProperties.myProp}" style expressions.
 *
 * <p>Note that actual processing of the {@code @Value} annotation is performed
 * by a {@link org.springframework.beans.factory.config.BeanPostProcessor
 * BeanPostProcessor} which in turn means that you <em>cannot</em> use
 * {@code @Value} within
 * {@link org.springframework.beans.factory.config.BeanPostProcessor
 * BeanPostProcessor} or
 * {@link org.springframework.beans.factory.config.BeanFactoryPostProcessor BeanFactoryPostProcessor}
 * types. Please consult the javadoc for the {@link AutowiredAnnotationBeanPostProcessor}
 * class (which, by default, checks for the presence of this annotation).
 *
 * @author Juergen Hoeller
 * @since 3.0
 * @see AutowiredAnnotationBeanPostProcessor
 * @see Autowired
 * @see org.springframework.beans.factory.config.BeanExpressionResolver
 * @see org.springframework.beans.factory.support.AutowireCandidateResolver#getSuggestedValue
 */
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {

	/**
	 * The actual value expression: e.g. "#{systemProperties.myProp}".
	 */
	String value();

}

言简意赅说一下,这个注解可以动态为属性赋值,处理过程是BeanPostProcessor!


② @PropertySource

源码如下:

/**
 * Annotation providing a convenient and declarative mechanism for adding a
 * {@link org.springframework.core.env.PropertySource PropertySource} to Spring's
 * {@link org.springframework.core.env.Environment Environment}. To be used in
 * conjunction with @{@link Configuration} classes.
 *
 * <h3>Example usage</h3>
 *
 * <p>Given a file {@code app.properties} containing the key/value pair
 * {@code testbean.name=myTestBean}, the following {@code @Configuration} class
 * uses {@code @PropertySource} to contribute {@code app.properties} to the
 * {@code Environment}'s set of {@code PropertySources}.
 *
 * <pre class="code">
 * &#064;Configuration
 * &#064;PropertySource("classpath:/com/myco/app.properties")
 * public class AppConfig {
 *     &#064;Autowired
 *     Environment env;
 *
 *     &#064;Bean
 *     public TestBean testBean() {
 *         TestBean testBean = new TestBean();
 *         testBean.setName(env.getProperty("testbean.name"));
 *         return testBean;
 *     }
 * }</pre>
 *
 * Notice that the {@code Environment} object is @{@link
 * org.springframework.beans.factory.annotation.Autowired Autowired} into the
 * configuration class and then used when populating the {@code TestBean} object. Given
 * the configuration above, a call to {@code testBean.getName()} will return "myTestBean".
 *
 * <h3>Resolving ${...} placeholders in {@code <bean>} and {@code @Value} annotations</h3>
 *
 * In order to resolve ${...} placeholders in {@code <bean>} definitions or {@code @Value}
 * annotations using properties from a {@code PropertySource}, one must register
 * a {@code PropertySourcesPlaceholderConfigurer}. This happens automatically when using
 * {@code <context:property-placeholder>} in XML, but must be explicitly registered using
 * a {@code static} {@code @Bean} method when using {@code @Configuration} classes. See
 * the "Working with externalized values" section of @{@link Configuration}'s javadoc and
 * "a note on BeanFactoryPostProcessor-returning @Bean methods" of @{@link Bean}'s javadoc
 * for details and examples.
 *
 * <h3>Resolving ${...} placeholders within {@code @PropertySource} resource locations</h3>
 *
 * Any ${...} placeholders present in a {@code @PropertySource} {@linkplain #value()
 * resource location} will be resolved against the set of property sources already
 * registered against the environment. For example:
 *
 * <pre class="code">
 * &#064;Configuration
 * &#064;PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
 * public class AppConfig {
 *     &#064;Autowired
 *     Environment env;
 *
 *     &#064;Bean
 *     public TestBean testBean() {
 *         TestBean testBean = new TestBean();
 *         testBean.setName(env.getProperty("testbean.name"));
 *         return testBean;
 *     }
 * }</pre>
 *
 * Assuming that "my.placeholder" is present in one of the property sources already
 * registered, e.g. system properties or environment variables, the placeholder will
 * be resolved to the corresponding value. If not, then "default/path" will be used as a
 * default. Expressing a default value (delimited by colon ":") is optional.  If no
 * default is specified and a property cannot be resolved, an {@code
 * IllegalArgumentException} will be thrown.
 *
 * <h3>A note on property overriding with @PropertySource</h3>
 *
 * In cases where a given property key exists in more than one {@code .properties}
 * file, the last {@code @PropertySource} annotation processed will 'win' and override.
 *
 * For example, given two properties files {@code a.properties} and
 * {@code b.properties}, consider the following two configuration classes
 * that reference them with {@code @PropertySource} annotations:
 *
 * <pre class="code">
 * &#064;Configuration
 * &#064;PropertySource("classpath:/com/myco/a.properties")
 * public class ConfigA { }
 *
 * &#064;Configuration
 * &#064;PropertySource("classpath:/com/myco/b.properties")
 * public class ConfigB { }
 * </pre>
 *
 * The override ordering depends on the order in which these classes are registered
 * with the application context.
 * <pre class="code">
 * AnnotationConfigApplicationContext ctx =
 *     new AnnotationConfigApplicationContext();
 * ctx.register(ConfigA.class);
 * ctx.register(ConfigB.class);
 * ctx.refresh();
 * </pre>
 *
 * In the scenario above, the properties in {@code b.properties} will override any
 * duplicates that exist in {@code a.properties}, because {@code ConfigB} was registered
 * last.
 *
 * <p>In certain situations, it may not be possible or practical to tightly control
 * property source ordering when using {@code @ProperySource} annotations. For example,
 * if the {@code @Configuration} classes above were registered via component-scanning,
 * the ordering is difficult to predict. In such cases - and if overriding is important -
 * it is recommended that the user fall back to using the programmatic PropertySource API.
 * See {@link org.springframework.core.env.ConfigurableEnvironment ConfigurableEnvironment}
 * and {@link org.springframework.core.env.MutablePropertySources MutablePropertySources}
 * javadocs for details.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Phillip Webb
 * @since 3.1
 * @see PropertySources
 * @see Configuration
 * @see org.springframework.core.env.PropertySource
 * @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources()
 * @see org.springframework.core.env.MutablePropertySources
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

	/**
	 * Indicate the name of this property source. If omitted, a name will
	 * be generated based on the description of the underlying resource.
	 * @see org.springframework.core.env.PropertySource#getName()
	 * @see org.springframework.core.io.Resource#getDescription()
	 */
	String name() default "";

	/**
	 * Indicate the resource location(s) of the properties file to be loaded.
	 * For example, {@code "classpath:/com/myco/app.properties"} or
	 * {@code "file:/path/to/file"}.
	 * <p>Resource location wildcards (e.g. *&#42;/*.properties) are not permitted;
	 * each location must evaluate to exactly one {@code .properties} resource.
	 * <p>${...} placeholders will be resolved against any/all property sources already
	 * registered with the {@code Environment}. See {@linkplain PropertySource above}
	 * for examples.
	 * <p>Each location will be added to the enclosing {@code Environment} as its own
	 * property source, and in the order declared.
	 */
	String[] value();

	/**
	 * Indicate if failure to find the a {@link #value() property resource} should be
	 * ignored.
	 * <p>{@code true} is appropriate if the properties file is completely optional.
	 * Default is {@code false}.
	 * @since 4.0
	 */
	boolean ignoreResourceNotFound() default false;

	/**
	 * A specific character encoding for the given resources, e.g. "UTF-8".
	 * @since 4.3
	 */
	String encoding() default "";

	/**
	 * Specify a custom {@link PropertySourceFactory}, if any.
	 * <p>By default, a default factory for standard resource files will be used.
	 * @since 4.3
	 * @see org.springframework.core.io.support.DefaultPropertySourceFactory
	 * @see org.springframework.core.io.support.ResourcePropertySource
	 */
	Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

}

言简意赅说明一下,该注解将制定文件中的key/value形式的属性配置注入到了Environment中!


③ @ConfigurationProperties

@ConfigurationProperties源码如下:

/**
 * 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()}.
	 * @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()}.
	 * @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 fields with periods in their
	 * names should be ignored.
	 * @return the flag value (default false)
	 */
	boolean ignoreNestedProperties() 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;

	/**
	 * Flag to indicate that an exception should be raised if a Validator is available,
	 * the class is annotated with {@link Validated @Validated} and validation fails. If
	 * it is set to false, validation errors will be swallowed. They will be logged, but
	 * not propagated to the caller.
	 * @return the flag value (default true)
	 * @deprecated as of 1.5 since validation only kicks in when {@code @Validated} is
	 * present
	 */
	@Deprecated
	boolean exceptionIfInvalid() default true;

}

功能自己看源码上面的javadoc,这里需要注意的是ConfigurationPropertiesBindingPostProcessor。

很熟悉吧,又是一个后置处理器!

ConfigurationPropertiesBindingPostProcessor源码如下:

public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
		BeanFactoryAware, EnvironmentAware, ApplicationContextAware, InitializingBean,
		DisposableBean, ApplicationListener<ContextRefreshedEvent>, PriorityOrdered {

	/**
	 * The bean name of the configuration properties validator.
	 */
	public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";

	private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator",
			"javax.validation.ValidatorFactory",
			"javax.validation.bootstrap.GenericBootstrap" };

	private static final Log logger = LogFactory
			.getLog(ConfigurationPropertiesBindingPostProcessor.class);

	private ConfigurationBeanFactoryMetaData beans = new ConfigurationBeanFactoryMetaData();

	private PropertySources propertySources;

	private Validator validator;

	private volatile Validator localValidator;

	private ConversionService conversionService;

	private DefaultConversionService defaultConversionService;

	private BeanFactory beanFactory;

	private Environment environment = new StandardEnvironment();

	private ApplicationContext applicationContext;

	private List<Converter<?, ?>> converters = Collections.emptyList();

	private List<GenericConverter> genericConverters = Collections.emptyList();

	private int order = Ordered.HIGHEST_PRECEDENCE + 1;

	/**
	 * A list of custom converters (in addition to the defaults) to use when converting
	 * properties for binding.
	 * @param converters the converters to set
	 */
	@Autowired(required = false)
	@ConfigurationPropertiesBinding
	public void setConverters(List<Converter<?, ?>> converters) {
		this.converters = converters;
	}

	/**
	 * A list of custom converters (in addition to the defaults) to use when converting
	 * properties for binding.
	 * @param converters the converters to set
	 */
	@Autowired(required = false)
	@ConfigurationPropertiesBinding
	public void setGenericConverters(List<GenericConverter> converters) {
		this.genericConverters = converters;
	}

	/**
	 * Set the order of the bean.
	 * @param order the order
	 */
	public void setOrder(int order) {
		this.order = order;
	}

	/**
	 * Return the order of the bean.
	 * @return the order
	 */
	@Override
	public int getOrder() {
		return this.order;
	}

	/**
	 * Set the property sources to bind.
	 * @param propertySources the property sources
	 */
	public void setPropertySources(PropertySources propertySources) {
		this.propertySources = propertySources;
	}

	/**
	 * Set the bean validator used to validate property fields.
	 * @param validator the validator
	 */
	public void setValidator(Validator validator) {
		this.validator = validator;
	}

	/**
	 * Set the conversion service used to convert property values.
	 * @param conversionService the conversion service
	 */
	public void setConversionService(ConversionService conversionService) {
		this.conversionService = conversionService;
	}

	/**
	 * Set the bean meta-data store.
	 * @param beans the bean meta data store
	 */
	public void setBeanMetaDataStore(ConfigurationBeanFactoryMetaData beans) {
		this.beans = beans;
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		if (this.propertySources == null) {
			this.propertySources = deducePropertySources();
		}
		if (this.validator == null) {
			this.validator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class);
		}
		if (this.conversionService == null) {
			this.conversionService = getOptionalBean(
					ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME,
					ConversionService.class);
		}
	}

	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		freeLocalValidator();
	}

	@Override
	public void destroy() throws Exception {
		freeLocalValidator();
	}

	private void freeLocalValidator() {
		try {
			Validator validator = this.localValidator;
			this.localValidator = null;
			if (validator != null) {
				((DisposableBean) validator).destroy();
			}
		}
		catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
	}

	private PropertySources deducePropertySources() {
		PropertySourcesPlaceholderConfigurer configurer = getSinglePropertySourcesPlaceholderConfigurer();
		if (configurer != null) {
			// Flatten the sources into a single list so they can be iterated
			return new FlatPropertySources(configurer.getAppliedPropertySources());
		}
		if (this.environment instanceof ConfigurableEnvironment) {
			MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment)
					.getPropertySources();
			return new FlatPropertySources(propertySources);
		}
		// empty, so not very useful, but fulfils the contract
		logger.warn("Unable to obtain PropertySources from "
				+ "PropertySourcesPlaceholderConfigurer or Environment");
		return new MutablePropertySources();
	}

	private PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() {
		// Take care not to cause early instantiation of all FactoryBeans
		if (this.beanFactory instanceof ListableBeanFactory) {
			ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
			Map<String, PropertySourcesPlaceholderConfigurer> beans = listableBeanFactory
					.getBeansOfType(PropertySourcesPlaceholderConfigurer.class, false,
							false);
			if (beans.size() == 1) {
				return beans.values().iterator().next();
			}
			if (beans.size() > 1 && logger.isWarnEnabled()) {
				logger.warn("Multiple PropertySourcesPlaceholderConfigurer "
						+ "beans registered " + beans.keySet()
						+ ", falling back to Environment");
			}
		}
		return null;
	}

	private <T> T getOptionalBean(String name, Class<T> type) {
		try {
			return this.beanFactory.getBean(name, type);
		}
		catch (NoSuchBeanDefinitionException ex) {
			return null;
		}
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		ConfigurationProperties annotation = AnnotationUtils
				.findAnnotation(bean.getClass(), ConfigurationProperties.class);
		if (annotation != null) {
			postProcessBeforeInitialization(bean, beanName, annotation);
		}
		annotation = this.beans.findFactoryAnnotation(beanName,
				ConfigurationProperties.class);
		if (annotation != null) {
			postProcessBeforeInitialization(bean, beanName, annotation);
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		return bean;
	}

	@SuppressWarnings("deprecation")
	private void postProcessBeforeInitialization(Object bean, String beanName,
			ConfigurationProperties annotation) {
		Object target = bean;
		PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(
				target);
		factory.setPropertySources(this.propertySources);
		factory.setValidator(determineValidator(bean));
		// If no explicit conversion service is provided we add one so that (at least)
		// comma-separated arrays of convertibles can be bound automatically
		factory.setConversionService(this.conversionService == null
				? getDefaultConversionService() : this.conversionService);
		if (annotation != null) {
			factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
			factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
			factory.setExceptionIfInvalid(annotation.exceptionIfInvalid());
			factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties());
			if (StringUtils.hasLength(annotation.prefix())) {
				factory.setTargetName(annotation.prefix());
			}
		}
		try {
			factory.bindPropertiesToTarget();
		}
		catch (Exception ex) {
			String targetClass = ClassUtils.getShortName(target.getClass());
			throw new BeanCreationException(beanName, "Could not bind properties to "
					+ targetClass + " (" + getAnnotationDetails(annotation) + ")", ex);
		}
	}

	private String getAnnotationDetails(ConfigurationProperties annotation) {
		if (annotation == null) {
			return "";
		}
		StringBuilder details = new StringBuilder();
		details.append("prefix=").append(annotation.prefix());
		details.append(", ignoreInvalidFields=").append(annotation.ignoreInvalidFields());
		details.append(", ignoreUnknownFields=").append(annotation.ignoreUnknownFields());
		details.append(", ignoreNestedProperties=")
				.append(annotation.ignoreNestedProperties());
		return details.toString();
	}

	private Validator determineValidator(Object bean) {
		Validator validator = getValidator();
		boolean supportsBean = (validator != null && validator.supports(bean.getClass()));
		if (ClassUtils.isAssignable(Validator.class, bean.getClass())) {
			if (supportsBean) {
				return new ChainingValidator(validator, (Validator) bean);
			}
			return (Validator) bean;
		}
		return (supportsBean ? validator : null);
	}

	private Validator getValidator() {
		if (this.validator != null) {
			return this.validator;
		}
		if (this.localValidator == null && isJsr303Present()) {
			this.localValidator = new ValidatedLocalValidatorFactoryBean(
					this.applicationContext);
		}
		return this.localValidator;
	}

	private boolean isJsr303Present() {
		for (String validatorClass : VALIDATOR_CLASSES) {
			if (!ClassUtils.isPresent(validatorClass,
					this.applicationContext.getClassLoader())) {
				return false;
			}
		}
		return true;
	}

	private ConversionService getDefaultConversionService() {
		if (this.defaultConversionService == null) {
			DefaultConversionService conversionService = new DefaultConversionService();
			this.applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
			for (Converter<?, ?> converter : this.converters) {
				conversionService.addConverter(converter);
			}
			for (GenericConverter genericConverter : this.genericConverters) {
				conversionService.addConverter(genericConverter);
			}
			this.defaultConversionService = conversionService;
		}
		return this.defaultConversionService;
	}

	/**
	 * {@link LocalValidatorFactoryBean} supports classes annotated with
	 * {@link Validated @Validated}.
	 */
	private static class ValidatedLocalValidatorFactoryBean
			extends LocalValidatorFactoryBean {

		private static final Log logger = LogFactory
				.getLog(ConfigurationPropertiesBindingPostProcessor.class);

		ValidatedLocalValidatorFactoryBean(ApplicationContext applicationContext) {
			setApplicationContext(applicationContext);
			setMessageInterpolator(new MessageInterpolatorFactory().getObject());
			afterPropertiesSet();
		}

		@Override
		public boolean supports(Class<?> type) {
			if (!super.supports(type)) {
				return false;
			}
			if (AnnotatedElementUtils.hasAnnotation(type, Validated.class)) {
				return true;
			}
			if (type.getPackage() != null && type.getPackage().getName()
					.startsWith("org.springframework.boot")) {
				return false;
			}
			if (getConstraintsForClass(type).isBeanConstrained()) {
				logger.warn("The @ConfigurationProperties bean " + type
						+ " contains validation constraints but had not been annotated "
						+ "with @Validated.");
			}
			return true;
		}

	}

	/**
	 * {@link Validator} implementation that wraps {@link Validator} instances and chains
	 * their execution.
	 */
	private static class ChainingValidator implements Validator {

		private Validator[] validators;

		ChainingValidator(Validator... validators) {
			Assert.notNull(validators, "Validators must not be null");
			this.validators = validators;
		}

		@Override
		public boolean supports(Class<?> clazz) {
			for (Validator validator : this.validators) {
				if (validator.supports(clazz)) {
					return true;
				}
			}
			return false;
		}

		@Override
		public void validate(Object target, Errors errors) {
			for (Validator validator : this.validators) {
				if (validator.supports(target.getClass())) {
					validator.validate(target, errors);
				}
			}
		}

	}

	/**
	 * Convenience class to flatten out a tree of property sources without losing the
	 * reference to the backing data (which can therefore be updated in the background).
	 */
	private static class FlatPropertySources implements PropertySources {

		private PropertySources propertySources;

		FlatPropertySources(PropertySources propertySources) {
			this.propertySources = propertySources;
		}

		@Override
		public Iterator<PropertySource<?>> iterator() {
			MutablePropertySources result = getFlattened();
			return result.iterator();
		}

		@Override
		public boolean contains(String name) {
			return get(name) != null;
		}

		@Override
		public PropertySource<?> get(String name) {
			return getFlattened().get(name);
		}

		private MutablePropertySources getFlattened() {
			MutablePropertySources result = new MutablePropertySources();
			for (PropertySource<?> propertySource : this.propertySources) {
				flattenPropertySources(propertySource, result);
			}
			return result;
		}

		private void flattenPropertySources(PropertySource<?> propertySource,
				MutablePropertySources result) {
			Object source = propertySource.getSource();
			if (source instanceof ConfigurableEnvironment) {
				ConfigurableEnvironment environment = (ConfigurableEnvironment) source;
				for (PropertySource<?> childSource : environment.getPropertySources()) {
					flattenPropertySources(childSource, result);
				}
			}
			else {
				result.addLast(propertySource);
			}
		}

	}

}

可以发现这个类实现了我们很多眼熟的接口:

public class ConfigurationPropertiesBindingPostProcessor implements 
BeanPostProcessor,	BeanFactoryAware, EnvironmentAware, 
ApplicationContextAware, InitializingBean,DisposableBean, 
ApplicationListener<ContextRefreshedEvent>, PriorityOrdered {
//...
}

大概就是在bean的属性赋值、初始化前后进行的操作,具体参考博文bean的初始化和销毁过程详解

(15) springboot下外部xml文件引用application.properties配置

① 不适用context:property-placeholder或者PropertyPlaceholderConfigurer

在这里插入图片描述

如下所示,spring.xml文件尝试引用XXXX.properites中属性配置。

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
		http://www.springframework.org/schema/mvc 
		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.2.xsd 
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

	<bean id="sysUser" class="com.appraiseteach.entity.SysUser">
		<property name="field1" value="${com.jane.appid.test}"/>
	</bean>

</beans>

因为application.properties中配置了spring.profiles.active=env ,那么就会从application-env.properties查找com.jane.appid.test配置。否则,从application.properties中查找。

② 使用context:property-placeholder

<context:property-placeholder location="classpath:application-test.properties" />

<bean id="sysUser" class="com.appraiseteach.entity.SysUser">
	<property name="field1" value="${com.jane.appid}"/>
</bean>

此时的查找顺序

  • ① 从application-env.properties查找,如果找到不再往下查找;
  • ② 从application-test.properties中查找;
  • ③ 如果①没有找到,②找到了,则使用②的值。否则使用①的值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值