spring-boot-admin,如何配置它的reactor库使用的objectMapper?

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

我正在尝试在我的 kubernetes 集群中设置一个 spring-boot-admin 服务(使用 https://github.com/codecentric/spring-boot-admin-runtime-playground)。一切进展顺利:它看到了我的服务,我能够看到“见解”页面、“日志记录”页面,而其他页面还不能工作。

我想设置一切工作,现在我正在努力解决“JVM->线程转储”,并且我遇到了 Jackson objectMapper 的问题。

问题是我的服务(不是 spring-boot-admin 服务,而是我想要监控的其他服务)使用特定的配置:

        objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

因此,当我在 spring-boot-admin 中请求 '/actuator/threaddump' 时,它会失败(因为 spring-boot-admin 请求我的服务,并且它返回无效的 json),并显示以下错误(错误很长,所以我已缩短):

    2024-11-12 07:37:52.425 DEBUG 1 --- [nio-8080-exec-3] o.apache.coyote.http11.Http11Processor   : Error state [CLOSE_NOW] reported while processing request
    
    org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type `java.util.ArrayList<java.lang.Object>` from Object value (token `JsonToken.ST
    ART_OBJECT`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.util.ArrayList<java.lang.Object>` from Object value (token `JsonToken.START_OBJECT`)
     at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1]
            at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-5.3.30.jar!/:5.3.30]
            at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.30.jar!/:5.3.30]
            at javax.servlet.http.HttpServlet.service(HttpServlet.java:529) ~[tomcat-embed-core-9.0.82.jar!/:na]
            at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.30.jar!/:5.3.30]
            at javax.servlet.http.HttpServlet.service(HttpServlet.java:623) ~[tomcat-embed-core-9.0.82.jar!/:na]
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209) ~[tomcat-embed-core-9.0.82.jar!/:na]
...
...
Caused by: org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type `java.util.ArrayList<java.lang.Object>` from Object value (token `JsonToken.START_OBJECT`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputExcep
tion: Cannot deserialize value of type `java.util.ArrayList<java.lang.Object>` from Object value (token `JsonToken.START_OBJECT`)
 at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1]
        at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:242) ~[spring-web-5.3.30.jar!/:5.3.30]
        Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
        *__checkpoint <E2><87><A2> Body from GET http://10.0.3.223:8081/actuator/threaddump [DefaultClientResponse]
Original Stack Trace:
                at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:242) ~[spring-web-5.3.30.jar!/:5.3.30]
                at org.springframework.http.codec.json.AbstractJackson2Decoder.decode(AbstractJackson2Decoder.java:198) ~[spring-web-5.3.30.jar!/:5.3.30]
                at org.springframework.http.codec.json.AbstractJackson2Decoder.lambda$decodeToMono$1(AbstractJackson2Decoder.java:179) ~[spring-web-5.3.30.jar!/:5.3.30]
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125) ~[reactor-core-3.4.33.jar!/:3.4.33]
                at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.4.33.jar!/:3.4.33]
...
...
        Suppressed: java.lang.Exception: #block terminated with an error
                at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:100) ~[reactor-core-3.4.33.jar!/:3.4.33]
                at reactor.core.publisher.Mono.block(Mono.java:1742) ~[reactor-core-3.4.33.jar!/:3.4.33]
                at de.codecentric.boot.admin.server.web.servlet.InstancesProxyController.instanceProxy(InstancesProxyController.java:125) ~[spring-boot-admin-server-2.7.16.jar!/:2.7.16]
                at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) ~[na:na]
                at java.base/java.lang.reflect.Method.invoke(Unknown Source) ~[na:na]
...
...
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.util.ArrayList<java.lang.Object>` from Object value (token `JsonToken.START_OBJECT`)
 at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1]
        at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59) ~[jackson-databind-2.13.5.jar!/:2.13.5]
        at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1741) ~[jackson-databind-2.13.5.jar!/:2.13.5]
...

乍一看,这似乎是一个简单的问题:只需找到 objectMapper 并配置 ACCEPT_SINGLE_VALUE_AS_ARRAY=true 即可。 但我就是找不到它 我的意思是我找不到负责反序列化对其他服务的请求的那个(似乎 spring-boot-admin 有几个)。

我尝试过的:

/**
 * this one actually is somehow used by spring-boot-admin, since if i configure
 * it wrong the whole app stops working properly,
 * but doesn't help with my issue
 * */   
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
    ...


/**also tried this*/
@Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
    return new Jackson2ObjectMapperBuilder()
            .featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
}

