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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25020331/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 23:27:21  来源:igfitidea点击:

Spring MVC: How to modify json response sent from controller

javajsonspringspring-mvc

提问by Vitalii Ivanov

I've built a json REST service with controllers like this one:

我用这样的控制器构建了一个 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();
    }
}

It works fine, but now I need to modify all responses and add "status" and "message" fields to all of them. I've read about some solutions:

它工作正常,但现在我需要修改所有响应并向所有响应添加“状态”和“消息”字段。我已经阅读了一些解决方案:

  1. return from all controller methods object of some specific class, for example, RestResponse, which will contain "status" and "message" fields (but it's not general solution, cause I will have to modify all my controllers and write new controllers in new style)
  2. intercept all controller methods with aspects (but in this case I can't change return type)
  1. 从某些特定类的所有控制器方法对象返回,例如,RestResponse,它将包含“状态”和“消息”字段(但这不是通用解决方案,因为我将不得不修改所有控制器并以新样式编写新控制器)
  2. 拦截所有带有方面的控制器方法(但在这种情况下,我无法更改返回类型)

Can you suggest some other, general and correct solution, if I want to wrap values returned from controller methods into objects of class:

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

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
}

采纳答案by Alexander

I've encountered with similar problem and suggest you to use Servlet Filters to resolve it.

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

Servlet Filters are Java classes that can be used in Servlet Programming to intercept requests from a client before they access a resource at back end or to manipulate responses from server before they are sent back to the client.

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

Your filter must implement the javax.servlet.Filter interface and override three methods:

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

public void doFilter (ServletRequest, ServletResponse, FilterChain)

This method is called every time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain.

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

public void init(FilterConfig filterConfig)

Called before the filter goes into service, and sets the filter's configuration object.

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

public void destroy()

Called after the filter has been taken out of service.

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

There is possibility to use any number of filters, and the order of execution will be the same as the order in which they are defined in the web.xml.

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

web.xml:

网页.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>
...

So, this filter gets the controller response, converts it into String, adds as feild to your RestResponse class object (with status and message fields), serializes it object into Json and sends the complete response to the client.

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

ResponseFilter class:

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) method invokes the next filter in the chain, or if the calling filter is the last filter in the chain invokes servlet logic.

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

The HTTP servlet response wrapper uses a custom servlet output stream that lets the wrapper manipulate the response data after the servlet is finished writing it out. Normally, this cannot be done after the servlet output stream has been closed (essentially, after the servlet has committed it). That is the reason for implementing a filter-specific extension to the ServletOutputStream class.

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

FilterServletOutputStream class:

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);
}
}

To use the FilterServletOutputStream class should be implemented a class that can act as a response object. This wrapper object is sent back to the client in place of the original response generated by the servlet.

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

ResponseWrapper class:

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();
}
}

I think this approach will be a good solution for your issue.

我认为这种方法将是您问题的一个很好的解决方案。

Please, ask a questions, if something not clear and correct me if I'm wrong.

如果有什么不明白的地方,请提出问题,如果我错了,请纠正我。

回答by smartwjw

If you use spring 4.1 or above, you can use ResponseBodyAdviceto customizing response before the body is written.

如果使用 spring 4.1 或以上版本,则可以在写入正文之前使用ResponseBodyAdvice自定义响应。