① 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">
* @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 {
//...
}
大概就是在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中查找;
- ③ 如果①没有找到,②找到了,则使用②的值。否则使用①的值。