首页 文章

Spring MVC:如何修改从控制器发送的json响应

提问于
浏览
17

我用这样的控制器构建了一个json REST服务:

@Controller
@RequestMapping(value = "/scripts")
public class ScriptController {

    @Autowired
    private ScriptService scriptService;

    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public List<Script> get() {
        return scriptService.getScripts();
    }
}

它工作正常,但现在我需要修改所有响应并向所有响应添加“status”和“message”字段 . 我读过一些解决方案:

  • 从某些特定类的所有控制器方法对象返回,例如,RestResponse,它将包含"status"和"message"字段(但它不是通用解决方案,因为我将不得不修改所有控制器并以新样式编写新控制器)

  • 拦截所有带方面的控制器方法(但在这种情况下我无法更改返回类型)

如果我想将从控制器方法返回的值包装到类的对象中,您能否提出一些其他的,一般的和正确的解决方案:

public class RestResponse {

    private int status;
    private String message;
    private Object data;

    public RestResponse(int status, String message, Object data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }

    //getters and setters
}

2 回答

  • 20

    如果使用spring 4.1或更高版本,则可以在写入正文之前使用ResponseBodyAdivce自定义响应 .

  • 19

    我遇到过类似的问题,建议您使用Servlet过滤器来解决它 .

    Servlet过滤器是可用于Servlet编程的Java类,用于在客户端访问后端资源之前拦截客户端的请求,或者在将服务器发送回客户端之前操纵服务器的响应 .

    您的过滤器必须实现javax.servlet.Filter接口并覆盖三个方法:

    public void doFilter (ServletRequest, ServletResponse, FilterChain)
    

    每次请求/响应对通过链时都会调用此方法,因为客户端请求链末端的资源 .

    public void init(FilterConfig filterConfig)
    

    在过滤器投入使用之前调用,并设置过滤器的配置对象 .

    public void destroy()
    

    在过滤器停止服务后调用 .

    可以使用任意数量的过滤器,执行顺序与web.xml中定义的顺序相同 .

    web.xml中:

    ...
    <filter>
        <filter-name>restResponseFilter</filter-name>
        <filter-class>
            com.package.filters.ResponseFilter
        </filter-class>
    </filter>
    
    <filter>
        <filter-name>anotherFilter</filter-name>
        <filter-class>
            com.package.filters.AnotherFilter
        </filter-class>
    </filter>
    ...
    

    因此,此过滤器获取控制器响应,将其转换为String,作为feild添加到您的RestResponse类对象(具有状态和消息字段),将其对象序列化为Json并将完整响应发送到客户端 .

    ResponseFilter类:

    public final class ResponseFilter implements Filter {
    
    @Override
        public void init(FilterConfig filterConfig) {
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
    
        ResponseWrapper responseWrapper = new ResponseWrapper((HttpServletResponse) response);
    
        chain.doFilter(request, responseWrapper);
    
        String responseContent = new String(responseWrapper.getDataStream());
    
        RestResponse fullResponse = new RestResponse(/*status*/, /*message*/,responseContent);
    
        byte[] responseToSend = restResponseBytes(fullResponse);
    
        response.getOutputStream().write(responseToSend);
    
    }
    
    @Override
    public void destroy() {
    }
    
    private byte[] restResponseBytes(RestResponse response) throws IOException {
        String serialized = new ObjectMapper().writeValueAsString(response);
        return serialized.getBytes();
    }
    }
    

    chain.doFilter(request,responseWrapper)方法调用链中的下一个过滤器,或者如果调用过滤器是链中的最后一个过滤器,则调用servlet逻辑 .

    HTTP servlet响应包装器使用自定义servlet输出流,使得包装器在servlet完成写入后操作响应数据 . 通常,在关闭servlet输出流之后不能这样做(实际上,在servlet提交之后) . 这就是为ServletOutputStream类实现特定于过滤器的扩展的原因 .

    FilterServletOutputStream类:

    public class FilterServletOutputStream extends ServletOutputStream {
    
    DataOutputStream output;
    public FilterServletOutputStream(OutputStream output) {
        this.output = new DataOutputStream(output);
    }
    
    @Override
    public void write(int arg0) throws IOException {
        output.write(arg0);
    }
    
    @Override
    public void write(byte[] arg0, int arg1, int arg2) throws IOException {
        output.write(arg0, arg1, arg2);
    }
    
    @Override
    public void write(byte[] arg0) throws IOException {
        output.write(arg0);
    }
    }
    

    要使用FilterServletOutputStream类,应该实现一个可以充当响应对象的类 . 此包装器对象将发送回客户端,代替servlet生成的原始响应 .

    ResponseWrapper类:

    public class ResponseWrapper extends HttpServletResponseWrapper {
    
    ByteArrayOutputStream output;
    FilterServletOutputStream filterOutput;
    HttpResponseStatus status = HttpResponseStatus.OK;
    
    public ResponseWrapper(HttpServletResponse response) {
        super(response);
        output = new ByteArrayOutputStream();
    }
    
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        if (filterOutput == null) {
            filterOutput = new FilterServletOutputStream(output);
        }
        return filterOutput;
    }
    
    public byte[] getDataStream() {
        return output.toByteArray();
    }
    }
    

    我认为这种方法对您的问题来说是一个很好的解决方案 .

    如果问题不明确,请提出问题,如果我错了,请纠正我 .

相关问题