# 24.7. 类型安全的配置属性

使用`@Value("${property}")`注解注入配置属性有时会比较难处理，特别是但你需要使用多个properties，或者数据本身有层次结构的时候。Spring Boot提供一种可选的使用配置的方法，这种方法让强类型的beans来管理和校验应用的配置，如下所示：

```java
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("acme")
public class AcmeProperties {

    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定义了如下的属性：

* acme.enabled，默认值为false
* acme.remote-address，带有一个能从String转换的类型
* acme.security.username，有一个嵌套的“security”对象，它的名字由属性名定义。特别的，返回类型没有在那儿被使用，本来可以是SecurityProperties。
* acme.security.password
* acme.security.roles，一个String的集合

**注意** getters和setters时常是必须的。因为跟Spring MVC一样，绑定是通过标准的Java Beans属性描述符进行的。但是也有不需要setter的情况：

* Maps，只要它们被初始化了，需要getter，但setter不是必须的。因为binder能够改变它们。
* Collections和arrays能够通过索引（通常用YAML），或者使用单个的由逗号分隔的值（属性）存取。 在后面的情况下，setter是必须的。对于这些类型，我们建议总是加上setter。如果你初始化一个collection，确保它不是不可变的（如同之前的例子）。
* 如果嵌套的POJO属性被初始化（就像之前例子里的Security域），setter就不需要。如果你想要binder使用它默认的构造器，即时创建一个实例，你就需要一个setter。

有些人使用Project Lombok自动添加getters和setters。确保Lombok不会为这些类型生成任何特别的构造器，因为它会自动地被容器使用，来实例化对象。

**提示** 查看[@Value和@ConfigurationProperties](https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/htmlsingle/#boot-features-external-config-vs-value)之间的区别。

你需要在`@EnableConfigurationProperties`注解中列出要注册的属性类，如下所示：

```java
@Configuration
@EnableConfigurationProperties(AcmeProperties.class)
public class MyConfiguration {
}
```

**注** 当`@ConfigurationProperties` bean以这种方式注册时，该bean将有个约定的名称：`<prefix>-<fqn>`，`<prefix>`是`@ConfigurationProperties`注解中定义的environment key前缀，`<fqn>`是bean的全限定名。如果注解中没有提供任何前缀，那就只使用bean的全限定名。上述示例中的bean名称将是`acme-com.example.AcmeProperties`。

尽管上述配置为`FooProperties`创建了一个常规的bean，不过我们建议`@ConfigurationProperties`只用来处理environment（只用于注入配置，系统环境之类的），特别是不要注入上下文中的其他beans。话虽如此，`@EnableConfigurationProperties`注解会自动应用到你的项目，任何存在的，注解`@ConfigurationProperties`的bean将会从`Environment`中得到配置。只要确定`AcmeProperties`是一个已存在的bean，`MyConfiguration`就可以不用了。

```java
@Component
@ConfigurationProperties(prefix="acme")
public class AcmeProperties {

    // ... see the preceding example 

}
```

这种配置风格跟`SpringApplication`的外部化YAML配置配合的很好：

```javascript
# application.yml

acme:
    remote-address: 192.168.1.1
    security:
        username: admin
        roles:
          - USER
          - ADMIN

# additional configuration as required
```

为了使用`@ConfigurationProperties` beans，你可以像使用其他bean那样注入它们：

```java
@Service
public class MyService {

    private final AcmeProperties properties;

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

     //...

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

}
```

**注** 使用`@ConfigurationProperties`能够产生可被IDEs使用的元数据文件，来为你自己的键值提供自动补全，具体参考[Appendix B, Configuration meta-data](https://jack80342.gitbook.io/spring-boot/iv.-spring-boot-features/24.-externalized-configuration/broken-reference)。
