我正在尝试在我的 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
听起来您正在尝试让 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。