赞
踩
最近上线一个版本之后,发现微信支付回调那里老是报如下的异常
熟悉支付的同学应该知道,回调里处理完业务逻辑之后,要给微信/支付宝回调成功的响应
java.lang.IllegalStateException: getWriter() has already been called for this response
at org.apache.catalina.connector.Response.getOutputStream(Response.java:591)
at org.apache.catalina.connector.ResponseFacade.getOutputStream(ResponseFacade.java:194)
at javax.servlet.ServletResponseWrapper.getOutputStream(ServletResponseWrapper.java:100)
at javax.servlet.ServletResponseWrapper.getOutputStream(ServletResponseWrapper.java:100)
我的回调的代码如下:
public String onNotify(HttpServletRequest request, HttpServletResponse response) throws IOException { logger.info("微信支付回调数据开始"); //业务逻辑省略 String resXml ="<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> "; } //执行完业务之后,将支付成功的标志响应给微信,否则微信得不到我们SUCCESS的 //响应,会一直重试 BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); log.info("微信支付回调数据结束"); return null; }
很奇怪,为什么会报出getWriter() has already been called for this response
这个错误信息,我的方法体里没有用到getWriter()
呀?
好吧,先从为什么报这个错开始来看吧。
从报错堆栈信息来看,response.getOutputStream()
->ResponseFacade.getOutputStream()
->response.getOutputStream()
。
其实response.getWriter()的调用逻辑与上面的很类似,你会在Response类里发现getWriter()这个方法
protected boolean usingOutputStream = false; protected boolean usingWriter = false; public ServletOutputStream getOutputStream() throws IOException { if (usingWriter) { throw new IllegalStateException (sm.getString("coyoteResponse.getOutputStream.ise")); } usingOutputStream = true; if (outputStream == null) { outputStream = new CoyoteOutputStream(outputBuffer); } return outputStream; } @Override public PrintWriter getWriter() throws IOException { if (usingOutputStream) { throw new IllegalStateException (sm.getString("coyoteResponse.getWriter.ise")); } if (ENFORCE_ENCODING_IN_GET_WRITER) { setCharacterEncoding(getCharacterEncoding()); } usingWriter = true; outputBuffer.checkConverter(); if (writer == null) { writer = new CoyoteWriter(outputBuffer); } return writer; }
上面分别把getOutputStream()
与getWriter()
方法截出来了。
初始化的时候usingOutputStream=false
、usingWriter = false
。
但是调用上面两个方法中的一个的时候,会把另一个标志变为true。而标志位变为true,则会抛出异常
在一个方法体里getOutputStream
和getWriter
方法,只能使用其中一个,否则会报出最上面的错误信息。
类似的,getOutputStream() has already been called for this response也是这个问题。
同理,getReader()与getInputStream()方法也不能同时使用
尽管上面的原因会导致报错,但是我的方法体里只用到一次getOutputStream
方法呀,还是不应该 出现这个报错呀。会不会是controller方法前,有拦截器或过滤器或aop等原因,导致了response对象中usingOutputStream=true
或usingWriter = true
。
通过排查代码,发现真有一个aop前置通知进行请求参数获取并且序列化。
@Before("execution(* com.wojiushiwo..*Controller.*(..))") public void putParams(JoinPoint joinPoint) throws JsonProcessingException { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) (RequestContextHolder.getRequestAttributes()); //获取参数值 Object[] args = joinPoint.getArgs(); //获取参数名称 String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); //参数值和名称是一一对应的 Map<String, Object> map = new HashMap<>(); for (int i = 0; i < parameterNames.length; i++) { if (Objects.nonNull(args[i])) { //最后一个参数名称是request 这里过滤掉 if (args[i] instanceof HttpServletRequest) { continue; } //过滤掉参数中的BindingResult if(args[i] instanceof BindingResult){ continue; } } map.put(parameterNames[i], args[i]); } //将参数放到session中 requestAttributes.setAttribute("params", map, RequestAttributes.SCOPE_SESSION); //序列化参数 ObjectMapper objectMapper=new ObjectMapper(); objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); objectMapper.writeValueAsString(map); //...
这里获取参数的时候,只过滤了HttpServletRequest
、BindingResult
(参数校验对象),并没有过滤HttpServletResponse
,所以HttpServletResponse
对象会随着去序列化。
前置通知结束后,才会走到我们的controller中,才会出现上面的问题。
所以改正方式只需要在获取参数的时候,过滤HttpServletResponse
即可,如
if(args[i] instanceof HttpServletResponse){
continue;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。