Spring Boot控制器响应如何转义控制字符?通用Controller Advice实现问询
可行性及实现方案
完全可以实现,不需要逐个修改POJO属性,以下是两种可直接落地的无侵入方案:
方案1:基于Jackson全局字符串序列化器(推荐)
Spring Boot默认使用Jackson做JSON序列化,直接自定义String类型的全局序列化器即可实现所有字符串字段的控制字符替换,性能更高、逻辑更简洁。
实现步骤:
- 编写自定义字符串序列化器
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; public class ControlCharCleanSerializer extends JsonSerializer<String> { @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (value == null) { gen.writeNull(); return; } // 可根据需求调整替换规则,示例为替换所有控制字符,也可单独只替换\n、\r等 String cleanedValue = value.replaceAll("\\p{Cntrl}", ""); gen.writeString(cleanedValue); } }
- 全局注册序列化器
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer customStringSerializer() { return builder -> builder.serializerByType(String.class, new ControlCharCleanSerializer()); } }
该方案对所有返回值生效,包括ResponseEntity<T>、直接返回POJO等所有场景,不需要修改任何业务代码。如果有个别字段不需要做替换,可给对应字段加@JsonSerialize(using = JsonSerializer.None.class)注解单独排除。
方案2:基于ResponseBodyAdvice实现
如果需要对响应对象做更复杂的自定义处理,可通过ResponseBodyAdvice在响应写入前递归处理POJO的所有字符串属性:
import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.lang.reflect.Field; import java.util.Collection; import java.util.Map; @ControllerAdvice public class ResponseCleanAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { // 只匹配返回类型为ResponseEntity的接口,返回true则匹配所有响应 return returnType.getParameterType().isAssignableFrom(ResponseEntity.class); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof ResponseEntity) { Object realBody = ((ResponseEntity<?>) body).getBody(); try { cleanStringFields(realBody); } catch (IllegalAccessException ignored) {} } return body; } // 递归清理所有字符串字段的控制字符 private void cleanStringFields(Object obj) throws IllegalAccessException { if (obj == null) return; // 跳过非引用类型 Class<?> clazz = obj.getClass(); if (clazz.isPrimitive() || clazz.isEnum() || Number.class.isAssignableFrom(clazz) || Boolean.class.isAssignableFrom(clazz)) { return; } // 处理字符串 if (obj instanceof String) { ((String) obj).replaceAll("\\p{Cntrl}", ""); return; } // 处理集合 if (obj instanceof Collection) { for (Object item : (Collection<?>) obj) { cleanStringFields(item); } return; } // 处理Map if (obj instanceof Map) { for (Map.Entry<?, ?> entry : ((Map<?, ?>) obj).entrySet()) { cleanStringFields(entry.getValue()); } return; } // 处理自定义POJO Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Object value = field.get(obj); if (value instanceof String) { field.set(obj, ((String) value).replaceAll("\\p{Cntrl}", "")); } else { cleanStringFields(value); } } } }
该方案性能略低于Jackson序列化方案,需要自行处理递归边界避免栈溢出,适合有额外响应处理需求的场景。
内容的提问来源于stack exchange,提问作者Halley
相关产品推荐
相关产品推荐

