今天工作遇到一个问题:每次请求接口如果业务代码报错的话会再请求一次dispatchservlet的doservice方法;请求两次接口
开始怀疑是前端请求了两次,但是经过排查并不是这个原因,而且用postman测试接口也出现同样的问题:
经过分析发现是在新加了一个filter后出现的情况,于是把新加的filter去掉后测试果然不会再出现请求两次的情况
现在我们来看一下这个filter的问题:
同事在dofilter()方法中写了这样一段代码
try {
logVo.setRequestBody(bodyStringBuilder.toString());
logVo.setHost(servletRequest.getHeader("Host"));
logVo.setIp(getIpAddress(servletRequest));
logVo.setOperateTime(new Date());
resolveUserIdentify(logVo);
long start = System.currentTimeMillis();
chain.doFilter(servletRequest, response);//交给下个filter
logVo.setTimestamp(System.currentTimeMillis() - start);
log.info("Request1:{}", JSON.toJSONString(logVo));
}catch (Exception e){
log.error("AbstractLogFilter error",e);
long start = System.currentTimeMillis();
chain.doFilter(servletRequest, response);//交给下个filter
logVo.setTimestamp(System.currentTimeMillis() - start);
log.info("Request2:{}", JSON.toJSONString(logVo));
}
重点在try块和catch块中的chain.doFilter(servletRequest, response);
每次try中报错后这个filter会捕获异常,并且再次调用chain.doFilter(servletRequest, response);
那么如果try块中chain.doFilter(servletRequest, response)之前没有报错会怎么样呢:
我们进入tomcat的ApplicationFilterChain的doFilter()方法:
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction() {
@Override
public Void run()
throws ServletException, IOException {
internalDoFilter(req,res);
return null;
}
}
);
} catch( PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException)
throw (ServletException) e;
else if (e instanceof IOException)
throw (IOException) e;
else if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new ServletException(e.getMessage(), e);
}
} else {
internalDoFilter(request,response);
}
调用了internalDoFilter(req,res):
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
filter = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
filter, request, response);
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege
("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response);
} catch (IOException | ServletException | RuntimeException e) {
if (filter != null)
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
if (filter != null)
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
filter, request, response, e);
throw new ServletException
(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
servlet, request, response);
if (request.isAsyncSupported()
&& !support.getWrapper().isAsyncSupported()) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse)) {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
servlet.service(request, response);
}
} else {
servlet.service(request, response);
}
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response);
}
重点在pos和n,来看一下这两个变量的注释,简单来说n表示chain上filter的数量,pos表示已经执行过得filter数量
/**
* The int which is used to maintain the current position
* in the filter chain.
*/
private int pos = 0;
/**
* The int which gives the current number of filters in the chain.
*/
private int n = 0;
那么在try中成功执行过chain.doFilter(servletRequest,response)方法并且所有filter都执行成功后,poss=n;而这个时候业务代码报错了,filter捕获这个异常后又在catch{}块中又一次执行了chain.doFilter(servletRequest, response);这时if (pos< n)判断为flase;
代码会走到servlet.service(request, response);
又一次去调用servlet的service()方法导致业务代码执行了两次。
原来同事写这段代码的原因是try{}块中的chain.doFilter(servletRequest,response)之前可能会出现报错的情况,如果不catch会出现filter无法执行完,也就无法进入servlet的情况,try得位置不对导致了chain.doFilter(servletRequest,response)执行两次就会出现调用两次servlet的情况