24.7 Type-safe Configuration Properties

使用@Value("${property}")注解来注入配置属性有的时候会有点笨重(cumbersome),尤其是当你要使用大量属性或者数据是层次结构的(data is hierarchical in nature)。Spring Boot提供了一个替代的方法允许强类型的beans来管理和验证你应用的配置(allows strongly typed beans to govern and validate the configuration of your application)。

package com.example;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("foo")
public class FooProperties {

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    public boolean isEnabled() { ... }

    public void setEnabled(boolean enabled) { ... }

    public InetAddress getRemoteAddress() { ... }

    public void setRemoteAddress(InetAddress remoteAddress) { ... }

    public Security getSecurity() { ... }

    public static class Security {

        private String username;

        private String password;

        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

        public String getUsername() { ... }

        public void setUsername(String username) { ... }

        public String getPassword() { ... }

        public void setPassword(String password) { ... }

        public List<String> getRoles() { ... }

        public void setRoles(List<String> roles) { ... }

    }
}

以上的POJO定义了如下属性: foo.enable,默认值是false foo.remotoe-address,它的类型InetAddress可以从String强制转换得到 foo.security.username,是一个嵌套的属性(Securityusername属性) foo.security.password foo.security.roles,是一个String的集合

通常对Java Bean来说Getters和Setters是必须要有的,因为属性的绑定是通过标准Java Bean属性描述符的(standard Java Beans property descriptors),就像在Spring MVC里一样,但是这里有一些情况使得setters可以被省略:

  • Maps,只要它们被初始化,就只需要getter但setter就不是必须的了,因为它们可以被邦定器(binder)改变
  • Collections和arrays可以通过an index(在YAML中)或者是逗号隔开的属性值(在*.properties)中访问到,在后一种情况中,setter是必须的。不过我们建议不管什么情况都请加上setter。如果你初始化一个集合(可以忽略setter的方式),请确保被初始化的集合是可变的(正如上面的例子中Collections.singleton("USER")是不可变的,必须给它套上一个new ArrayList<>(Collections.singleton("USER"))保证roles是可变的,从而可以省略roles的setter方法,并保证能够自动从配置文件中绑定属性)
  • 如果一个嵌套的POJO属性被初始化了(就像Security security = new Security()),setter不是必须的了,如果你想要绑定器(Binder)在运行时(on-the-fly)使用类的默认构造器产生实例,那么你就需要添加setter 有些人使用Project Lombok来自动化地添加getters和setters。你要确保Lombok 不会自动产生任何特殊化的构造器(particular constructor 就是带属性参数的构造器 如public Security(String username){...},因为容器需要使用default constructor自动化地实例化对象)

可以参见differences between @Value and @ConfigurationProperties.

补充 在YAML中定义collections 和 arrays:

foo:
    list:
        - l1
        - l2
        - l3

在properties中定义collections 和 arrays:

foo.list=l1, l2

foo.list[0]=l1
foo.list[1]=l2

在YAML中定义map:

foo:
    map:
        key1: val1
        key2: val2

在properties中定义map

map.key1=val1
map.key2=val2

你还必须需要通过@EnableConfigurationProperties注解向注册器(register)列出属性类(如果没有这一步@ConfigurationProperties将是不起作用的,而且当你从ApplicationContextgetBean的时候将会抛出NoSuchBeanDefinitionException)(You also need to list the properties classes to register in the @EnableConfigurationProperties annotation:)

@Configuration
@EnableConfigurationProperties(FooProperties.class)
public class MyConfiguration {
}

@ConfigurationPropertiesbean被通过这种方式注册了,这个bean将会获得一个惯例的名称:<prefix>-<fqn>,其中<prefix>是在@ConfigurationPropertiesprefix参数中提供的环境前缀,<fqn>是该bean的全限定类名。如果@ConfigurationProperties没有提供prefix那么只用全限定类名被使用。 上面例子中的bean name是foo-com.example.FooProperties

虽然按照以上的代码的配置会产生一个普通的FooProperties的bean,但是我们建议只使用@ConfigurationProperties注解来处理和环境(environment )有关的内容(不要用它和@EnableConfigurationProperties来注册产生一些普通的bean),在实践中也不要向@ConfigurationProperties bean注入一些上下文中(context)的其他的bean。但是话又说回来,将@EnableConfigurationProperties应用于你的项目,其实也就自动地将Environment里的属性、配置什么的注入到了@ConfigurationProperties bean中。当然你也可以不需要只孤零零地添加了@EnableConfigurationProperties注解的MyConfiguration——直接给FooProperties添加@Component,就等于注册了FooProperties这个bean,效果和上面的方式一样:

@Component
@ConfigurationProperties(prefix="foo")
public class FooProperties {

    // ... see above

}

最后附上上面的代码的外部配置YAML文件(这个配置文件中的属性会自动注入到FooProperties中):

# application.yml

foo:
    remote-address: 192.168.1.1
    security:
        username: foo
        roles:
          - USER
          - ADMIN

# additional configuration as required

你可以向对待普通bean一样对待被@ConfigurationProperties注解的bean(它可以被注入到其他bean中):

@Service
public class MyService {

    private final FooProperties properties;

    @Autowired
    public MyService(FooProperties properties) {
        this.properties = properties;
    }

     //...

    @PostConstruct
    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        // ...
    }

}

使用@ConfigurationProperties可以让你生成 meta-data files,这files可以被IDEs使用来为你自己的keys提供自动化完成(Using @ConfigurationProperties also allows you to generate meta-data files that can be used by IDEs to offer auto-completion for your own keys) 查看Appendix B, Configuration meta-data中的具体内容

results matching ""

    No results matching ""