Spring Boot之异常处理

关于异常的理解

首先要清楚,一切异常对系统来说,都是不正常的表现,尽管有时由于业务处理的需要我们会主动抛出一些异常,但也不意味对这些异常可以不管不顾,总有一个地方需要对自己抛出的异常进行特殊处理。

在日常开发中,我们应该尽量提高系统可用性,最大限度的避免任何异常的出现,而不是去指望完善异常处理来完善系统。
异常处理是异常无法避免的出现后而采取的一种应急措施,主要目的是对外增加友好性,对内提供补救措施。

异常处理很重要,但不要因此就认为完善的异常处理是系统核心,不要指望异常处理尽善尽美,不要指望异常处理来给系统缺陷擦屁股。

如果系统异常过多,那么我们要做的不是去尽可能的完善异常处理机制,而是要好好去反思:系统架构设计是否合理,系统逻辑设计是否合理。

Spring Boot 中全局异常处理

在spring boot中提供了默认的异常处理机制:Spring Boot 默认提供了程序出错的结果映射路径/error。这个/error请求会在BasicErrorController中处理,其内部是通过判断请求头中的Accept的内容是否为text/html来区分请求是来自客户端浏览器(浏览器通常默认自动发送请求头内容Accept:text/html)还是客户端接口的调用,以此来决定返回页面视图还是 JSON 消息内容。
默认返回的视图或Json消息内容如下:

{
    "timestamp": "2018-05-12T06:11:45.209+0000",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/index.html"
} 

在这里插入图片描述
很显然,把这样的响应结果返回给前端或者浏览器不太友好。在实际开发中,如果需要返回页面,我们也是希望返回公司自定义的统一的错误页面,而不是“Whitelabel Error Page”;如果要返回json也最好是公司统一规定格式的json,这样有利于前后端分离更好的协作。幸运的是,在Spring boot中我们可以实现这样的愿望。

@ControllerAdvice+ @ExceptionHandler

这种组合可以实现全局的异常统一处理,既可以返回json格式的结果,也可以返回指定的错误页。事实上,@ControllerAdvice+ @ExceptionHandler这种组合方式并不是spring boot特有的,而是spring提供的异常处理方式,使用如下:

@ControllerAdvice
public class GlobalExceptionController {

	//返回json格式的响应结果
    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public BaseResp exceptionHandler(RuntimeException e, HttpServletResponse response) {
        BaseResp resp = new BaseResp();
        resp.setCode(300);
        resp.setMsg("未知错误");
        return resp;
    }

	//返回页面 ,这里有多种实现方式,错误页面如何放置取决于我们使用什么样的模板引擎或者是否使用模板引擎,这里不多讲述,网上有大把的参考样例,这里想说的是,现在基本都是前后端分离的开发模式,所以这种直接返回页面的方式其实很少使用
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);  //异常内容(页面展示)
        mav.addObject("url", req.getRequestURL()); //请求的url地址(页面展示)
        mav.setViewName("error"); //设置视图名称
        return mav;
    }

}

注解解释:

  • @ControllerAdvice:属于类注解,被此注解标识的类可以处理全局的异常
  • @ExceptionHandler:属于方法注解,用于指定此方法可以处理的异常类型,如:@ExceptionHandler(value = Exception.class)

注意事项:
@ControllerAdvice+ @ExceptionHandler虽然可以处理全局的异常,但是这种方式只能处理应用级别的异常,有一些容器级别的异常它就无能为力了,例如Filter抛出异常,若需要处理这类容器级别的异常,我们只须要提供自定义的ErrorController便可,自定义的ErrorController有两种方式:一种是实现ErrorController接口,另外一种是直接继承BasicErrorController,因为接口只提供一个待实现的方法,而BasicErrorController已实现了不少功能,所以我们可以选择继承BasicErrorController来实现自己的ErrorController。

有时还会遇到@RestontrollerAdvice注解,它相当于@ResponseBody + @ControllerAdvice,如果我们的全局异常处理类中的方法返回的都是Json格式的结果,那就可以在类上使用@RestontrollerAdvice注解,同时可以省去每个方法上的@ResponseBody注解。

