SpringBoot学习之配置文件注入@ConfigurationProperties

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的话,修改配置

162911_sV59_1042509.png

2、将中文通过JDK自带的native2ascii进行转换

 

 

转载于:https://my.oschina.net/u/1042509/blog/864836

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值