1.简介与代码
异常处理方式一. @ExceptionHandler
异常处理方式二. 实现HandlerExceptionResolver接口
异常处理方式三. @ControllerAdvice+@ExceptionHandler
package com.example.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
/**
* 异常处理器demo
*/
@RestController
public class ControllerAdviceDemo {
/**
* demo1,用于测试当前类异常处理器
*/
@RequestMapping("/demo1")
@ResponseBody
public Object demo1() {
int i = 1 / 0;
return new Date();
}
/**
* 方式1:controller中使用ExceptionHandler注解处理异常
* 注意事项: 1. 一个Controller下多个@ExceptionHandler上的异常类型不能出现一样的,否则运行时抛异常.
* Ambiguous @ExceptionHandler method mapped for;
* 2. @ExceptionHandler下方法返回值类型支持多种,常见的ModelAndView,@ResponseBody注解标注,ResponseEntity等类型都OK.
*/
@ExceptionHandler({RuntimeException.class})
public String fix(Exception ex) {
System.out.println("方式1,do This");
return ex.getMessage();
}
/**
* demo2,用于测试全局异常处理器
*/
@RequestMapping("/demo2")
@ResponseBody
public Object demo2(String msg) throws Exception {
System.out.println("进入demo2");
if (StringUtils.isEmpty(msg)) {
throw new Exception("msg为空");
}
return new Date();
}
}
/**
* 方式2:全局异常处理,HandlerExceptionResolver方式
* 只能返回ModelAndView,不能返回json,不常用。
*/
@Component
class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("方式2,发生全局异常!");
ModelMap mmp = new ModelMap();
mmp.addAttribute("ex", ex.getMessage());
response.addHeader("Content-Type", "application/json;charset=UTF-8");
try {
new ObjectMapper().writeValue(response.getWriter(), ex.getMessage());
response.getWriter().flush();
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
/**
* 方式,3:全局异常处理器,所有controller,抛出方法对应的异常将被捕获
* 用法说明: 这种情况下 @ExceptionHandler 与第一种方式用法相同,
* 返回值支持ModelAndView,@ResponseBody等多种形式.
*/
@ControllerAdvice
class GlobalController {
@ResponseBody
@ExceptionHandler(Exception.class)
public String fix1(Exception e) {
System.out.println("方式3,全局的异常处理器");
return e.getMessage();
}
}
2.调用demo1
请求:
日志:
2020-11-26 12:40:04.827 INFO 58496 --- [nio-8081-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-11-26 12:40:04.827 INFO 58496 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-11-26 12:40:04.845 INFO 58496 --- [nio-8081-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 17 ms
方式1,do This
2020-11-26 12:40:04.962 WARN 58496 --- [nio-8081-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [java.lang.ArithmeticException: / by zero]
3.调用demo2
请求:
日志:
进入demo2
方式3,全局的异常处理器
2020-11-26 12:43:26.406 WARN 58496 --- [nio-8081-exec-3] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [java.lang.Exception: msg为空]
注意:因为方式3的优先级高于方式2,所以这里被方式3捕获到。若去掉方式3的代码,则被方式2捕获。
4. 比较说明
方式1,@Controller+@ExceptionHandler
方式2,HandlerExceptionResolver接口形式
方式3(推荐),@ControllerAdvice+@ExceptionHandler优缺点说明:
在Spring4.3.0版本下,
-
优先级来说,@Controller+@ExceptionHandler优先级最高,其次是@ControllerAdvice+@ExceptionHandler,最后才是HandlerExceptionResolver,说明假设三种方式并存的情况 优先级越高的越先选择,而且被一个捕获处理了就不去执行其他的.
-
三种方式都支持多种返回类型,@Controller+@ExceptionHandler、@ControllerAdvice+@ExceptionHandler可以使用Spring支持的@ResponseBody、ResponseEntity,而HandlerExceptionResolver方法声明返回值类型只能是 ModelAndView,如果需要返回JSON、xml等需要自己实现.
-
缓存利用,@Controller+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerCache,@ControllerAdvice+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中, 而HandlerExceptionResolver接口是不做缓存的,在前面两种方式都fail的情况下才会走自己的HandlerExceptionResolver实现类,多少有点性能损耗.