[spring-projects/spring-boot]端点 ID 应支持驼峰命名法

2024-04-17 150 views
2

关于与 @snicoll 讨论的#11107,当将方法添加到 Endpoint 时,采用驼峰式(或 pascal 式)的端点 id 会被拒绝。

这是在 Spring Boot 2.0.5 和 2.1.0.M4 上测试的

例如:

@Component
@Endpoint(id = "camelCase")
public class CamelCaseEndpoint {

  @ReadOperation
  public String hello() {
    return "hello";
  }
}
Description:

Configuration property name 'management.endpoint.camelCase.cache.time-to-live' is not valid:

    Invalid characters: 'C'
    Bean: jmxMBeanExporter
    Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter

Action:

Modify 'management.endpoint.camelCase.cache.time-to-live' so that it conforms to the canonical names requirements.

回答

2

我想知道我们是否不应该只要求端点 ID 为 az 且小写。

1

我不确定我们应该这样做,因为 ID 是默认路径,而且这是相当有限的。我们有一些代码可以将 ID 转换为有效的规范名称(之前报告过此问题),但由于某种原因引入了某种回归。

3

EndpointIdTimeToLivePropertyFunction使用端点 id 来计算缓存 ttl 密钥,并且不考虑这一点。

5

实际上这两个属性都会受到影响。OnEnabledEndpointCondition不适用于不使用我们规范格式的 id。并且EndpointIdTimeToLivePropertyFunction也受到影响。

如果ConfigurationMetadata#toDashedCase()在某个地方可用,或者更一般地说,如果有一种方法可以“清理”配置名称以使其符合我们的规范格式,则两者都可以工作。

我们说过从规范格式转变为原始格式是不可能的,但我不确定我们是否讨论过从格式转变为规范格式的可能性。我们需要此功能来正确支持驼峰式端点。

由于注释处理器的原因,不幸的是,该逻辑必须被复制/粘贴。

4

我们首先需要弄清楚我们想要用#14802做什么

6

实际上,我们有很多地方使用端点 ID,因此我们需要非常小心如何/何时进行转换。财产方面我们有:

management.endpoint.<id>.enabled
management.endpoint.<id>.cache.time-to-live
management.endpoints.web.include=<id>
management.endpoints.web.path-mapping.<id>=somepath

此外,ID 还用作 Web 路径(当未定义显式映射时)并用于DefaultEndpointObjectNameFactory生成方法名称。

4

我非常确信我们应该围绕端点 ID 实施一些规则。如果我们小心的话,我们可以支持骆驼案。我认为无论如何,将字符限制为字母数字将是一个很好的举措。

0

现在,如果您在尝试获取 TTL 时执行主要读取操作,则会中断。如果您尝试使端点有条件,它也会中断。

这里有两个相互竞争的用例:

  1. ID 用于推迟一些事情(Web 路径和 JMX 名称是最明显的)
  2. 我们希望 id 的格式是可预测的

由于不同的用户有不同的要求,因此很难对 Web 路径有意见。然而,我们希望清理和协调 JMX 名称属性(使用 pascal 大小写,这是目前不太一致的另一部分,请参阅 参考资料DefaultEndpointObjectNameFactory)。

最后,我犹豫是否强制用户使用规范格式的唯一原因是,如果他们想要不同格式的网络路径,则必须配置自定义 Web 路径。

离线讨论的另一个潜在解决方案是将简单的内容转移StringEndpointId.此类将从注释中获取原始值,并能够从中生成一个规范版本,该版本将在所有配置键中使用。如果不能,它应该产生异常。由于 AP 依赖性,这意味着必须重复此代码。

剩下的唯一奇怪的事情是有一个端点被标识为mySuperEndpoint并且必须使用 id 通过 id 排除它my-super-endpoint

4

以下是所报告问题的完整堆栈跟踪:

