Java 系列-Exception

10分钟阅读时长

在 Java 中,Exception 被单独列为了一章,足以证明其独立性与重要性。所以什么是异常?

Java 的基本理念是“结构不佳的代码不能运行”。

对于程序 Error 而言,恢复系统 Error 十分重要,而 Exception 作为一种对 Error 改善的机制,能够极大的提高代码健壮性

对于 Exception , 在编译期间发现它自然是最好的时机,因为此时我们可以立即更改代码,提高健壮性来解决问题。

但是, 有些错误,属于运行时错误 RuntimeException, 只有在程序编译完成后,在 JVM 中运行时,才能发现这种隐患。那么此时就需要有一种方式,将错误的信息传递出来,让接收者可以收到信息,以便定位问题,解决问题。

Exception 就是这样一种错误报告机制

基本异常

对于异常来说,根据异常情形(exceptional condition)可以分为两种情况:

  • 普通问题:在当前环境下能得到足够的信息,总能处理这个错误。也就是你可以通过修改代码让这个问题不会再发生。
  • 异常问题: 我们的代码在当前环境(代码块)由于缺少某些信息数据,导致程序不能再继续执行。也就是代码会在特定情形下发生错误。

此时,对于异常问题,你所能做的事情就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。

当抛出异常后,有几件事会随之发生。

首先,同 Java 中其他对象的创建一样,我们会使用关键字 new 在堆上创建异常对象。然后,当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。

举一个抛出异常的简单例子:

if( obj == null ){
  throw new NullPointerException();
}

它意味着,当前代码可能出现为 obj 为空的情况,出现这种情况时,由于执行了 “throw 抛出” 这种操作,因此可以在别的地方(稍后会介绍)来接收并处理错误信息。
于是乎,在当前环境(代码块),我们不必再为 obj 可能为空 这一情况所担心,它会在其他地方被处理。

异常的参数

与使用 Java 中的其他对象一样,我们总是用关键字 new 在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。

所有标准异常类都有两个构造器:一个是无参构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:

throw new NullPointerException("t = null");

异常处理程序

前面提到了,抛出的异常会在其他“地方”被处理,这个其他地方叫做 “异常处理程序”. 而 try{}catch(e){}中的 catch块就是异常处理程序

try {
    // 可能产生异常的代码
    int x = 1;
    int y = x/0;

} catch(Type1 id1) {
    // 异常处理程序:专用于处理类型Type1的异常
    onMyException1();
} catch(Type2 id2) {
    // 异常处理程序:专用于处理类型Type2的异常
    onMyException2();
} catch(Type3 id3) {
    // 异常处理程序:专用于处理类型Type3的异常
    onMyException3();
} finally {
    // 总会执行
    customExceptionHandler();
}

实践:spring 中的异常

在 spring 框架中,spring 已经对异常进行了抽象封装,便于我们去管理创建异常处理程序。你总不想看到代码里面,这里一块 try…catch ,那里一块 try…catch 对吧。

spring 提供了多种处理异常的解决方案,你只需要记住 @ControllerAdvice 这一种即可。

  1. 通过 @ControllerAdvice 标记的类GlobalExceptionHandler,被称之为为 全局异常处理程序。看看上面,catch 块 可以有多个,他们分别用于处理不同的异常.
  2. 因此,我们可以创建多个异常处理程序在 customErrorHandler(), onMyException11(),onMyException2() 来专门处理不同类型的异常。其中 customErrorHandler 被用来返回统一格式的数据。

接下来给出一个通用的示例:

@ControllerAdvice
public class GlobalExceptionHandler {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
     * @param binder
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {}

    /**
     * 把值绑定到Model中,使再controller中可以通过 @RequestMapping可以获取到该值
     * @param model
     */
    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("author", "ifredom");
    }

    /**
     * 所有异常都返回统一的json数据
     * @param CustomException
     */
    @ResponseBody
    @ExceptionHandler(value = CustomException.class)
    public Map<String,Object> customErrorHandler(CustomException ex) {
        Map<String,Object> map = new HashMap<>(16);
        map.put("code", ex.getCode());
        map.put("msg", ex.getMessage());
        return map;
    }

    @ExceptionHandler(value = {MyCustomException1.class})
    public void onMyException1(){
        // 处理 MyCustomException1 这种异常
    }

    @ExceptionHandler(value = {MyCustomException2.class})
    public void onMyException2(){
       // 处理 MyCustomException2 这种异常
    }

    /**
     * 处理 MethodArgumentNotValidException, BindException 这两种异常
     * 解释:利用 jackson 对请求参数进行校验
     */
    @ExceptionHandler(value= {MethodArgumentNotValidException.class , BindException.class})
    public String onException(Exception e) throws JsonProcessingException {
        BindingResult bindingResult = null;
        if (e instanceof MethodArgumentNotValidException) {
            bindingResult = ((MethodArgumentNotValidException)e).getBindingResult();
        } else if (e instanceof BindException) {
            bindingResult = ((BindException)e).getBindingResult();
        }
        Map<String,String> errorMap = new HashMap<>(16);
        assert bindingResult != null;
        bindingResult.getFieldErrors().forEach((fieldError)->
                errorMap.put(fieldError.getField(),fieldError.getDefaultMessage())
        );
        return objectMapper.writeValueAsString(errorMap);
    }
}
// 自定义一种异常类型
public class CustomException extends RuntimeException{
    private static final long serialVersionUID = 1L;

    private Integer code;
    private String message;

    public CustomException(String message){
        this.message = message;
    }
    public CustomException(String message, Integer code){
        this.message = message;
        this.code = code;
    }
    public CustomException(String message, Throwable e){
        super(message, e);
        this.message = message;
    }
    public MyCustomException(String message, int code, Throwable e) {
        super(message, e);
        this.message = message;
        this.code = code;
    }
    @Override
    public String getMessage(){
        return message;
    }
    public Integer getCode(){
        return code;
    }
}

// 自定义一种异常类型
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
class MyCustomException1 extends RuntimeException{
    private String message;
    public MyCustomException() {}
    public MyCustomException(String message) {
        this.message = message;
    }
}
// 自定义一种异常类型
@ResponseStatus(value = HttpStatus.FORBIDDEN)
class MyCustomException2 extends RuntimeException{
    private String message;
    public MyCustomException() {}
    public MyCustomException(String message) {
        this.message = message;
    }
}

现在已经完成异常的定义,接下来启动应用访问 localhost:8080/user , 你会发现异常被拦截住了。

HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 19 Oct 2022 06:25:33 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "msg": "here",
  "code": 500
}
@RestController
public class UserController {
    @GetMapping("/user")
    public void registerUser( @ModelAttribute("author") String author){

        System.out.println(author);
        throw new MyCustomException("hehhe");
    }
}

有时。如果不需要返回 json 数据,而要渲染某个页面模板返回给浏览器。那么可以利用这个类 ModelAndView

@ExceptionHandler(value = MyException.class)
public ModelAndView myErrorHandler(MyException ex) {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("error");
    modelAndView.addObject("code", ex.getCode());
    modelAndView.addObject("msg", ex.getMsg());
    return modelAndView;
}

补充:如果全部异常处理返回 json,那么可以使用 @RestControllerAdvice 代替 @ControllerAdvice ,这样在方法上就可以不需要添加 @ResponseBody。

拓展阅读

------ 如果文章对你有用,感谢右上角 >>>点赞 | 收藏 <<<

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值