我们写代码离不开写try-catch语句,在Controller类里面,这也是一种处理异常的方法,但这种方法毕竟有很多弊端,一是我们在每个方法中都写try-catch很麻烦;二是不见得我们的代码能捕获所有异常。事实上SpringBoot 通过 spring-boot-starter-web 启动 WEB 容器的时候,会自动的提供一个映射,URL 是/error,同时会自动加载一个默认的错误处理控制器 - BasicErrorController,这个控制器处理的请求路径是 /error。如果控制器抛出异常,并没有处理的话,都会统一转发到一个 error.html 的错误结果页面,此页面由 SpringBoot(spring-boot-starter-web)提供。其中的处理逻辑是将异常信息分类封装到作用域中,并传递给视图error。
1.1 自定义错误页面
在 SpringBoot 启动的 WEB 应用中,如果一旦发生了异常,且代码中没有对异常做出处理,会自动将请求转发到'/error'路径上,BasicErrorController 会将异常错误信息收集,并通过Model 来传递错误信息,如果系统中没有提供错误页面,则会通过默认的错误页面来显示错误内容,这个默认错误页面是由 SpringBoot 提供的,大致内容如下:
如果需要提供一个统一的错误页面处理异常,可以在系统中提供一个 error.html 来实现。
BasicErrorController 收集的错误信息包含:
error - 错误描述,如: Internal Server Error (服务内部错误)
exception - 异常类型, 如: java.lang. ArithmeticException
message - 异常描述, 如: / by zero
timestamp - 时间戳
status - 响应状态码, 如: 200, 500, 404 等。
当系统报错时,返回到页面的内容通常是一些杂乱的代码段,这种显示对用户来说不友好,因此我们需要自定义一个友好的提示系统异常的页面。
在 src/main/resources 下创建 /public/error,在该目录下再创建一个名为 5xx.html 文件,该页面的内容就是当系统报错时返回给用户浏览的内容:
系统错误
系统内部错误
Spring Boot 会在系统报错时将返回视图指向该目录下的文件。如下图:
上边处理的 5xx 状态码的问题,接下来解决 404 状态码的问题。
当出现 404 的情况时,用户浏览的页面也不够友好,因此我们也需要自定义一个友好的页面给用户展示。
在 /public/error 目录下再创建一个名为 404.html 的文件:
访问异常
找不到页面
我们请求一个不存在的资源,如:http://localhost:8080/hi,结果如下图:
虽然通过定义页面,我们可以对错误进行展示,但很明显,所有的错误都是相同的展示,在实际生产中是达不到我们的业务需要的。
1.2@ExceptionHandler
Spring 支持异常的细致化处理,可以在控制器中定义若干方法,专门用于处理异常。处理异常的方法使用注解@ExceptionHandler 来描述,注解的 value 属性是 Class[]类型,代表该方法可以处理的异常种类。我们写个简单的例子来说明下:
@RestController
@RequestMapping("/handler")
public class HandlerController {
@RequestMapping("/test")
public void test() {
throw new NullPointerException("出错了!");
}
@ExceptionHandler({NullPointerException.class})
public String exception(NullPointerException e) {
System.out.println(e.getMessage());
e.printStackTrace();
return "null pointer exception";
}
}
这种异常处理方式力度较细,处理方式多样化。其缺陷是,如果定义了太多的异常处理方法,会提升维护成本;且异常处理方法只对当前控制器有效。代码有 bad smell。
这种开发方式适合针对性处理。 因为定义在控制器中的异常处理方法处理优先级最高 。
1.3@ControllerAdvice&@ExceptionHandler
Spring 支持控制器 Advice 定义。原理是使用AOP对Controller控制器进行增强(前置增强、后置增强、环绕增强,AOP原理请自行查阅);可以独立定义一个类型作为 ControllerAdvice,类型需要使用 @ControllerAdvice来描述,类型中定义若干异常处理方法,方法使用@ExceptionHandler 来描述。
当应用中发生异常时,异常的处理顺序是: 当前控制器中定义的@ExceptionHandler方法 -> @ControllerAdvice 类中定义的@ExceptionHandler 方法 -> BasicErrorController 中定义的服务方法(error.html 默认异常页面)
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 自定义errors.html,返回错误信息
* @param request
* @param response
* @param e
* @return
* @throws Exception
*/
@ExceptionHandler(value = Exception.class)
public Object errorHandler(HttpServletRequest request,
HttpServletResponse response, Exception e) {
e.printStackTrace();
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", request.getRequestURL());
mav.setViewName("my_error");
return mav;
}
}
@Controller
@RequestMapping("/advice")
public class AdviceController {
@RequestMapping("/hello")
public ModelAndView hello() {
ModelAndView mav = new ModelAndView("index");
mav.addObject("msg", "hello");
int result=5/0;
return mav;
}
}
使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,只要设计得当,就再也不用在 Controller 层进行 try-catch 了!而且,@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 BindingResult/Errors 了!
优缺点
1.4HandlerExceptionResolver
Spring支持使用@Configuration的配置方式,为控制器配置一个HandlerExceptionResolver 来处理异常。
HandlerExceptionResolver 是一个接口,这个接口有若干实现类,常用实现类是
SimpleMappingExceptionResolver。这个类型通过一个 Properties 对象来配置异常种类和错误页面的映射关系,并通过 Model 向客户端传递异常对象,attribute 名称为 exception。在视图逻辑中,可以通过异常对象来获取需要使用的异常信息。
当应用中发生异常时,异常的处理顺序是: 当前控制器中定义的@ExceptionHandler
方法 -> @ControllerAdvice 类中定义的@ExceptionHandler 方法 -> @Configuration 类中配置
的 HandlerExceptionResolver -> BasicErrorController 中定义的服务方法(error.html 默认异常
页面)
优点: 使用一个 Configuration 来定义若干的错误处理映射,开发效率更高。
缺点: 优先级太低。会被大多数的异常处理逻辑覆盖。
1.5 自定义 HandlerExceptionResolver
HandlerExceptionResolver 也可以自定义提供实现。只需要为其定义的抽象方法提供实现即可:
/**
* 处理异常逻辑,在方法中,只需要对参数 ex 进行逻辑控制,并提供相应的返回结果,
* 即可实现异常逻辑的自定义处理。
* @param request - 请求对象
* @param response - 应答对象
* @param handler - 发生异常的控制器对象
* @param ex - 发生的异常对象
* @return - 返回的数据模型和视图
*/
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler,Exception ex) {
return null;
}
我们写个例子说明
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) {
String ex=null;
if( e instanceof Exception){
ex=e.getMessage();
}else{
ex="未知错误";
}
ModelAndView mv=new ModelAndView();
mv.addObject("exception", e);
mv.addObject("url", httpServletRequest.getRequestURL());
mv.setViewName("my_error");
return mv;
}
}
至于controller类就不再写了,想看的话可以看下我的代码
参考:
https://www.cnblogs.com/moonlightL/p/7891806.html
https://www.jianshu.com/p/d44dc345bb10
https://blog.csdn.net/Cychronized/article/details/80960905
https://www.cnblogs.com/xuwujing/p/10933082.html
https://www.cnblogs.com/kangyuanjie/p/8559350.html
https://www.xttblog.com/?p=3586
https://www.cnblogs.com/shuimuzhushui/p/6791600.html
https://my.oschina.net/xiedeshou/blog/1859811