[spring-projects/spring-boot]减少 ConfigData 基础设施中反射和注释的使用

2024-04-12 998 views
8

我希望有很多地方可以改进。让我们从一个简单的开始。

ConfigDataProperties是一个值对象,并且通过反射方式构造,Binder.bind()这是浪费的。据我所知,它仅用于查找spring.config.import(环境变量大写版本从未使用过,因为它已经绑定到“初始”导入)的值。并且“导入”没有任何 kebab 大小写变体,因此您不需要Binder.我认为我们可以简单地查看Environment属性是否存在,ConfigDataProperties通过调用构造函数或因子方法直接构造一个实例。

它还使用注释@Name来重命名绑定到其构造函数的属性,这似乎也是不必要的间接(我们控制属性名称和构造函数源代码)。如果我们需要使用,我们可能无法摆脱注释处理Binder.bind(),但如果我们可以跳过它那就更好了。

回答

9

这是另一个想法:我相信 Spring Boot 可以将所有Binder结果追溯到配置属性名称和值。也许我们可以使用该信息来缩短流程 - 绑定一次并索引结果并重新使用它。我很乐意在构建时考虑这样做(符合 spring-init 的总体议程和限制)。我们可能需要能够更轻松地覆盖现有工厂 - 在运行时,您可能需要查阅构建时已知内容的记录,这样您就可以避免再次这样做。

0

另外,FWIW 这是 Spring Init 中的一个接口,用于Binder绕过@ConfigurationProperties

public interface PropertiesBinder<T> {

    T bind(T bean, Environment environment);

}

然后有一个全局实用程序PropertiesBinders,您可以使用它来注册 的实例PropertiesBinder。类似的东西在这里可以工作 - 一个替换Binder基于默认Environment绑定的钩子。

9

据我所知,它仅用于查找spring.config.import

它还向上看spring.config.activate.on-cloud-platform,然后spring.config.activate.on-profile。这两个都是枚举,并且都可以application.properties以轻松的形式在 main 中使用。例如spring.profile.activate.oncloudplatform=Kubernetes是有效的。

我认为我们可以简单地查看环境,如果该属性存在,则ConfigDataProperties通过调用构造函数或工厂方法直接构造一个实例。

如果我们尝试直接获取值,有许多边缘情况会导致问题。过去我们已经被这个问题困扰了很多次,所以当我们开始研究新类时,我们有意决定将核心保留Binder在新代码中。一个常见问题是列出 YAML 文件扩展为[N]语法的位置。我们需要处理那些意味着重复大量逻辑的问题IndexedElementsBinder

它还使用注释 @Name 来重命名绑定到其构造函数的属性,这似乎也是不必要的间接(我们控制属性名称和构造函数源代码)。

我们需要一种不同的方式来获取 中的参数名称ValueObjectBinder。目前PARAMETER_NAME_DISCOVERER是硬编码的。我选择了注释,因为这意味着我们可以进行更新,ValueObjectBinder而无需增加 API 的表面积。

7

我们是否假设您不能使用环境或系统变量来覆盖属性?

0

一般来说,情况并非如此,但我认为对于构建本机映像和容器,人们愿意接受这种妥协。我们只需要明确指出,它们只会在“严格的封闭世界”中获得增强的性能(在运行时不会更改类路径和环境)。

无论如何,可能没有必要限制环境实际上没有变化,而只限制环境属性值的变化。

2

Binder.bind()我仍然不认为这里(和其他地方)需要完全主导。除了其他任何事情之外,调试/阅读/理解真的很难。我们是否可以将“反射”、“宽松的属性名称”和“类型转换”问题进一步分开,以便可以更强制地编写它?

例如

Collection<String> imports = StringUtils.commaDelimitedListToSet(Something.getRelaxedProperty(environment, "spring.config.import", ""));

属性名称约定可以封装在Environment事件中。然后,您可以完成这一切,而无需读取任何注释元数据,也无需反射性地调用任何方法:

Collection<String> imports = StringUtils.commaDelimitedListToSet(environment, "spring.config.import", "");
String[] profiles = StringUtils.commaDelimitedListToStringArray(environment, "spring.config.activate.on-profile", "");
CloudPlatform platform = CloudPlatform.valueOf(environment, "spring.config.activate.on-cloud-platform", "NONE").toUpperCase());
ConfigDataProperties data = new ConfigDataProperties(imports, new Activate(platform, profiles));

ConfigDataProperties只有 3 个输入(因此它是一个合理的值对象),并且它们都是原始的(从 String 进行简单的类型转换)。

2

我完全同意可以Binder进行一些改进以帮助调试和理解代码。然而,我不同意答案是不使用它来处理ConfigData。活页夹如此复杂的原因之一是它试图处理可能发生的所有微妙的装订问题。过去我们遇到过一些问题,人们对为什么某些功能在某些时间无法使用感到困惑。

例如,如果我们切换到,我们会立即限制可以使用的StringUtils.commaDelimitedListToSet(Something.getRelaxedProperty(environment, "spring.config.import", ""))方式。spring.config.import

这将是一个有效的application.yml

spring.config.import: "classpath:foo.yaml,classpath:bar.yaml"

这一个不会:

spring.config.import: ["classpath:foo.yaml", "classpath:bar.yaml"]

我们必须记录该限制并解释原因。我们还会添加一个限制,即逗号永远不能成为位置字符串的一部分。

Environment最重要的是,新代码故意在所有处理完成之前不进行修改。因此我们需要一个新Environment实例供ConfigData处理期间使用。

我最近做了一个更改,Bindable允许它携带属性。我认为我们可以用它来删除反射@Name注释检查。我认为我们应该从这一点开始,看看我们还能做些什么来改进代码Binder

9

如果我们切换到StringUtils.commaDelimitedListToSet()

所以也许我们不要这样做。我们必须更改绑定的实现,这样它就不需要字符串值。这可能包含在“类型转换”部分中。我们的目标只是让它变得更加势在必行、不那么晦涩难懂。您仍然可以封装尽可能多的内容。

不确定我是否遵循了有关新Environment实例的内容。

3

纵观所有选项,我认为我们无法删除 的使用Binder,当然不会造成很多不稳定。我已经打开 #23713 来查看一般的活页夹,看看我们如何能够减少 3.x 中反射的使用。

4

:失望:我没想到要删除它,只是让它可以插入其他东西。我们至少可以考虑在 2.4 中这样做吗?

7

我想从上面的所有讨论中我认为没有一个明智的替代方案Binder不会导致各种微妙的问题。我不确定我是否真的遵循只替换这段代码的愿望。即使我们能够以某种方式为 提供可插拔系统ConfigData,难道它不会Binder仍然用于几乎所有其他系统@ConfigurationProperties吗?这就是为什么我认为打开 #23713 是个好主意的原因之一。

3

是的,同意。问题是我认为我们需要公开这些微妙的问题,即使它是“买家小心”。如果我想限制绑定的表面积,以便只有属性名称的精确匹配才起作用,我应该能够做出这样的选择。我应该说,如果不需要提供非反思性的选项,我不会有这样的意见,但无论如何,它似乎应该是普遍有趣的。

6

我不明白为什么 #23713 不是一个比这个问题规模可能更大的后续问题。另外,我可以看到这与 Spring Native 相关,并且据我们所知,坦率地说,我不确定我是否同意这是必须具备的。我现在要结束这个,我们可以在到达 #23713 后重新访问