相关文章:
{% post_link 拦截和转发 %}
请求的分发是通过doFilter实现的,可以在同一个拦截器内同时实现转发和分发
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
try {
// 获取请求路径
String uri = request.getRequestURI();
if ("/core/web/ycApi/documentVerifySign".equals(uri)){
// 前端发起的验证请求也需要转发, 去掉web标记
uri = "/core/ycApi/documentVerifySign";
log.info("/core/web/ycApi/documentVerifySign ==> /core/ycApi/documentVerifySign");
}
if (uri.startsWith("/core/web/ycApi")){
// 页面请求,不做转发
filterChain.doFilter(request, response);
return;
}
// 转发
forwardFilter(request, response, uri);
} catch (Exception e){
log.error("转发服务异常", e);
// 交给Spring的异常解析器去处理
// exceptionResolver.resolveException(request, response, null, e);
// 必须得传 HandlerMethod 否则会走GlobalExceptionHandler,而不是指定的YanCaoApiAdvice
// 没有通过servlet进行分发,因此没有获取到方法对象
// 如有必要再添加对方法的匹配,这里默认去找Controller的第一个方法
HandlerMethod handlerMethod = new HandlerMethod(ycController, ycController.getClass().getDeclaredMethods()[0]);
exceptionResolver.resolveException(request, response, handlerMethod, e);
}
}
一般而言,异常会由HandlerExceptionResolverComposite处理,它包含了Advice注解的异常处理方案。
但是有些情况下,请求没有执行到DispatcherServlet的分析,获得到handler不能指向我们需要的Advice,比如文件上传接口,超出了tomcat的最大限制导致的checkMultipart解析失败。
因为DispatcherServlet#checkMultipart解析是在DispatcherServlet#getHandler之前,导致异常处理程序因为没有接收到handler而没法交给特定的Advice处理,从而转给全局Advice处理。
目前有两种解决方案
异常处理前是指DispatcherServlet#processHandlerException方法内遍历handlerExceptionResolvers时,会先执行DefaultErrorAttributes#resolveException方法,在请求头中设置异常状态
request.setAttribute(ERROR_ATTRIBUTE, ex);
但是在这个过程中如果返回了视图对象ModelAndView,则不会往下执行异常的处理
因此,我们可以继承DefaultErrorAttributes对象,并注入我们自己的规则,拦截某些请求的异常流程,将请求分发中的异常交由指定的Advice处理。
@Slf4j
@Component
public class MyDefaultErrorAttributes extends DefaultErrorAttributes {
@Autowired
private YanCaoApiController ycController;
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver exceptionResolver;
/**
* 文件上传分发出错导致链路没有解析出mappedHandler
* @see org.springframework.web.servlet.DispatcherServlet#doDispatch(HttpServletRequest, HttpServletResponse)
* @see org.springframework.web.servlet.DispatcherServlet#checkMultipart(HttpServletRequest)
* 单独针对指定路径的文件上传请求做异常处理的指定
* 因为没法指定handler,异常处理会走向全局处理
* @see com.gomain.exception.GlobalExceptionHandler
* 但是可以在异常处理(HandlerExceptionResolver)前,先通过DefaultErrorAttributes返回ModelAndView来提前将拦截掉异常处理
* @see org.springframework.web.servlet.DispatcherServlet#processHandlerException(HttpServletRequest, HttpServletResponse, Object, Exception)
* processHandlerException方法中的handlerExceptionResolvers是优先处理DefaultErrorAttributes,不返回null就可以实现对异常的拦截
* 再次将异常处理交给了ycController对应的Advice,这里随意指定了ycController中的一个方法
* 可以进一步细化到对应的类方法,即实现DispatcherServlet的部分功能
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return ModelAndView
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
String uri = request.getRequestURI();
if ("/core/web/ycApi/fileUploadStream".equals(uri)){
log.error("文件上传分发失败, uri=/core/web/ycApi/fileUploadStream, error:{}", ex.getMessage());
Method method;
try {
method = ycController.getClass().getMethod("fileUploadStream", MultipartFile.class, HttpServletRequest.class, HttpServletResponse.class);
} catch (Exception e){
log.error("没有找到方法:fileUploadStream");
method = ycController.getClass().getDeclaredMethods()[0];
}
// 指定Advice处理异常
HandlerMethod handlerMethod = new HandlerMethod(ycController, method);
return exceptionResolver.resolveException(request, response, handlerMethod, ex);
}
return super.resolveException(request, response, handler, ex);
}
}
异常处理后救场是指默认filterChain.doFilter中发生异常,并由我们不认可的异常处理程序处理
分发链路处理中,并不会抛出异常,我们可以在分发链路执行完成后,检查请求头中是否有异常标记
通过修改响应体来达成目的
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
try{
...
// 页面请求,不做转发
filterChain.doFilter(request, response);
// multipartResolver 检查请求时出错,会导致dispatcher解析不出handler,从而被全局异常捕获
Object dispatcherException = request.getAttribute(DispatcherServlet.EXCEPTION_ATTRIBUTE);
if (Objects.nonNull(dispatcherException)){
log.error("分发过程中发生异常");
//TODO 已提交的响应不能重置的解决方案
// 分发发生异常时交由指定的Advice处理
throw (Exception) dispatcherException;
}
// 分发过程中没有发生异常,将进入Controller处理
return;
...
} catch (Exception e){
log.error("分发异常", e);
HandlerMethod handlerMethod = new HandlerMethod(ycController, ycController.getClass().getDeclaredMethods()[0]);
exceptionResolver.resolveException(request, response, handlerMethod, e);
}
}
但是这里有个问题,通过doFilter处理过异常后,response的缓冲区是已提交状态,只能追加数据,不能重置数据
可以自定义ResponseWrap来达成对缓冲区的修改
参考:https://www.jianshu.com/p/5746b5876a46
解决方案有点偏,不尝试了。