You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JDK21下Spring响应空值默认值设置报错解决方案咨询

问题解决方案

异常原因

JDK 9+引入模块化系统后,java.util包中的内部不可变集合类(如ImmutableCollections$ListN)禁止未命名模块通过反射访问其内部字段。原代码中setDefault方法会遍历所有对象的字段,包括JDK自带的不可变集合,触发权限校验,导致InaccessibleObjectException。

方案一:修复ResponseBodyAdvice反射逻辑

调整代码,只处理自定义JavaBean的字段,跳过JDK内置类型(如String、List、Map),同时正确实例化null的data字段:

package com.guannet.you.admin.rest.aop;

import com.guannet.frame.web.core.RestResult;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@ControllerAdvice
public class ResponseBodyDefaultValueAdvice implements ResponseBodyAdvice<RestResult<?>> {
    @Override
    public boolean supports(MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
        return returnType.getParameterType() == RestResult.class;
    }

    @Override
    public RestResult<?> beforeBodyWrite(RestResult<?> body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (Objects.isNull(body)) {
            return null;
        }
        Object data = body.getData();
        if (Objects.nonNull(data)) {
            // 仅处理自定义Bean,跳过JDK内置类型
            if (!isJdkBuiltInType(data.getClass())) {
                setDefault(data);
            }
            return body;
        }

        // 获取data字段的泛型类型,创建默认空实例
        Class<?> dataType = (Class<?>) returnType.getGenericParameterType();
        if (dataType != null) {
            data = createDefaultInstance(dataType);
            // 通过反射设置data字段(若RestResult无setData方法)
            Field dataField = ReflectionUtils.findField(RestResult.class, "data");
            if (dataField != null) {
                ReflectionUtils.makeAccessible(dataField);
                ReflectionUtils.setField(dataField, body, data);
            }
            // 若创建的是自定义Bean,递归处理其内部字段
            if (!isJdkBuiltInType(dataType)) {
                setDefault(data);
            }
        }
        return body;
    }

    /**
     * 判断是否为JDK内置类型
     */
    private boolean isJdkBuiltInType(Class<?> clazz) {
        return clazz.getName().startsWith("java.") || clazz.getName().startsWith("javax.") || clazz.getName().startsWith("jakarta.");
    }

    /**
     * 根据类型创建对应的空实例
     */
    private Object createDefaultInstance(Class<?> type) {
        if (type == String.class) {
            return "";
        } else if (List.class.isAssignableFrom(type)) {
            return Collections.emptyList();
        } else if (Map.class.isAssignableFrom(type)) {
            return Collections.emptyMap();
        } else {
            // 实例化自定义Bean
            try {
                return type.getDeclaredConstructor().newInstance();
            } catch (Exception e) {
                return null;
            }
        }
    }

    /**
     * 递归设置自定义Bean的null字段默认值
     */
    private void setDefault(Object obj) {
        ReflectionUtils.doWithFields(obj.getClass(), field -> {
            ReflectionUtils.makeAccessible(field);
            Object val = ReflectionUtils.getField(field, obj);
            if (val != null) {
                // 若字段值为自定义Bean,递归处理
                if (!isJdkBuiltInType(field.getType())) {
                    setDefault(val);
                }
                return;
            }
            // 给null字段设置默认值
            Object defaultValue = createDefaultInstance(field.getType());
            if (defaultValue != null) {
                ReflectionUtils.setField(field, obj, defaultValue);
            }
        });
    }
}

方案二:Jackson全局序列化配置(更简洁)

直接在JSON序列化阶段将null值转为对应空值,无需反射处理,避免模块化权限问题:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Map;

@Configuration
public class JacksonNullValueConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule nullHandleModule = new SimpleModule();

        // String类型null转为""
        nullHandleModule.addSerializer(String.class, new NullSerializer() {
            @Override
            public void serialize(Object value, com.fasterxml.jackson.core.JsonGenerator gen, com.fasterxml.jackson.databind.SerializerProvider serializers) throws java.io.IOException {
                gen.writeString("");
            }
        });

        // List类型null转为[]
        nullHandleModule.addSerializer(List.class, new NullSerializer() {
            @Override
            public void serialize(Object value, com.fasterxml.jackson.core.JsonGenerator gen, com.fasterxml.jackson.databind.SerializerProvider serializers) throws java.io.IOException {
                gen.writeStartArray();
                gen.writeEndArray();
            }
        });

        // Map类型null转为{}
        nullHandleModule.addSerializer(Map.class, new NullSerializer() {
            @Override
            public void serialize(Object value, com.fasterxml.jackson.core.JsonGenerator gen, com.fasterxml.jackson.databind.SerializerProvider serializers) throws java.io.IOException {
                gen.writeStartObject();
                gen.writeEndObject();
            }
        });

        mapper.registerModule(nullHandleModule);
        return mapper;
    }
}

方案对比

  • 方案一:适合需要在业务对象层面统一处理空值的场景,可递归处理嵌套Bean的空字段。
  • 方案二:仅在JSON输出阶段处理,代码更简洁高效,无反射权限问题,推荐优先使用。

内容的提问来源于stack exchange,提问作者Dolphin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 07:55:53