本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!
- 🚀 魔都架构师 | 全网30W技术追随者
- 🔧 大厂分布式系统/数据中台实战专家
- 🏆 主导交易系统百万级流量调优 & 车联网平台架构
- 🧠 AIGC应用开发先行者 | 区块链落地实践者
- 🌍 以技术驱动创新,我们的征途是改变世界!
- 👉 实战干货:编程严选网
1 错误场景
public class UserController {
public UserController() {
log.info("construct");
}
@GetMapping("/reg/{name}")
@ResponseBody
public String saveUser(String name) {
log.info("register JavaEdge success!");
return "OK";
}
}验证请求的Token合法性的Filter。Token校验失败时,直接抛自定义异常,移交给Spring处理:
@WebFilter
@Component
@Slf4j
public class PermissionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("token");
if (!"JavaEdge".equals(token)) {
log.info("throw IllegalRequestException");
resolver.resolveException(httpServletRequest, httpServletResponse, null,
new IllegalRequestException());
}
chain.doFilter(request, response);
}
}public class IllegalRequestException extends RuntimeException {
public IllegalRequestException() {
super();
}
}@RestControllerAdvice
public class IllegalRequestExceptionHandler {
@ExceptionHandler(IllegalRequestException.class)
@ResponseBody
public String handle() {
System.out.println("403");
return "{\"code\": 403}";
}
}测试HTTP请求:

日志输出如下:说明IllegalRequestExceptionHandler未生效。

why?需精通Spring异常处理流程。
2 解析

当所有Filter执行完毕,Spring才处理Servlet相关,在DispatcherServlet,Spring处理了请求和处理器的对应关系及统一异常处理。
Filter内异常无法被统一处理,因为异常处理发生在DispatcherServlet#doDispatch(),但此时,过滤器已全部执行完。
3 Spring异常统一处理
3.1 Spring加载并暴露ControllerAdvice
WebMvcConfigurationSupport#handlerExceptionResolver()
实例化并注册一个ExceptionHandlerExceptionResolver:
@Bean
public HandlerExceptionResolver handlerExceptionResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
// 添加默认异常解析器
addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}最终,Spring实例化ExceptionHandlerExceptionResolver类:
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {
@Override
public void afterPropertiesSet() {
initExceptionHandlerAdviceCache();
...
}initExceptionHandlerAdviceCache
完成所有ControllerAdvice中的ExceptionHandler初始化:查找 @ControllerAdvice 注解的Bean集,放入exceptionHandlerAdviceCache。这里看到自定义illegalRequestExceptionHandler:

private void initExceptionHandlerAdviceCache() {
...
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
...
}所有被 @ControllerAdvice 注解的异常处理器,都在 ExceptionHandlerExceptionResolver 实例化时自动扫描并装载在其exceptionHandlerAdviceCache。
initHandlerExceptionResolvers
当第一次客户端请求发生时,DispatcherServlet#initHandlerExceptionResolvers() 获取所有注册到 Spring 的 HandlerExceptionResolver 实例(如ExceptionHandlerExceptionResolver),存到handlerExceptionResolvers:
/** List of HandlerExceptionResolvers used by this servlet. */
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
...
}3.2 ControllerAdvice如何被Spring消费并处理异常?
3.2.1 DispatcherServlet
① doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
throws Exception {
// ...
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// ...
// 查找当前请求对应的 handler, 并执行
// ...
}
catch (Exception ex) {
// 先赋值
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 再移交
processDispatchResult(processedRequest, response, mappedHandler, mv,
dispatchException);
}
// ...
}② processDispatchResult
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 当Exception非空时,继续移交
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
}③ processHandlerException
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
// 从 handlerExceptionResolvers 获取有效的异常解析器以解析异常
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
}这里的 handlerExceptionResolvers 一定包含声明的IllegalRequestExceptionHandler#IllegalRequestException 的异常处理器的 ExceptionHandlerExceptionResolver 包装类。
4 修正
为利用到 Spring MVC 异常处理机制,改造Filter:
- 手动捕获异常
- 将异常通过 HandlerExceptionResolver 解析处理
修改 PermissionFilter,注入 HandlerExceptionResolver:
@WebFilter
@Component
@Slf4j
public class PermissionFilter implements Filter {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;然后,在 doFilter 捕获异常并移交 HandlerExceptionResolver:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String token = httpServletRequest.getHeader("token");
if (!"JavaEdge".equals(token)) {
log.info("throw IllegalRequestException");
// see!
resolver.resolveException(httpServletRequest, httpServletResponse, null,
new IllegalRequestException());
return;
// see!
}
chain.doFilter(request, response);
}再用错误 Token 请求,日志:
[21:40:21.095] [http-nio-12345-exec-1] [INFO ] [c.j.spring.exception.PermissionFilter:35 ] - throw IllegalRequestException 403响应体:
{
"code": 403
}
















