Spring Boot 使用一些松的规则来绑定属性到@ConfigurationProperties Bean 并且支持分层结构;
需要注意的是@ConfigurationProperties在1.4版本之后locations被启用,如果线上有之前配置升级之后会报错,解决办法见最后:参考 http://fabiomaffioletti.me/blog/2016/12/20/spring-configuration-properties-handle-deprecated-locations/
application.properties
my.book.name=SprintBoot从入门到放弃
my.book.description=Spring Boot 使用一些松的规则来绑定属性到@ConfigurationProperties bean 并且支持分层结构(hierarchical structure)
my.book.author.name=张三
my.book.author.age=20
BookProperties.java
package com.redreamer.properties;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "my.book")
public class BookProperties {
@NotBlank
private String name;
@NotBlank
private String description;
private Author author;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
Author.java
package com.redreamer.properties;
public class Author {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
PropertiesTest.java
package com.redreamer.properties;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest
public class PropertiesTest {
@Resource
private BookProperties bookProperties;
@Test
public void test() {
System.out.println(bookProperties.getName());
System.out.println(bookProperties.getDescription());
System.out.println(bookProperties.getAuthor().getName());
System.out.println(bookProperties.getAuthor().getAge());
}
}
关于@ConfigurationProperties location被弃用之后的解决方案:
基本思路就是创建一个可以使实现@ConfigurationProperties location功能的注解MyConfigurationProperties 然后在使用@ConfigurationProperties的地方使用@MyConfigurationProperties即可,具体实现如下所示:
MyConfigurationProperties.java
package com.redreamer.annotation.myconfig;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* @link http://fabiomaffioletti.me/blog/2016/12/20/spring-configuration-properties-handle-deprecated-locations/
* <p>
* how did the @ConfigurationProperties annotation look like?
* It was like this: @ConfigurationProperties(locations = "classpath:config/redirection/old2new.yml").
* With the locations deprecation, in Spring 1.5 it will not be possible to do this again,
* and this is a problem for several application already running in production
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
public @interface MyConfigurationProperties {
String[] locations();
String prefix() default "";
}
MyConfigurationPropertiesBindingPostProcessor.java
package com.redreamer.annotation.myconfig;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.boot.env.PropertySourcesLoader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
/**
* Converts properties files to objects and registers them as resolvable dependencies.
* See ConfigurationPropertiesBindingPostProcessor for the deprecated implementation.
*/
public class MyConfigurationPropertiesBindingPostProcessor implements BeanFactoryAware, ApplicationContextAware, ResourceLoaderAware, EnvironmentAware {
private BeanFactory beanFactory;
private ApplicationContext applicationContext;
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private Environment environment = new StandardEnvironment();
@PostConstruct
public void init() throws IOException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, BindException {
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(MyConfigurationProperties.class);
for (String beanName : beansWithAnnotation.keySet()) {
Class<?> clazz = beansWithAnnotation.get(beanName).getClass();
Object newInstance = bindPropertiesToTarget(clazz);
ConfigurableListableBeanFactory configurableListableBeanFactory = (ConfigurableListableBeanFactory) beanFactory;
configurableListableBeanFactory.registerResolvableDependency(clazz, newInstance);
}
}
private Object bindPropertiesToTarget(Class<?> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, BindException {
MyConfigurationProperties applicationProperties = clazz.getAnnotation(MyConfigurationProperties.class);
Constructor<?> constructor = clazz.getConstructor();
Object newInstance = constructor.newInstance();
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<>(newInstance);
factory.setPropertySources(loadPropertySources(applicationProperties.locations()));
factory.setConversionService(new DefaultConversionService());
if (StringUtils.hasLength(applicationProperties.prefix())) {
factory.setTargetName(applicationProperties.prefix());
}
try {
factory.bindPropertiesToTarget();
} catch (Exception ex) {
String targetClass = ClassUtils.getShortName(clazz);
throw new BeanCreationException(clazz.getSimpleName(), "Could not bind properties to " + targetClass + " (" + applicationProperties.toString() + ")", ex);
}
return newInstance;
}
private PropertySources loadPropertySources(String[] locations) {
try {
PropertySourcesLoader loader = new PropertySourcesLoader();
for (String location : locations) {
Resource resource = this.resourceLoader.getResource(this.environment.resolvePlaceholders(location));
String[] profiles = this.environment.getActiveProfiles();
for (int i = profiles.length; i-- > 0; ) {
String profile = profiles[i];
loader.load(resource, profile);
}
loader.load(resource);
}
return loader.getPropertySources();
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
使用MyConfigurationProperties注解后的BookProperties.java
package com.redreamer.properties;
import com.redreamer.annotation.myconfig.MyConfigurationProperties;
import org.hibernate.validator.constraints.NotBlank;
@MyConfigurationProperties(prefix = "my.book", locations = {"classpath:properties/blog.properties"})
public class BookProperties {
@NotBlank
private String name;
@NotBlank
private String description;
private Author author;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
blog.properties
# 自定义属性:如果是中文则需要通过 native2ascii 将中文转换一下
my.book.name=SprintBoot从入门到放弃
my.book.description=Spring Boot 使用一些松的规则来绑定属性到@ConfigurationProperties bean 并且支持分层结构(hierarchical structure)
my.book.author.name=张三
my.book.author.age=20
测试类需要改的地方:
@Resource private BookProperties bookProperties;
改为
@Autowired private BookProperties bookProperties;
否则注入的值为null
关于乱码的坑:
1、使用IDEA的话,修改配置
2、将中文通过JDK自带的native2ascii进行转换