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

Spring Boot控制器响应如何转义控制字符?通用Controller Advice实现问询

可行性及实现方案

完全可以实现,不需要逐个修改POJO属性,以下是两种可直接落地的无侵入方案:


方案1:基于Jackson全局字符串序列化器(推荐)

Spring Boot默认使用Jackson做JSON序列化,直接自定义String类型的全局序列化器即可实现所有字符串字段的控制字符替换,性能更高、逻辑更简洁。
实现步骤:

  1. 编写自定义字符串序列化器
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);
    }
}
  1. 全局注册序列化器
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 08:30:00