org.springframework.boot.context.properties.source.InvalidConfigurationPropertyNameException: Configuration property name 'management.endpoint.camelCase.cache.time-to-live' is not valid
    at org.springframework.boot.context.properties.source.InvalidConfigurationPropertyNameException.throwIfHasInvalidChars(InvalidConfigurationPropertyNameException.java:51) ~[classes/:na]
    at org.springframework.boot.context.properties.source.ConfigurationPropertyName.lambda$3(ConfigurationPropertyName.java:459) ~[classes/:na]
    at org.springframework.boot.context.properties.source.ConfigurationPropertyName.processElement(ConfigurationPropertyName.java:550) ~[classes/:na]
    at org.springframework.boot.context.properties.source.ConfigurationPropertyName.process(ConfigurationPropertyName.java:540) ~[classes/:na]
    at org.springframework.boot.context.properties.source.ConfigurationPropertyName.of(ConfigurationPropertyName.java:456) ~[classes/:na]
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:166) ~[classes/:na]
    at org.springframework.boot.actuate.autoconfigure.endpoint.EndpointIdTimeToLivePropertyFunction.apply(EndpointIdTimeToLivePropertyFunction.java:54) ~[classes/:na]
    at org.springframework.boot.actuate.autoconfigure.endpoint.EndpointIdTimeToLivePropertyFunction.apply(EndpointIdTimeToLivePropertyFunction.java:36) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor.apply(CachingOperationInvokerAdvisor.java:46) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor.apply(OperationInvokerAdvisor.java:42) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationsFactory.applyAdvisors(DiscoveredOperationsFactory.java:104) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationsFactory.createOperation(DiscoveredOperationsFactory.java:96) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationsFactory.lambda$createOperation$1(DiscoveredOperationsFactory.java:80) ~[classes/:na]
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_144]
    at java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet.lambda$entryConsumer$0(Collections.java:1575) ~[na:1.8.0_144]
    at java.util.Spliterators$IteratorSpliterator.tryAdvance(Spliterators.java:1812) ~[na:1.8.0_144]
    at java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntrySetSpliterator.tryAdvance(Collections.java:1594) ~[na:1.8.0_144]
    at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_144]
    at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498) ~[na:1.8.0_144]
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485) ~[na:1.8.0_144]
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_144]
    at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_144]
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_144]
    at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464) ~[na:1.8.0_144]
    at org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationsFactory.createOperation(DiscoveredOperationsFactory.java:82) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationsFactory.lambda$createOperations$0(DiscoveredOperationsFactory.java:74) ~[classes/:na]
    at org.springframework.core.MethodIntrospector.lambda$selectMethods$0(MethodIntrospector.java:70) ~[spring-core-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:562) ~[spring-core-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.core.MethodIntrospector.selectMethods(MethodIntrospector.java:68) ~[spring-core-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationsFactory.createOperations(DiscoveredOperationsFactory.java:73) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.addOperations(EndpointDiscoverer.java:216) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.convertToEndpoint(EndpointDiscoverer.java:194) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.convertToEndpoints(EndpointDiscoverer.java:185) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.discoverEndpoints(EndpointDiscoverer.java:125) ~[classes/:na]
    at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.getEndpoints(EndpointDiscoverer.java:117) ~[classes/:na]
    at org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration.jmxMBeanExporter(JmxEndpointAutoConfiguration.java:95) ~[classes/:na]
    at org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration$$EnhancerBySpringCGLIB$$647fa87b.CGLIB$jmxMBeanExporter$1(<generated>) ~[classes/:na]
    at org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration$$EnhancerBySpringCGLIB$$647fa87b$$FastClassBySpringCGLIB$$24b727c4.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228) ~[spring-core-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:365) ~[spring-context-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration$$EnhancerBySpringCGLIB$$647fa87b.jmxMBeanExporter(<generated>) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_144]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_144]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_144]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_144]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:583) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1246) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759) ~[spring-beans-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:548) ~[spring-context-5.0.10.BUILD-SNAPSHOT.jar:5.0.10.BUILD-SNAPSHOT]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[classes/:na]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) [classes/:na]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:386) [classes/:na]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [classes/:na]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1242) [classes/:na]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1230) [classes/:na]
    at com.example.demo.ScratchApplication.main(ScratchApplication.java:10) [classes/:na]
7

此更改破坏了 Spring Cloud,因为它有一个名为 的端点hystrix.stream。那里有这个点不太理想,我认为 Spring Cloud 团队应该对此做点什么

重新开放,看看我们如何帮助他们实现这一改变。

2

FWIW 该端点名称是 Netflix 在年点中选择的。我认为不支持有效 URL 的端点路径是不合理的,并且我认为 Spring Cloud 中的路径不应该更改(也许我们可以更改配置键)。

7

如果这是配置路径的话,这是一个公平的说法。事实并非如此。该 ID 可用于许多其他地方,例如在配置属性中,以及推断我们为端点公开的ObjectNameID 。MBean

6

我知道。这里的梯度有点陡——所有带有 Hystrix 和 Actuator 的 Spring Cloud 应用程序现在都被破坏了。也许我们可以以某种方式让他们成为祖父?

WebEndpointProperties有一个pathMapping属性:

端点 ID 与应公开它们的路径之间的映射。

如果我们能够直接了解合法身份的规则,这可能就是我们最终需要的一切?但这可能无法解决现有应用程序的问题。

9

spring-boot-actuator我认为如果解析端点 id 并将它们转换为替代的驼峰命名(并且仍然保留各种命名情况的旧名称)以便它可以支持双方,这是有意义的?

1

我认为我们需要在 2.0.x 中支持.名称,否则就太大了。也许我们可以在 2.1.x 中进一步收紧规则。

1

我知道这很痛苦,但我认为我宁愿有选择地将受支持的字符添加到 ID(如果需要),而不是支持所有 URL 字符。我将从添加开始.,我们将看看这能让我们走多远。

4

再次重新打开考虑URL更改为小写