将ConfigurationProperties绑定到<enum,pojo>的映射

时间:2018-09-27 15:46:53

标签: java spring-boot

说明 我正在尝试将以下配置与Component类绑定在一起-

platform:
  service:
    config:
      guard:
       hostname: fancy-host1.kiki.com
       resources:
         - name: bark
           api-path: dog/alert/bark/{dog-id}
         - name: bite
           api-path: dog/alert/bite/{dog-id}
           json-path: $..kill-mode
      play:
        hostname: fancy-host2.kiki.com
        resources:
         - name: lick
           api-path: dog/chill/lick/{dog-id}
           json-path: $..cute-mode

我的Component类看起来像这样-

@Component
@ConfigurationProperties(prefix = "platform.service")
public class DogConfig
{
    @Getter
    @Setter
    public class Resource
    {
        private String name;
        private String apiPath;
        private String jsonPath;
    }

    @Getter
    @Setter
    public class APIConfig
    {
        private String hostname;
        private List<Resource> resources = new ArrayList<>();
    }

    private Map<ServiceType, APIConfig> config = new LinkedHashMap<>();

    public Map<ServiceType, APIConfig> getConfig()
    {
        return config;
    }

    public void setConfig(Map<ServiceType, APIConfig> config)
    {
        this.config = config;
    }
}

在上面的代码中,ServiceType是具有值GUARD和PLAY的枚举。

问题 虽然我的Spring Boot应用程序在初始化时没有引发任何错误,但是它没有将我的YAML绑定到DogConfig类。我不确定在这里到底缺少什么。

到目前为止我的故障排除工作 我依靠this spring doc来外部化我的配置。我知道@ConfigurationProperties是类型安全的,并且已经分别测试了Enums,Map和POJO的绑定。但是同时实现这三个功能是我无法实现的。

4 个答案:

答案 0 :(得分:2)

请在您的内部类Resource和APIConfig中添加 static 例如:

public static class Resource {
    private String name;
    private String apiPath;
    private String jsonPath;
}

答案 1 :(得分:1)

您可以这样做:

  1. 像这样创建POJO:

    @Getter
    @Setter
    @ConfigurationProperties("platform.service")
    public class DogProperties {
    
        private Map<String, APIConfig> config;
    
    }
    
  2. 在DogConfig中,您可以执行此操作以获取属性:

    @Configuration
    @EnableConfigurationProperties(DogProperties.class)
    public class DogConfig {
    
        @Autowire
        private DogProperties properties
    
        ...
    
        @Bean
        @Qualifier("guardConfig")
        public APIConfig guardConfig(){
           return properties.get("guard");
       }
    
     }
    

如果您查看此示例,则秘密是一个Map属性,您可以使用键盘锁来解析地图,也可以使用API​​Config播放。

答案 2 :(得分:1)

已编辑:

您可以使用ConstructorBinding注释(请参见https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-constructor-binding)将属性绑定到Constructor,以使用valueOf()方法将字符串键转换为枚举键:

@Component
@ConfigurationProperties(prefix = "platform.service")
@ConstructorBinding
public class DogConfig {

    DogConfig(Map<String, APIConfig> config) {
       this.config = config.entrySet().stream().collect(
                        Collectors.toMap(
                           e -> ServiceType.valueOf(e.getKey()), 
                           Map.Entry::getValue
                        )
                );
    }

...

或为此,通过另外使用Jackson-Databind ObjectMapper将具有附加地图值类型的有线地图转换为具有pojo值类型的有线地图:

@Component
@ConfigurationProperties(prefix = "platform.service")
@ConstructorBinding
public class DogConfig {

    DogConfig(Map<ServiceType, Map<String,Object> > config) {
       this.config = config.entrySet().stream().collect(
                        Collectors.toMap(
                           Map.Entry::getKey,
                           e -> (new ObjectMapper()).convertValue(e.getValue(),APIConfig.class)
                        )
                );
    }

...

就像OP所暗示的那样,地图中的Enum和Pojo的组合似乎不起作用。您可能必须将两种可能的结构之一转换为所需的结构。

答案 3 :(得分:0)

将用于访问整个结构的主类: PlatformConfigContainer

import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@AllArgsConstructor
@ConfigurationProperties(prefix = "platform.service")
@Component
public class PlatformConfigContainer {
  private final Map<ConfigType, ServiceConfig> config;
}

ConfigType

public enum ConfigType {
  guard, play
}

ServiceConfig

import com.fasterxml.jackson.annotation.JsonProperty; // this is the trick
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ServiceConfig {
  @JsonProperty("hostname")
  private String hostname;

  @JsonProperty("resources")
  private List<NamedApiPath> resources;
}

NamedApiPath

import com.fasterxml.jackson.annotation.JsonProperty; // this is the trick
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class NamedApiPath {
  @JsonProperty("name")
  private String name;

  @JsonProperty("api-path")
  private String apiPath;

  @JsonProperty(value = "json-path", required = false) // bit extra verbose
  private Optional<String> jsonPath = Optional.empty();
}