0

0

Spring Boot WebClient发送增强型Bean序列化问题的解决方案

心靈之曲

心靈之曲

发布时间:2025-12-04 14:16:02

|

835人浏览过

|

来源于php中文网

原创

spring boot webclient发送增强型bean序列化问题的解决方案

在使用Spring Boot构建微服务应用时,我们经常需要通过WebClient进行RESTful API调用。当尝试将一个由Spring管理的、特别是被增强过的Bean(如通过`@ConfigurationProperties`注解配置的Bean)直接作为请求体发送时,可能会遇到序列化异常。本文将深入分析这一问题,并提供两种解决方案,包括一种实用的规避方案和一种更符合最佳实践的推荐方法。

理解问题:Spring Bean与WebClient序列化挑战

在某些场景下,为了避免重复创建请求对象,开发者可能希望直接使用一个已配置好的Spring Bean作为REST请求的JSON体。例如,一个通过@ConfigurationProperties注解管理的配置Bean,其结构如下:

@ConfigurationProperties(prefix = "myprefix")
@Configuration("configname")
@Getter
@Setter
public class ConfigDetails {
    private String c1;
    private String c2;
    private String c3;
}

当尝试使用WebClient将此ConfigDetails Bean发送出去时,代码可能类似于:

// configDetails 是通过 @Autowired 注入的 ConfigDetails 实例
webClient.post().body(Mono.just(configDetails), ConfigDetails.class).retrieve().bodyToMono(String.class).block();

然而,在序列化过程中,通常会遇到类似以下堆跟踪的错误:

 No serializer found for class org.springframework.context.expression.StandardBeanExpressionResolver and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.example.ConfigDetails$$EnhancerBySpringCGLIB$$cad0a6e6["$$beanFactory"]->org.springframework.beans.factory.support.DefaultListableBeanFactory["beanExpressionResolver"])

分析根本原因

这个错误信息揭示了问题的核心:ConfigDetails$$EnhancerBySpringCGLIB$$cad0a6e6。这表明Spring在运行时对ConfigDetails Bean进行了增强,通常是通过CGLIB库创建了一个代理子类。这种增强是为了实现Spring的AOP(面向切面编程)功能、代理作用域或处理@Configuration类中的@Bean方法等。

被CGLIB增强的Bean实例,除了我们定义的业务属性(如c1, c2, c3)外,还会包含一些Spring框架内部使用的属性,例如$$beanFactory、$$beanName等。当Jackson等JSON序列化库尝试序列化这些增强过的Bean时,它会遍历所有属性。对于这些Spring内部的属性,Jackson找不到对应的序列化器,也无法将其识别为普通的Java Bean属性,从而抛出No serializer found的异常。

解决方案一:创建静态非增强副本 (Workaround)

一种直接的规避方法是在被增强的Bean内部维护一个其自身属性的“纯净”副本。这个副本不会被Spring增强,因此在序列化时不会携带额外的Spring内部属性。

具体实现步骤如下:

  1. 在ConfigDetails类中添加一个静态变量来持有非增强的副本。
  2. 使用@PostConstruct注解的方法在Bean初始化后,将当前实例的业务属性复制到静态副本中。
  3. 提供一个静态方法来获取这个非增强的副本。

修改后的ConfigDetails类如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import lombok.Getter;
import lombok.Setter;

@ConfigurationProperties(prefix = "myprefix")
@Configuration("configname")
@Getter
@Setter
public class ConfigDetails {

    private String c1;
    private String c2;
    private String c3;

    // 静态变量,用于保存非增强的ConfigDetails副本
    private static ConfigDetails staticConfigDetailsInstance;

    @PostConstruct
    public void init(){
        // 在Bean初始化后,将当前实例的属性复制到静态副本
        staticConfigDetailsInstance = new ConfigDetails();
        staticConfigDetailsInstance.setC1(this.c1);
        staticConfigDetailsInstance.setC2(this.c2);
        staticConfigDetailsInstance.setC3(this.c3);
        // 如果有其他属性,也需要在此处进行复制
    }

    // 提供静态方法获取非增强的副本
    public static ConfigDetails getInstance(){
        return staticConfigDetailsInstance;
    }
}

现在,在WebClient调用时,不再直接使用注入的configDetails实例,而是使用其静态副本:

Lumen5
Lumen5

