springboot项目通过ResponseBodyAdvice增加响应头和修改响应内容

对于需要统一对所有的http响应增加响应头(比如增加响应签名,响应时间,响应序号等)以及响应数据进行统一处理(比如数据包装,数据转换等)的场景,可以通过ResponseBodyAdvice进行实现。

ResponseBodyAdvice就是spring框架中预留的钩子,它作用在Controller方法执行完成之后,http响应体写回客户端之前,这个时候我们就能方便的织入一些自己的业务逻辑处理了。

1. 定义ResponseBodyAdvice实现类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
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;


@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {

    protected static Logger logger = LoggerFactory.getLogger(MyResponseBodyAdvice.class);

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {

        Class targetClass = returnType.getMethod().getDeclaringClass();
        logger.info("supports execute methodParameter={} targetClass={} class={}", returnType, targetClass, converterType);

        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        logger.info("beforeBodyWrite data={}", body);

        // response.addHeader("Pragma", "no-cache");

        if(body instanceof String)
        {
            body = "this is modified body!";
        }

        // 这里计算签名值,并设置到请求头
        response.getHeaders().set("x-abc", "header-value");

        return body;
    }
}

2. 接口说明

查看ResponseBodyAdvice接口的源码中,提供了两个方法:supports和beforeBodyWrite。supports方法用于判断beforeBodyWrite方法的执行与否,返回值为布尔类型,返回true即执行beforeBodyWrite方法。

  • MethodParameter以及HttpMessageConverter类型判断是否需要改写http响应体

  • beforeBodyWrite方法提供了用于修改http响应体的能力

3. 特别说明

对于通过@ResponseBody和ResponseEntity方式返回响应给调用者的情况,filter和interceptor对响应体和响应头的修改,没有效果,原因在于,请求返回给前端以后,才会执行filter和interceptor的后处理,所以此时,只能用于响应体信息的读取和记录。

interceptor中记录请求信息。

@Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        MyResponseWrapper myResponseWrapper = null;

        if(response instanceof  MyResponseWrapper)
        {
            myResponseWrapper = (MyResponseWrapper)response;

            byte[] body = myResponseWrapper.getBody();

            String strBody = new String(body);

            logger.info("body is:" + strBody);

        }

        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

filter中记录响应体的方法:

public class AddResponseHeaderFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        response.addHeader("X-Frame-Options", "DENY");
        response.addHeader("Cache-Control", "no-cache, no-store, must-revalidate, max-age=0");
        MyResponseWrapper myResponseWrapper = new MyResponseWrapper((HttpServletResponse)response);

        filterChain.doFilter(request, myResponseWrapper);

        byte[] body = myResponseWrapper.getBody();
        String strBody = new String(body);

        System.out.println("strBody:" + strBody);
    }
}

对于返回前端用于页面显示的情况下,是可以用filter和interceptor的方式来改写响应体和响应头的,原因是controller执行完成以后,执行过滤器的后处理,然后再进行页面渲染,最终返回给前端用于显示。

对于记录响应信息时,还需要对响应做一次封装,避免在读取信息后,后续读取信息时出现错误。以下是对响应的封装类。

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;


public class MyResponseWrapper extends HttpServletResponseWrapper {

    private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    private HttpServletResponse response;

    public MyResponseWrapper(HttpServletResponse response) {
        super(response);
        this.response = response;
    }

    public byte[] getBody() {
        return byteArrayOutputStream.toByteArray();
    }

    @Override
    public ServletOutputStream getOutputStream() {
        return new ServletOutputStreamWrapper(this.byteArrayOutputStream , this.response);
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return new PrintWriter(new OutputStreamWriter(this.byteArrayOutputStream , this.response.getCharacterEncoding()));
    }


    private static class ServletOutputStreamWrapper extends ServletOutputStream {

        private ByteArrayOutputStream outputStream;
        private HttpServletResponse response;

        public ServletOutputStreamWrapper(ByteArrayOutputStream outputStream, HttpServletResponse response)
        {
            this.outputStream = outputStream;
            this.response = response;
        }

        public ByteArrayOutputStream getOutputStream() {
            return outputStream;
        }

        public void setOutputStream(ByteArrayOutputStream outputStream) {
            this.outputStream = outputStream;
        }

        public HttpServletResponse getResponse() {
            return response;
        }

        public void setResponse(HttpServletResponse response) {
            this.response = response;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setWriteListener(WriteListener listener) {

        }

        @Override
        public void write(int b) throws IOException {
            this.outputStream.write(b);
        }

        @Override
        public void flush() throws IOException {
            if (! this.response.isCommitted()) {
                byte[] body = this.outputStream.toByteArray();
                ServletOutputStream outputStream = this.response.getOutputStream();
                outputStream.write(body);
                outputStream.flush();
            }
        }
    }

}

所以,具体采用哪种方式,还需要看具体的场景,如果是返回接口调用结果,就使用ResponseBodyAdvice,如果是返回页面显示,就可以使用filter或interceptor。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值