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
相关产品推荐
相关产品推荐