一个在线视频创建平台,AI将博客文章转换成视频

下载
// 使用静态方法获取非增强的实例进行序列化
webClient.post()
         .body(Mono.just(ConfigDetails.getInstance()), ConfigDetails.class)
         .retrieve()
         .bodyToMono(String.class)
         .block();

此方案的优缺点:

  • 优点: 有效解决了序列化问题,实现简单直接。
  • 缺点:
    • 引入了手动属性复制的环节,如果ConfigDetails的属性发生变化,需要同步更新init()方法,增加了维护成本和出错的可能性。
    • 静态实例的生命周期与应用相同,如果ConfigDetails的属性在运行时可能动态变化,这种方式可能无法及时反映最新状态(尽管@ConfigurationProperties通常是静态配置)。

解决方案二:推荐的实践 - 使用数据传输对象 (DTO)

更符合软件工程最佳实践的方法是使用数据传输对象(DTO)。DTO是专门为数据传输设计的简单Java对象,它不包含任何业务逻辑,也不会被Spring增强。通过DTO,我们可以将内部的配置Bean与外部API的契约解耦。

实现步骤:

  1. 创建一个简单的POJO作为DTO,包含需要发送给API的属性。
  2. 在发送请求前,将ConfigDetails Bean的属性映射到DTO实例。
// 1. 定义一个简单的DTO类
@Getter
@Setter
@NoArgsConstructor // 需要无参构造函数以供Jackson使用
@AllArgsConstructor // 方便构造
public class ConfigRequestDTO {
    private String c1;
    private String c2;
    private String c3;
}
  1. 在调用WebClient前进行映射:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class MyApiService {

    private final WebClient webClient;
    private final ConfigDetails configDetails; // 注入原始的ConfigDetails Bean

    @Autowired
    public MyApiService(WebClient webClient, ConfigDetails configDetails) {
        this.webClient = webClient;
        this.configDetails = configDetails;
    }

    public String sendConfigToApi() {
        // 2. 将ConfigDetails的属性映射到ConfigRequestDTO
        ConfigRequestDTO requestDTO = new ConfigRequestDTO(
            configDetails.getC1(),
            configDetails.getC2(),
            configDetails.getC3()
        );

        // 3. 使用DTO作为WebClient的请求体
        return webClient.post()
                        .uri("/api/endpoint") // 替换为实际的API路径
                        .body(Mono.just(requestDTO), ConfigRequestDTO.class)
                        .retrieve()
                        .bodyToMono(String.class)
                        .block();
    }
}

此方案的优缺点:

  • 优点:
    • 解耦: 将内部实现细节(ConfigDetails作为Spring Bean)与外部API契约(ConfigRequestDTO)分离。
    • 清晰的API契约: DTO明确定义了API期望的数据结构,易于理解和维护。
    • 避免序列化问题: DTO是纯粹的POJO,不会被Spring增强,因此不会有内部属性导致序列化失败。
    • 灵活性: 可以在DTO中添加或移除字段,而不会影响ConfigDetails的内部结构。
  • 缺点: 引入了一个额外的类和映射步骤,对于非常简单的场景可能显得有些繁琐(但通常是值得的)。

总结与最佳实践

当Spring Bean(特别是被CGLIB增强的Bean)作为WebClient请求体进行序列化时,由于其包含Spring内部属性,可能导致序列化失败。

  1. 静态非增强副本是一种快速解决问题的规避方案。它通过在Bean内部创建一个纯净的静态副本进行序列化。此方法适用于配置Bean属性相对稳定且不频繁变动的场景,但需注意手动维护属性复制的风险。

  2. 使用数据传输对象(DTO)是更推荐的最佳实践。它通过引入一个专门用于API通信的POJO来解耦内部Bean和外部API契约。DTO模式不仅解决了序列化问题,还提高了代码的可维护性、可读性和API契约的清晰度,是构建健壮微服务应用的优选方案。

在实际开发中,我们应优先考虑使用DTO模式,以确保API接口的稳定性和代码的健壮性。只有在极度追求简洁且明确知晓风险的情况下,才可考虑静态非增强副本的规避方案。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

838

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

742

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

737

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

399

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16926

2023.08.03

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

3

2026.01.20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.9万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1.0万人学习

React核心原理新老生命周期精讲
React核心原理新老生命周期精讲

共12课时 | 1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号