阅读
上文
,了解到了可以通过自定义
HandlerExceptionResolver
实现来处理程序异常,当然
Spring MVC
也内置了一些实现来对异常处理进行支持。但是作为新时代的程序员,我估计已经很少人知道
HandlerExceptionResolver
这个异常处理器接口(更有甚者连
ModelAndView
都没听说过也大有人在啊),虽然这不应该,但存在即合理。因此从现象上可以认为使用自定义
HandlerExceptionResolver
实现的方式去处理异常已经out了,它已经被新的方式所取代:
@ExceptionHandler
方式,这就是本章节的核心议题,来探讨它的使用以及原理。
回忆上篇文章讲述
HandlerExceptionResolver
,你是否疑问过这个问题:
通过
HandlerExceptionResolver
如何返回一个json串呢
?其实这个问题雷同于:源生
Servlet
如何给前端返回一个json串呢?因为上文的示例都是返回的一个
ModelAndView
页面,so本文在最开头先解决这个疑问,为下面内容做个铺垫吧。
HandlerExceptionResolver如何返回JSON格式数据?
基于上篇文章案例自定义了一个异常处理器来处理
Handler
抛出的异常,示例中返回的是一个页面
ModelAndView
。但是通常情况下我们的应用都是
REST
应用,我们的接口返回的都是一个JSON串,那么若接口抛出异常的话我们处理好后也
同样的返回一个JSON串比返回一个页面更为合适
。
这时若你项目较老,使用的仍旧是
HandlerExceptionResolver
方式处理异常的话,我在本处提供两种处理方式,供以参考:
方式一:response直接输出json
自定义异常处理器(匿名实现):
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
// 自定义异常处理器一般请放在首位
exceptionResolvers.add(0, new AbstractHandlerExceptionResolver() {
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
try {
String jsonStr = "";
if (ex instanceof BusinessException) {
response.setStatus(HttpStatus.OK.value());
jsonStr = "{'code':100001,'message':'业务异常,请联系客服处理'}";
} else {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
jsonStr = "{'code':500,'message':'服务器未知异常'}";
response.getWriter().print(jsonStr);
response.getWriter().flush();
response.getWriter().close();
} catch (IOException e) {
e.printStackTrace();
return null;
访问截图如下:
注意事项:
- 因为
return null
,所以后面若还有处理器将继续执行。但因为本处已把response close了,因此请确保后面不会再使用此response
- 若所有
Resolver
处理完后还是return null
,那Spring MVC
将直接throw ex
,因此你看到的效果是:控制台上有异常栈,但是前段页面上显示是友好的json
串。
- 因为木有
ModelAndView
(值为null),所以不会有渲染步骤,因此后续步骤Spring MVC
也不会再使用到response(自定义的拦截器除外~)。
方式二:借助MappingJackson2JsonView
自定义异常处理器,借助MappingJackson2JsonView
这个json视图实现:
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
// 自定义异常处理器一般请放在首位
exceptionResolvers.add(0, new AbstractHandlerExceptionResolver() {
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView mv = new ModelAndView();
MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setJsonPrefix("fsxJson"); // 设置JSON前缀,有的时候很好用的哦
//view.setModelKey(); // 让只序列化指定的key
mv.setView(view);
// 这样添加key value就非常方便
mv.addObject("code", "100001");
mv.addObject("message", "业务异常,请联系客服处理");
return mv;
访问截图如下:
显然这种使用JsonView
的方式代码看起来更加舒服,使用起来更加的面向对象。
这两种方式都是基于自定义HandlerExceptionResolver
实现类的方式来处理异常,最终给前端返回一个json串。虽然方式二看起来步骤也不麻烦,也够面向对象,但接下来的@ExceptionHandler
方式可谓是杀手级的应用~
@ExceptionHandler
此注解是Spring 3.0
后提供的处理异常的注解,整个Spring
在3.0+
中新增了大量的能力来对REST
应用提供支持,此注解便是其中之一。
它(只能)标注在方法上,可以使得这个方法成为一个异常处理器,处理指定的异常类型。
// @since 3.0
@Target(ElementType.METHOD) // 只能标注在方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
// 指定异常类型,可以多个
Class<? extends Throwable>[] value() default {};
上篇讲解HandlerExceptionResolver
的原理部分讲到了,DispatcherServlet
对异常的处理最终都是无一例外的交给了HandlerExceptionResolver
异常处理器,因此很容易想到@ExceptionHandler
它的底层实现原理其实也是一个异常处理器,它便是:ExceptionHandlerExceptionResolver
。
在分析它之前,需要先前置介绍两个类:AbstractHandlerMethodExceptionResolver
和ExceptionHandlerMethodResolver
AbstractHandlerMethodExceptionResolver
它是ExceptionHandlerExceptionResolver
的抽象父类,服务于处理器类型是HandlerMethod
类型的抛出的异常,它并不规定实现方式必须是@ExceptionHandler
。它复写了抽象父类AbstractHandlerExceptionResolver
的shouldApplyTo
方法:
// @since 3.1 专门处理HandlerMethod类型是HandlerMethod类型的异常
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
// 只处理HandlerMethod这种类型的处理器抛出的异常~~~~~~
@Override
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
if (handler == null) {
return super.shouldApplyTo(request, null);
} else if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 可以看到最终getBean表示最终哪去验证的是它所在的Bean类,而不是方法本身
// 所以异常的控制是针对于Controller这个类的~
handler = handlerMethod.getBean();
return super.shouldApplyTo(request, handler);
} else {
return false;
@Override
@Nullable
protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
@Nullable
protected abstract ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);
此抽象类非常简单:规定了只处理HandlerMethod
抛出的异常。
ExceptionHandlerMethodResolver(重要)
它是一个会在Class及Class的父类中找出带有@ExceptionHandler
注解的类,该类带有key为Throwable
,value为Method
的缓存属性,提供匹配效率。
// @since 3.1
public class ExceptionHandlerMethodResolver {
// A filter for selecting {@code @ExceptionHandler} methods.
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
// 两个缓存:key:异常类型 value:目标方法Method
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
// 唯一构造函数
// detectExceptionMappings:传入method,找到这个Method可以处理的所有的异常类型们(注意此方法的逻辑)
// addExceptionMapping:把异常类型和Method缓存进mappedMethods里
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
// 找到此Method能够处理的所有的异常类型
// 1、detectAnnotationExceptionMappings:本方法或者父类的方法上标注有ExceptionHandler注解,然后读取出其value值就是它能处理的异常们
// 2、若value值木有指定,那所有的方法入参们的异常类型,就是此方法能够处理的所有异常们
// 3、若最终还是空,那就抛出异常:No exception types mapped to " + method
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<>();
detectAnnotationExceptionMappings(method, result);
if (result.isEmpty()) {
for (Class<?> paramType : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
if (result.isEmpty()) {
throw new IllegalStateException("No exception types mapped to " + method);
return result;
// 对于添加方法一样有一句值得说的:
// 若不同的Method表示可以处理同一个异常,那是不行的:"Ambiguous @ExceptionHandler method mapped for ["
// 注意:此处必须是同一个异常(比如Exception和RuntimeException不属于同一个...)
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
Method oldMethod = this.mappedMethods.put(exceptionType, method);
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" + oldMethod + ", " + method + "}");
// 给指定的异常exception匹配上一个Method方法来处理
// 若有多个匹配上的:使用ExceptionDepthComparator它来排序。若木有匹配的就返回null
@Nullable
public Method resolveMethod(Exception exception) {
return resolveMethodByThrowable(exception);
// @since 5.0 递归到了couse异常类型 也会处理
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
Throwable cause = exception.getCause();
if (cause != null) {
method = resolveMethodByExceptionType(cause.getClass());
return method;
//1、先去exceptionLookupCache找,若匹配上了直接返回
// 2、再去mappedMethods这个缓存里找。很显然可能匹配上多个,那就用ExceptionDepthComparator排序匹配到一个最为合适的
// 3、匹配上后放进缓存`exceptionLookupCache`,所以下次进来就不需要再次匹配了,这就是缓存的效果
// ExceptionDepthComparator的基本理论上:精确匹配优先(按照深度比较)
@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
this.exceptionLookupCache.put(exceptionType, method);
return method;
对于本类的功能,可总结如下:
- 找到指定
Class
类(可能是Controller
本身,也可能是@ControllerAdvice
)里面所有标注有@ExceptionHandler
的方法们
- 同一个
Class
内,不能出现同一个(注意理解同一个的含义)异常类型被多个Method
处理的情况,否则抛出异常:Ambiguous @ExceptionHandler method mapped for ...
1. 相同异常类型处在不同的Class内的方法上是可以的,比如常见的一个在Controller
内,一个在@ControllerAdvice
内~
- 提供缓存:
1. mappedMethods
:每种异常对应的处理方法(直接映射代码上书写的异常-方法映射)
2. exceptionLookupCache
:经过按照深度逻辑精确匹配上的Method方法
- 既能处理本身的异常,也能够处理
getCause()
导致的异常
ExceptionDepthComparator
的匹配逻辑是按照深度匹配。比如发生的是NullPointerException
,但是声明的异常有Throwable
和Exception
,这是它会根据异常的最近继承关系找到继承深度最浅的那个异常,即Exception
。
ExceptionHandlerExceptionResolver
(重要)
该子类实现就是用于处理标注有@ExceptionHandler
注解的HandlerMethod
方法的,是@ExceptionHandler
功能的实现部分。
请注意命名上和ExceptionHandlerMethodResolver
做区分~
// @since 3.1
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean {
// 这个熟悉:用于处理方法入参的(比如支持入参里可写HttpServletRequest等等)
@Nullable
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
// 用于处理方法返回值(ModelAndView、@ResponseBody、@ResponseStatus等)
@Nullable
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
// 消息处理器和内容协商管理器
private List<HttpMessageConverter<?>> messageConverters;
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
// 通知(因为异常是可以做全局效果的)
private final List<Object> responseBodyAdvice = new ArrayList<>();
@Nullable
private ApplicationContext applicationContext;
// 缓存:异常类型对应的处理器
// 它缓存着Controller本类,对应的异常处理器(多个@ExceptionHandler)~~~~
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
// 它缓存ControllerAdviceBean对应的异常处理器(@ExceptionHandler)
private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();
// 唯一构造函数:注册上默认的消息转换器
public ExceptionHandlerExceptionResolver() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
... // 省略所有的get/set方法
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
// 这一步骤同RequestMappingHandlerAdapter#initControllerAdviceCache
// 目的是找到项目中所有的`ResponseBodyAdvice`,然后缓存起来。
// 并且把它里面所有的标注有@ExceptionHandler的方法都解析保存起来
// exceptionHandlerAdviceCache:每个advice切面对应哪个ExceptionHandlerMethodResolver(含多个@ExceptionHandler处理方法)
//并且,并且若此Advice还实现了接口:ResponseBodyAdvice。那就还可干预到异常处理器的返回值处理上(基于body)
//可见:若你想干预到异常处理器的返回值body上,可通过ResponseBodyAdvice来实现哟~~~~~~~~~
// 可见ResponseBodyAdvice连异常处理方法也是生效的,但是`RequestBodyAdvice`可就木有啦。
initExceptionHandlerAdviceCache();
// 注册默认的参数处理器。支持到了@SessionAttribute、@RequestAttribute
// ServletRequest/ServletResponse/RedirectAttributes/ModelMethod等等(当然你还可以自定义)
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
// 支持到了:ModelAndView/Model/View/HttpEntity/ModelAttribute/RequestResponseBody
// ViewName/Map等等这些返回值 当然还可以自定义
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
// 处理HandlerMethod类型的异常。它的步骤是找到标注有@ExceptionHandler匹配的方法
// 然后执行此方法来处理所抛出的异常
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// 这个方法是精华,是关键。它最终返回的是一个ServletInvocableHandlerMethod可执行的方法处理器
// 也就是说标注有@ExceptionHandler的方法最终会成为它
// 1、本类能够找到处理方法,就在本类里找,找到就返回一个ServletInvocableHandlerMethod
// 2、本类木有,就去ControllerAdviceBean切面里找,匹配上了也是欧克的
// 显然此处会判断:advice.isApplicableToBeanType(handlerType) 看此advice是否匹配
// 若两者都木有找到,那就返回null。这里的核心其实是ExceptionHandlerMethodResolver这个类
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
// 给该执行器设置一些值,方便它的指定(封装参数和处理返回值)
if (this.argumentResolvers != null) {
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
if (this.returnValueHandlers != null) {
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
// 执行此方法的调用(比couse也传入进去了)
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
... // 下面处理model、ModelAndView、view等等。最终返回一个ModelAndView
// 这样异常梳理完成。
对它的功能,总结如下:
@ExceptionHandler
的处理和执行是由本类完成的,同一个Class上的所有@ExceptionHandler
方法对应着同一个ExceptionHandlerExceptionResolver
,不同Class上的对应着不同的~
- 标注有
@ExceptionHandler
的方法入参上可写:具体异常类型、ServletRequest/ServletResponse/RedirectAttributes/ModelMethod
等等
1. 注意:入参写具体异常类型时只能够写一个类型。(若有多种异常,请写公共父类,你再用instanceof来辨别,而不能直接写多个)
- 返回值可写:
ModelAndView/Model/View/HttpEntity/ModelAttribute/RequestResponseBody/@ResponseStatus
等等
@ExceptionHandler
只能标注在方法上。既能标注在Controller
本类内的方法上(只对本类生效),也可配合@ControllerAdvice
一起使用(对全局生效)
- 对步骤4的两种情况,执行时的匹配顺序如下:优先匹配本类(本
Controller
),再匹配全局的。
- 有必要再强调一句:
@ExceptionHandler
方式并不是只能返回JSON串,步骤4也说了,它返回一个ModelAndView
也是ok的
异常处理优先级
上篇文章 加上本文介绍了多种处理异常的方案,在实际生成环境中,我们的项目中一般确实也会存在多个HandlerExceptionResolver
异常处理器,那么对于抛出的一个异常,它的处理顺序到底是怎样的呢?
理解了DispatcherServlet
默认注册的异常处理器们和它们的执行原理后,再去解答这个问题就易如反掌了。这是DispatcherServlet
默认注册的异常处理器们:
所以在我们没有自定义HandlerExceptionResolver
来干扰这种顺序的情况下(绝大部分情况下我们都不会干扰它),最最最最先执行的便是@ExceptionHandler
方式的异常处理器,只有匹配不上才会继续执行其它的处理器。
根据此规律,我从使用层面总结出一个结论,供现在还不想深入理解原理的小伙伴参考和记忆:
@Controller + @ExceptionHandler
优先级最高
@ControllerAdvice + @ExceptionHandler
次之
HandlerExceptionResolver
最后(一般是DefaultHandlerExceptionResolver
)
全局异常示例
在很多Spring MVC
项目中你或许都可以看到一个名字叫GlobalExceptionHandler
(名字大同小异)的类,它的作用一般被标注上了@ControllerAdvice/@RestControllerAdvice
用于处理全局异常。
但据我了解,大多数项目对此类的设计是相当不完善的,它只做了一个通用处理:处理Exception
类型。显然这种宽泛的处理是很不优雅的,理应做细分,那么读了本文后我相信你已有能力去协助完善这一块内容,为你的团队带来改变哈。
此处我给出示例代码,抛砖引玉仅供参考:
@Slf4j
@RestControllerAdvice // 全部返回JSON格式,因为大都是REST项目
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
// 处理所有不可知的异常,作为全局的兜底
@ExceptionHandler(Exception.class)
AppResponse handleException(Exception e){
log.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail("未知错误,操作失败!");
return response;
// 处理所有业务异常(一般为手动抛出)
@ExceptionHandler(BusinessException.class)
AppResponse handleBusinessException(BusinessException e){
log.error(e.getMessage(), e);
AppResponse response = new AppResponse();
response.setFail(e.getMessage());
return response;
// 处理所有接口参数的数据验证异常(此处特殊处理了这个异常)
@ExceptionHandler(MethodArgumentNotValidException.class)
AppResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
log.warn(e.getMessage(), e); //此处我不建议使用error异常...
// 关于校验的错误信息的返回,此处我知识简单处理,具体你可以加强
AppResponse response = new AppResponse();
response.setFail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return response;
// 自己定制化处理HttpRequestMethodNotSupportedException这个异常类型喽
@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.warn(ex.getMessage());
String method = ex.getMethod();
String[] supportedMethods = ex.getSupportedMethods();
Map<String, Object> map = new HashMap<>();
map.put("code", status.value());
map.put("message", "不支持的请求类型:" + method + ",支持的请求类型:" + Arrays.toString(supportedMethods));
return super.handleExceptionInternal(ex, map, headers, status, request);
可能有人并不清楚为何我要继承ResponseEntityExceptionHandler
这个类,下面我就简单介绍一下它。
ResponseEntityExceptionHandler
它是个抽象类,可谓是Spring 3.2
后对REST
应用异常支持的一个暖心举动。它包装了各种Spring MVC
在处理请求时可能抛出的异常的处理,处理结果都是封装成一个ResponseEntity
对象。通过ResponseEntity
我们可以指定需要响应的状态码、header和body等信息~
因为它是个抽象类,所以我们要使用它只需要定义一个标注有@ControllerAdvice
的类继承于它便可(如上示例):
加上全局处理前(被DefaultHandlerExceptionResolver
处理的结果):
加上后:
因此个人建议若你是REST应用,可以在全局异常处理类上都设计为继承自此类,做兜底使用。它能处理的异常类型如下(同DefaultHandlerExceptionResolver
处理的异常类型):
ResponseEntityExceptionHandler:
@ExceptionHandler({
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class
@Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception { ... }
处理异常时又发生了异常怎么办呢?
这个问题你是否曾思考过呢?其实在理解了上文和本文的内容后,此问题的答案也就浮出水面了,强烈建议有兴趣的同学在本地调试出这种case,有助于你的理解~
结论:若处理器内部又抛出异常,一般就会交给tomcat处理把异常栈输出到前端,显示非常不友好的页面。因此:请务必保证你的异常处理程序中不要出现任何异常,保证健壮性。(当然最最最最为兜底的方案就是架构师统一设计一个HandlerExceptionResolver
放在末位,用最简单、最不会出bug的代码来处理一切前面不能处理的异常)
如何优雅统一处理Filter异常
因为我们无法通过@ControllerAdvice+@ExceptionHandler
的方式去处理Filter过滤器抛出的异常(理由希望读者自己能明白),所以此处我提供较为优雅的处理方式作为参考。
传统Spring MVC
指导思想步骤:
- catch住Filter所有异常
- 把
Exception
放进请求attr属性里
- 把该请求forward转发到专门处理错误的Controller里
- 该Controller里拿出异常throw出去,从而便可交给全局异常统一处理了
附参考代码:
Filter:
@Component("helloFilter")
@WebFilter(urlPatterns = "/*")
public class HelloFilter extends OncePerRequestFilter {
@Override
protected void initFilterBean() throws ServletException {
System.out.println("HelloFilter初始化...");
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
System.out.println(1 / 0);
filterChain.doFilter(request, response);
} catch (Exception e) { // 捕获所有异常做转发用
request.setAttribute(ErrorController.EXCEPTION_ATTR, e);
request.getRequestDispatcher(ErrorController.ERROR_URL).forward(request, response);
ErrorController:
@Slf4j
@RestController
public class ErrorController {
public static final String ERROR_URL = "/do/filter/errors";
public static final String EXCEPTION_ATTR = ErrorController.class.getName() + ".error";
* 把Filter里的异常同意交给全局异常处理
@GetMapping(value = "/do/filter/errors")
public void doFilterErrors(HttpServletRequest request) throws Exception {
throw Exception.class.cast(request.getAttribute(EXCEPTION_ATTR));
GlobalExceptionHandler:全局异常处理
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
// 处理所有不可知的异常,作为全局的兜底
@ExceptionHandler(Exception.class)
Object handleException(Exception e) {
log.error(e.getMessage(), e);
return "hello error";
Spring Boot
本文针对性的特别提出了SpringBoot case下的解决方案。因为SpringBoot
它会把所有的异常情况都转换为请求/error
,所以扩展它还是容易些的:
Filter:没必要自己catch了,交给SpringBoot全局处理即可
@Component("helloFilter")
@WebFilter(urlPatterns = "/*")
public class HelloFilter extends OncePerRequestFilter {
@Override
protected void initFilterBean() throws ServletException {
System.out.println("HelloFilter初始化...");
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println(1 / 0);
filterChain.doFilter(request, response);
@RestController
public class MyErrorController extends BasicErrorController {
// 最终使用的是此构造函数,所以魔方着只需要使用它即可
// return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
public MyErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
super(errorAttributes, serverProperties.getError());
@RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorJson(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
body.put("myErrorType", "this is my diy error");
return new ResponseEntity<>(body, status);
说明:若你在SpringBoot采用上面Spring MVC方式处理,优先级是更高的。至于为何:不解释
相关阅读:
web九大组件之—HandlerExceptionResolver异常处理器使用详解【享学Spring MVC】
本文呼吁,在实际生产中,请务必重视对异常的处理,我想表达的包含两层含义:
- 什么时候该抛异常,什么情况下它不是异常。(异常会使得JVM停顿,所以异常的使用请不要泛滥)
- 对于异常的统一处理,请务必要分而治之。不是所有异常都叫
Exception
~
1. 合理的处理异常,这对于微服务架构在服务治理层面具有重要的意义,这也是对一个优秀架构师的考验之一
本文推荐多使用@ExceptionHandler
方式去处理异常,因为它不仅书写方便、容易管理,而且还有缓存,效率也稍高一些。
Tips:@ExceptionHandler
仅能处理HandlerMethod
方式的异常。理论上是还可以有非HandlerMethod
的控制处理器的,但实际上真的还有吗?还有吗?有吗?