必看!!减少 try catch ,可以这样干
软件开发过程中,不可避免的是需要处理各种异常
,就我自己来说,至少有一半以上的时间都是在处理各种异常情况
,所以代码中就会出现大量的try {...} catch {...} finally {...} 代码块
,不仅有大量的冗余代码
,而且还影响代码的可读性
既然想要业务代码不显式地对异常进行捕获、处理
,而异常肯定还是处理的
,不然系统岂不是动不动就崩溃了,所以必须得有其他地方捕获并处理这些异常。
默认情况下,在遇到异常时,SpringBoot 会自动跳到一个统一的异常页面,Spring Boot提供/error处理所有错误的映射
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
SpringBoot 默认的异常处理机制:一旦程序中出现了异常 SpringBoot 就会请求 /error 的 url 。在 SpringBoot 中提供了一个叫 BasicErrorController 来处理 /error 请求,然后跳转到默认显示异常的页面来展示异常信息。
接下来就是自定义异常错误页面了,方法很简单,就是在目录 src/main/resources/templates/ 下定义一个叫 error 的文件,可以是 jsp 也可以是 html
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>自定义 springboot 异常处理页面title>
head>
<body>
Springboot BasicExceptionController 错误页面
<br>
<span th:text="${msg}">span>
body>
html>
也可以在error文件夹下放相应状态码的页面,error/下的4xx,5xx页面会被自动解析,有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
error/下的4xx,5xx页面会被自动解析原理
系统默认的异常解析器为 DefaultErrorAttributes
DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null
默认没有任何人能处理异常,所以异常会被抛出
默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
优先级
* <li>{@code '/<templates>/error/404.<ext>'}</li>
* <li>{@code '/<static>/error/404.html'}</li>
* <li>{@code '/<templates>/error/4xx.<ext>'}</li>
* <li>{@code '/<static>/error/4xx.html'}</li>
DefaultErrorViewResolver 源码,会拼接出 error/404.html 访问路径
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
模板引擎最终响应这个页面 error/500.html
Spring3.2
版本增加了一个注解@ControllerAdvice
,可以与@ExceptionHandler
、@InitBinder
、@ModelAttribute
等注解注解配套使用
跟异常处理相关
的只有注解@ExceptionHandler
,从字面上看,就是 异常处理器
的意思,其实际作用也是:
若在某个 Controller类 定义一个 异常处理方法
并 在方法上添加该注解
那么当 出现指定的异常 时
执行该处理异常的方法
其可以使用springmvc提供的数据绑定
,比如注入HttpServletRequest
等,还可以接受一个当前抛出的Throwable对象
。
但是,这样就必须在每一个Controller类都定义一套这样的异常处理方法
,因为异常可以是各种各样。这样就会造成大量的冗余代码
,而且若需要新增一种异常的处理逻辑,就必须修改所有Controller类
了,很不优雅。
当然你可能会说,那就定义个类似BaseController的基类,这样总行了吧。
这种做法虽然没错,但仍不尽善尽美,因为这样的代码有一定的侵入性和耦合性
。简简单单的Controller,我为啥非得继承这样一个类呢,万一已经继承其他基类了
呢。大家都知道Java只能继承一个类
使用这个注解就容易了,但是只能处理使用 @ExceptionHandler 注解的方法的 Controller 的异常
,对于其他 Controller 的异常就无能为力
了,只能再使用同样的方法将使用 @ExceptionHandler 注解的方法写入要捕获异常的 Controller 中,所以不推荐使用
。
使用方式:在ExceptionController
中 加入使用 @ExceptionHandler 注解的方法
代码,整个 ExceptionController
代码如下:
@Controller
public class ExceptionController {
/**
* 描述:捕获 ArithmeticException 异常
* @param model 将Model对象注入到方法中
* @param e 将产生异常对象注入到方法中
* @return 指定错误页面
*/
@ExceptionHandler(value = {ArithmeticException.class})
public String arithmeticExceptionHandle(Model model, Exception e) {
model.addAttribute("msg", "@ExceptionHandler" + e.getMessage());
log.info(e.getMessage());
return "error";
}
/**
* 服务器异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public R<String> exception(Exception e) {
log.error("服务器异常! msg: -> ", e);
return R.failed("服务器异常!");
}
}
代码说明:
注解 @ExceptionHandler
中 value 的值为数组
,表示指定捕获的异常类型
,这里表示捕获 ArithmeticException
异常,跳转的页面为统一的 error.html 页面
,model.addAttribute("msg", "@ExceptionHandler" + e.getMessage())
用来区分是 SpringBoot 处理的异常还是我们自己的方法处理的异常
,也是使用这个方式来区分。
@ExceptionHandler(value = MyException.class) -- 注解类型
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) -- 错误码
@ResponseBody -- 返回json
那有没有一种方案,既不需要跟Controller耦合
,也可以将定义的 异常处理器 应用到所有控制器呢?所以注解@ControllerAdvice
出现了,简单的说,该注解可以把异常处理器应用到所有控制器
,而不是单个控制器
。
借助该注解,我们可以实现:在独立的某个地方
,比如单独一个类
,定义一套对各种异常的处理机制
,然后在类的签名加上注解@ControllerAdvice
,统一对 不同阶段的、不同异常 进行处理
。这就是统一异常处理的原理。
使用 @ControllerAdvice
+@ExceptionHandler
注解能够处理全局异常
,这种方式推荐使用,可以根据不同的异常对不同的异常进行处理
。底层是 ExceptionHandlerExceptionResolver 支持
的
如果需要处理其他异常,例如 NullPointerException
异常,则只需要在 GlobalException 类中定义一个方法使用 @ExceptionHandler(value = {NullPointerException.class}) 注解该方法
,在该方法内部处理异常就可以了。
使用方式:定义一个类,使用 @ControllerAdvice 注解该类
,使用 @ExceptionHandler 注解方法
,这里定义了一个 GlobalException 类表示来处理全局异常,
代码如下:
@ControllerAdvice
//或者 @RestControllerAdvice 内部有 @ControllerAdvice 注解
@Order(Ordered.HIGHEST_PRECEDENCE) //代表这个过滤器在众多过滤器中级别最高,也就是过滤的时候最先执行
public class GlobalException {
/**
* 描述:捕获 ArithmeticException 异常
* @param model 将Model对象注入到方法中
* @param e 将产生异常对象注入到方法中
* @return 指定错误页面
*/
@ExceptionHandler(value = {ArithmeticException.class})
public String arithmeticExceptionHandle(Model model, Exception e) {
model.addAttribute("msg", "@ControllerAdvice + @ExceptionHandler :" + e.getMessage());
log.info(e.getMessage());
return "error";
}
/**
* 服务器异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R<String> exception(Exception e) {
log.error("服务器异常! msg: -> ", e);
return R.failed("服务器异常!");
}
}
实现 HandlerExceptionResolver
重写 resolveException 方法
处理异常;可以作为默认的全局异常处理规则
注意:在类上加上 @Configuration 注解
@Configuration
@Order(value= Ordered.HIGHEST_PRECEDENCE) //优先级,数字越小优先级越高
public class HandlerExceptionResolverImpl implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", "实现 HandlerExceptionResolver 接口处理异常");
//判断不同异常类型,做不同视图跳转
if(ex instanceof ArithmeticException){
modelAndView.setViewName("error");
}
return modelAndView;
}
}
通过ErrorViewResolver
实现自定义处理异常,原理:
basicErrorController
要去的页面地址是ErrorViewResolver
自己调用
response.error,请求也会被转发给basicErrorController
进行处理。自己没有调用
,并且异常没有任何人能够处理
,tomact底层会自动调用response.sendError将请求转发给basicErrorController处理
,里面使用了ErrorViewResolver进行解析
。ErrorViewResolver
会转到一个页面或者返回json数据
底层是ResponseStatusExceptionResolver
,把responsestatus注解的信息底层调用
response.sendError(statusCode, resolvedReason)
@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException {
public UserTooManyException(){
}
public UserTooManyException(String message){
super(message);
}
}
使用时直接throw new UserTooManyException()
通常情况下,我们都会定义一个全局异常处理类来处理异常,但是当我们定义了多个异常处理类,我们如何去保证它的执行顺序
呢?
可以通过@Order
注解来设置处理器的捕获执行顺序
,如果没有该注解,他就会使用默认值
,也就是Ordered.LOWEST_PRECEDENCE
。
越小的优先执行
,所以我们只需要在全局参数异常处理器加上
@Order(Ordered.LOWEST_PRECEDENCE - 2)
就能保证它的执行顺序优先于全局异常处理器
。