/**also tried this*/
@Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
    return jacksonObjectMapperBuilder -> {
    jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
    };
}

/**
 * also, from the error stacktrace, i've found that it uses org.springframework.http.codec.json.AbstractJackson2Decoder
 * which is actually used by reactive WebFlux library (in my case my spring-boot-admin works in classic: servlet mode, non-reactive)
 * therefore i've tried the below, but also without success, it doesn't use it
 * */
@Bean
Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper mapper){
        mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
    return new Jackson2JsonDecoder(mapper)

所以,据我了解这个问题,如下: spring-boot-admin 类 de.codecentric.boot.admin.server.web.servlet.InstancesProxyController 使用反应式堆栈(Flux、Mono 等),它使用分离的来自其他应用程序 objectMapper,我无法找到它来重新配置它。

有人知道吗?

我使用的配置, pom.xml:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.7.17</version>
</dependency>

<!-- Spring Boot Admin -->
<dependency>
  <groupId>de.codecentric</groupId>
  <artifactId>spring-boot-admin-starter-server</artifactId>
  <version>2.7.16</version>
</dependency>

应用程序.yml

server:
  port: 8080
  max-http-header-size: 65536
  forward-headers-strategy: none
spring:
  application: # Application-Infos for the Info-Actuator
    name: "@pom.artifactId@"
  cloud:
    kubernetes:
      discovery:
        # set this to false if running namespaced
        all-namespaces: false
  # Spring Boot Admin
  boot:
    admin:
      ui:
        public-url: https://hello-world.net/spring-boot-admin
        # public-url: ${SPRING_BOOT_ADMIN_UI_PUBLIC_URL:http://localhost:8080}
        title: ${SPRING_BOOT_ADMIN_UI_TITLE:Spring Boot Admin}
        brand: <img src="assets/img/icon-spring-boot-admin.svg"><span>${SPRING_BOOT_ADMIN_UI_TITLE:Spring Boot Admin}</span>
      discovery: # Filter discovery to tagged services
        instances-metadata:
          spring-boot-admin: true # is added as annotation in service.yaml in helm chart

management: # Actuator Configuration
  server:
    port: 8081
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint: # Health-Actuator
    health:
      show-details: always
      probes:
        enabled: true
        add-additional-paths: true
    env: # Environment-Actuator
      show-values: always # caution: can reveal passwords
    configprops: # Configuration-Actuator
      show-values: always # caution: can reveal passwords
  info: # Info-Actuator
    java:
      enabled: true
    os:
      enabled: true
    build:
      enabled: true
    env:
      enabled: true
    git:
      enabled: true
info: # Application-Infos for the Info-Actuator
  group: "@pom.groupId@"
  artifact: "@pom.artifactId@"
  description: "@pom.description@"
  version: "@pom.version@"
  spring-boot: "@pom.parent.version@"
  spring-boot-admin: "@spring-boot-admin.version@"
  spring-cloud: "@spring-cloud.version@"
  # Tags for the Spring Boot Admin UI
  tags:
    spring-boot: "@pom.parent.version@"
    spring-boot-admin: "@spring-boot-admin.version@"
    spring-cloud: "@spring-cloud.version@"
logging: # Logging-File for the Logfile-Actuator
  file:
    name: "spring-boot-admin.log"
  level:
    root: DEBUG
    org.apache.coyote: TRACE
    org.springframework.web: DEBUG
jackson spring-boot-admin
1个回答
0
投票

听起来您正在尝试让 Spring Boot 管理服务器正确处理服务的 /actuator/threaddump 端点,但您遇到了反序列化问题。关键问题是 Spring Boot Admin 使用 WebFlux 及其 WebClient 来调用您的服务,而您的服务使用标准 Spring MVC (Servlet) 堆栈。由于 Spring Boot Admin 的 WebClient 堆栈是响应式的并且具有自己的 ObjectMapper 配置,因此它不会获取您的自定义 ObjectMapper 设置,从而在尝试处理线程转储响应时导致反序列化错误。

修复方法如下:

解决方案概述: 您需要确保 Spring Boot Admin 的 WebClient 使用与您应用于服务的相同的 ObjectMapper 配置,特别是 ACCEPT_SINGLE_VALUE_AS_ARRAY 功能。

修复步骤: 为 Spring Boot 管理创建自定义 WebClient 配置 由于 Spring Boot Admin 在内部使用反应式 WebClient(通过 WebFlux),因此您可以自定义该 WebClient,以便它使用具有正确反序列化功能的 ObjectMapper。

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