① 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依次查找,如找到该属性则取其值。
那么是否能够说明了配置文件的加载次序呢?
(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"> * @Configuration * @PropertySource("classpath:/com/myco/app.properties") * public class AppConfig { * @Autowired * Environment env; * * @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"> * @Configuration * @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties") * public class AppConfig { * @Autowired * Environment env; * * @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"> * @Configuration * @PropertySource("classpath:/com/myco/a.properties") * public class ConfigA { } * * @Configuration * @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. **/*.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 { //... }
(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中查找;
- ③ 如果①没有找到,②找到了,则使用②的值。否则使用①的值。