另外,如果我们希望全局异常处理类只特定的处理某些Controller的异常,可以通过@ControllerAdvice的basePackages属性来指定范围。

除了全局异常处理,还有局部异常处理 @Controller + @ExceptionHandler
局部异常处理就是在@Controller类中定义一个被@ExceptionHandler注解的异常处理方法,当此Controller中的方法抛出异常且异常类型符合@ExceptionHandler指定的异常类型时,就会被异常处理方法捕获并处理,但是这个异常处理方法作用的范围仅限于此Controller。

@Controller
public class GlobalExceptionController {
    @Autowired
    private UserService userService;

    @RequestMapping("/test1")
    @ResponseBody
    public User test1(int userId) {
        return userService.getUser(userId);
    }

    @ExceptionHandler(value = Exception.class) 
    public BaseResp defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        BaseResp resp = new BaseResp();
        resp.setCode(300);
        resp.setMsg("未知错误");
        return resp;
    }
}

ErrorController接口实现全局异常处理

上面说过@ControllerAdvice只能统一处理应用级别的异常,而对于容器级别的异常,如Filter异常,@ControllerAdvice就无能为力了,这时就需要我们自定义ErrorController来处理全局异常,具体方式有两种:一种是实现ErrorController接口,另外一种是直接继承BasicErrorController。因为BasicErrorController本身就实现了ErrorController接口,并提供了额外的功能,所以这里使用继承BasicErrorController的方式来实现全局异常处理。

事实上,通过观察 BasicErrorController 可以发现,它处理的就是 /error 请求,所以我们只需要继承 BasicErrorController 之后,重写 /error方法,然后在 error() 方法里面对全局异常进行统一处理即可。代码如下:

@RestController
public class GlobalExceptionController extends BasicErrorController {

    private static final Logger log = LoggerFactory.getLogger(ErrorController.class);

    public GlobalExceptionController () {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }

    /**
     * produces 设置返回的数据类型:application/json
     *
     * @param request 请求
     * @return 自定义的返回实体类
     */
    @Override
    @RequestMapping(value = "", produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        // 获取错误信息
        String message = body.get("message").toString();
        int code = EnumUtil.getCodeByMsg(message, ResultEnum.class);

        HttpStatus httpStatus;
        if (code == 500) {
            // 服务端异常,状态码为500
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
        } else {
            // 其余异常(手动throw)为逻辑校验,状态码为200
            httpStatus = HttpStatus.OK;
        }
        return new ResponseEntity(Result.failed(code, message), httpStatus);
    }

}
  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot中,我们可以使用全局异常处理来统一处理应用程序中发生的异常。全局异常处理可以捕获并处理所有的异常,无论是在控制器层还是其他层级中抛出的异常。 要实现全局异常处理,可以按照以下步骤进行操作: 1. 创建一个用于处理全局异常的类,可以命名为GlobalExceptionHandler。 2. 在该类上使用@ControllerAdvice注解,以便让Spring Boot知道这是一个全局异常处理器。 3. 在该类中定义方法来处理各种类型的异常。这些方法需要使用@ExceptionHandler注解进行标记,并指定要处理的异常类型。 4. 在方法中定义处理异常的逻辑,例如返回自定义的错误消息、错误码等。 5. 可以选择将错误消息封装成一个自定义的响应实体类,以便在控制器中统一返回该实体类。 以下是一个简单的示例: ```java @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleException(Exception ex) { ErrorResponse errorResponse = new ErrorResponse(); errorResponse.setMessage("An error occurred"); errorResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); // 其他自定义的错误处理逻辑 return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } } ``` 在上述示例中,我们使用了Exception.class来处理所有类型的异常。在实际应用中,可以根据需要定义多个不同类型的异常处理方法。 需要注意的是,全局异常处理只能处理在Spring容器中抛出的异常。如果异常发生在过滤器(Filter)、拦截器(Interceptor)或Servlet中,则需要使用其他方式进行处理。 希望以上信息对你有帮助!如果你有更多的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值