具有类型和后备的 Spring 配置属性

问题描述 投票:0回答:1

问题

我有以下弹簧属性

myservice:
 domains:
  domain1:
    supplier:
      baseUrl:
      getEndpoint:
    customer:
      postEndpoint:
  domain2:
    supplier:
      baseUrl:
      getEndpoint:
      retryCount:
    customer:
      baseUrl:
      postEndpoint:
  fallbackDomain:
    supplier:
      baseUrl:
      getEndpoint:
      retryCount:
    customer:
      baseUrl:
      postEndpoint:

这个想法是多个域有恒定的配置,可以是“domain1”,“domain2”,“domain3”等。此外还有后备域。 如果在特定域上找不到属性,则使用后备值。 示例:

  • 获取domain1的客户发布端点 ->“myservice.domains.domain1.customer.postEndpoint”
  • 获取domain1的供应商重试计数 - >“myservice.domains.fallbackDomain.supplier.retryCount”,因为domain1不存在。

我正在使用 spring boot 2.3.3 和 spring 5.2.8,但如果需要的话考虑升级。

解决方案1

选项之一是从 org.springframework.core.env.Environment 获取属性,例如

public <T> T getPropertyWithFallback(String nameTemplate, String domain, Class<T> clazz){
  return Optional.ofNullable(environment.getProperty(nameTemaplte.format(domain), clazz))
  .orElse(environment.getProperty(nameTemplate.format("fallbackDomain"), clazz));
}
int count = getPropertyValueWithFallback("myservice.domains.%s.supplier.retryCount", "domain1", Integer.class);

但是然后:

  • 我需要处理字符串名称,并且属性名称很容易出现拼写错误
  • 我需要记住使用“%s”的模板约定,这没什么大不了的,但仍然可以更好
  • 我需要记住每个属性的类型
  • 删除属性很困难,因为它需要找到代码中的所有引用,这可能并不简单。

解决方案2

我可以使用 spring 配置属性功能,例如:

@Component
@ConfigurationProperties(prefix = "myservice")
public class MyServiceConfigurationProperties {
  // skipping gettters and setters for readability
  private Map<String, Domain> domains;

  public static class Domain {
    private Supplier supplier;
    private Customer customer;
  }
  public static class Supplier {
    private String baseUrl;
    private String getEndpoint;
    private Integer retryCount;
  }
  public static class Customer {
    private String baseUrl;
    private String postEndpoint;
  }
}

这工作正常,配置后我不需要记住属性名称,类型已经分配。浏览/列出域的所有属性很容易。

  • 定义属性的灵活性稍差一些 - 它假设每个域都有共同的结构 - 但这有助于避免混乱。
  • 添加/删除属性时需要更多工作,但代价是更容易维护。
  • 有一个问题 - 对于嵌套对象来说,实现后备变得很复杂。

我尝试使用代理来实现它

    private static class FallbackInvocationHandler implements InvocationHandler {
        private final Object primary;
        private final Object fallback;

        public FallbackInvocationHandler(Object primary, Object fallback) {
            this.primary = primary;
            this.fallback = fallback;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = method.invoke(primary, args);
            if (result == null) {
                result = method.invoke(fallback, args);
            }
            if (result != null && result.getClass().getPackage().getName().startsWith("no.eg.apps.pda6k.server.config")) {
                return Proxy.newProxyInstance(
                        result.getClass().getClassLoader(),
                        new Class[]{result.getClass()},
                        new FallbackInvocationHandler(result, method.invoke(fallback, args))
                );
            }
            return result;
        }
    }

  private <T> T getWithFallback(T value, T fallbackValue){
    if (value==null){
      return fallbackValue;
    }
    return (T) Proxy.newProxyInstance(
            value.getClass().getClassLoader(),
            new Class[]{value.getClass()},
            new FallbackInvocationHandler(value, fallbackValue)
  }
// and then each getter implement in such way
  public Domain getDomainWithFallback(String domain){
     Domain value = domains.get(domain);
     Domain fallbackValue = domains.get("fallbackDomain");
     return getWithFallback(value, fallbackValue);
  }

嵌套属性变得更加复杂/容易出错。 “乐观”的实施可能看起来像:

// witin Supplier class
public Integer getRetryCountWithFallback(){
    return getWithFallback(this.retryCount, domains.get("fallbackValue").getSupplier().getRetryCount());
}

但是如果 fallbackDomain 没有/空 supplier 属性怎么办 - 需要一些额外的空检查,这会使所有简单事情的代码变得复杂。 添加新属性也变得更加复杂。 如果存在重复属性,例如是否可以重用配置结构?客户和供应商具有相同的结构,我想为他们使用相同的 java 对象?

问题

您是否看到比上面提供的更好的解决方案可以让我解决问题?我不想改变属性的结构,但如果需要的话,这是一个选项。

java spring spring-boot properties
1个回答
0
投票

解决方案2现在已经很好了。但让我们跳出框框思考一下。您的域名到底是什么?这是一个服务、一个获取数据的地方、一个代码函数吗?其中每种不同的类型您可以有不同的解决方案。例如,在某些情况下,我可以应用简单的套接字线程来检查某些服务的运行状态。这种方法可以稍微修改一下并适用于很多问题。

© www.soinside.com 2019 - 2024. All rights reserved.