工作的时候遇到一个需求,同样的接口返回给App端的json数据需要将数值型改成字符型,而之前传给网页端的比如一些分页数据、时间戳、id等是数值的。
于是打算加一个拦截器拦截请求,在controller执行完后,如果是传给App端就把json做一下转换。
看一下拦截器的postHandler方法
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {
}
打断点运行到这个位置发行,由于使用@ResponseBody返回json数据,ModelAndView是null,而Object o似乎指的是controller而非返回数据。response中更是没有发现返回数据的踪迹。按理说拦截器可以访问action上下文,值栈里的对象,但是没找到可行的办法。
再试一下过滤器。
过滤器拦截器的执行顺序为过滤器、拦截器、过滤器、action、拦截器、过滤器。过滤器阶段获取request和response的访问数据是放在io流中的,从流中读取后需要再写回,否则数据无法再传到。
需要构造一个response的代理,放入过滤器中,等action执行完后,通过代理来获取response的io流数据,改写后再写入。具体代码如下。
**
* @description: 返回代理
* @author: WangJie
* @create: 2018-07-19 10:36
**/
public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper
{
private ByteArrayOutputStream buffer;
private ServletOutputStream out;
public MyHttpServletResponseWrapper(HttpServletResponse httpServletResponse)
{
super(httpServletResponse);
buffer = new ByteArrayOutputStream();
out = new WrapperOutputStream(buffer);
}
@Override
public ServletOutputStream getOutputStream()
throws IOException
{
return out;
}
@Override
public void flushBuffer()
throws IOException
{
if (out != null)
{
out.flush();
}
}
public byte[] getContent()
throws IOException
{
flushBuffer();
return buffer.toByteArray();
}
class WrapperOutputStream extends ServletOutputStream
{
private ByteArrayOutputStream bos;
public WrapperOutputStream(ByteArrayOutputStream bos)
{
this.bos = bos;
}
@Override
public void write(int b)
throws IOException
{
bos.write(b);
}
@Override
public boolean isReady()
{
// TODO Auto-generated method stub
return false;
}
@Override
public void setWriteListener(WriteListener arg0)
{
// TODO Auto-generated method stub
}
}
}
/**
* @description: 返回值过滤器
* @author: WangJie
* @create: 2018-07-19 10:38
**/
@Slf4j
@Component
public class ResponseFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
MyHttpServletResponseWrapper wrapperResponse = new MyHttpServletResponseWrapper((HttpServletResponse) response);//转换成代理类
// 这里只拦截返回,直接让请求过去,如果在请求前有处理,可以在这里处理
filterChain.doFilter(request, wrapperResponse);
log.info("进入返回值过滤器处理{}", ((HttpServletRequest) request).getRequestURI());
byte[] content = wrapperResponse.getContent();//获取返回值
//判断是否有值
if (content.length > 0) {
String str = new String(content, "UTF-8");
log.info("处理前返回值{}", str);
String expectStr = JsonUtil.jsonNumberToString(str);
//把返回值输出到客户端
ServletOutputStream out = response.getOutputStream();
out.write(expectStr.getBytes());
out.flush();
log.info("处理后返回值{}", expectStr);
}
}
@Override
public void init(FilterConfig arg0)
throws ServletException {
}
@Override
public void destroy() {
}
}
这样就做到了对返回数据的处理。测试了一下,正常的请求能达到理想效果,但在发生请求跳转的时候,这个方法总会报”getWriter() has already been called for this responsegetWriter() has already been called for this response”的错误,也是无法解决。换成重定向后就可以解决了。但是随之而来的问题是跨域出问题了。。。
又查了些资料后发现了第三种方法——使用@ControllerAdvice配合ResponseBodyAdvice 统一处理返回值/响应体。
ResponseBodyAdvice是spring4.1的新特性,其作用是在响应体写出之前做一些处理;比如,修改返回值、加密等。
完全契合我们的需求,代码如下。
/**
* @description:
* @author: WangJie
* @create: 2018-07-20 14:30
**/
@ControllerAdvice
public class MyResponseControllerAdvice implements ResponseBodyAdvice<Object> {
@Value(("${token.client.app}"))
private String appClient;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
ServletServerHttpRequest req = (ServletServerHttpRequest) serverHttpRequest;
HttpServletRequest httpReq = req.getServletRequest();
// Token token = (Token) httpReq.getAttribute("token");
String client = httpReq.getHeader("client");
if (client!=null && appClient.equals(client)) {
String jsonStr = JSON.toJSONString(o);
jsonStr=JsonUtil.jsonNumberToString(jsonStr);
return JSON.parseObject(jsonStr);
}
return o;
}
}