一個成熟的Java項目如何優雅地處理異常

據說微信搜索《Java魚仔》會變動強哦!前端

本文收錄於JavaStarter ,裏面有我完整的Java系列文章,學習或面試均可以看看哦java

(一)概述

異常處理是一個系統最重要的環節,當一個項目變得很大的時候,異常處理和日誌系統能讓你快速定位到問題。對於用戶或者接口調用者而言,優雅的異常處理可讓調用者快速知道問題所在。本文將介紹如何優雅地處理異常。git

(二)使用通用的返回體

咱們但願全部的錯誤都以Json的方式返回給客戶,所以拿出上次寫的通用返回體,新建一個類CommonResult記錄返回體。github

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult {
    private int code;
    private String message;
    private Object data;
}

新建一個枚舉類ResponseCode集成code和message。面試

public enum ResponseCode {

    // 系統模塊
    SUCCESS(0, "操做成功"),
    ERROR(1, "操做失敗"),
    SERVER_ERROR(500, "服務器異常"),

    // 通用模塊 1xxxx
    ILLEGAL_ARGUMENT(10000, "參數不合法"),
    REPETITIVE_OPERATION(10001, "請勿重複操做"),
    ACCESS_LIMIT(10002, "請求太頻繁, 請稍後再試"),
    MAIL_SEND_SUCCESS(10003, "郵件發送成功"),

    // 用戶模塊 2xxxx
    NEED_LOGIN(20001, "登陸失效"),
    USERNAME_OR_PASSWORD_EMPTY(20002, "用戶名或密碼不能爲空"),
    USERNAME_OR_PASSWORD_WRONG(20003, "用戶名或密碼錯誤"),
    USER_NOT_EXISTS(20004, "用戶不存在"),
    WRONG_PASSWORD(20005, "密碼錯誤"),
    ;

    ResponseCode(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private Integer code;
    private String msg;
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}

(三)自定義運行時異常

自定義一個運行時異常類,構造方法傳入異常參數便可。後端

public class MyException extends RuntimeException{
    private String msg;

    public MyException(String msg) {
        super(msg);
    }
}

(四)編寫一個統一的異常處理類

異常處理類是整個異常處理核心,SpringBoot中提供了ControllerAdvice註解來攔截異常,使用RestControllerAdvice註解保證了返回Json格式。api

若是攔截到的異常屬於MyException,則按Json格式返回錯誤結果。服務器

@RestControllerAdvice
public class ExceptionController {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = Exception.class)
    public CommonResult exceptionHandler(Exception e){
        //若是拋出的異常屬於自定義異常,就以JSON格式返回
        if (e instanceof MyException){
            return new CommonResult(ResponseCode.ERROR.getCode(),ResponseCode.ERROR.getMsg(),"自定義的錯誤爲:"+e.getMessage());
        }
        //若是都不是就打印出異常的信息
        return new CommonResult(ResponseCode.ERROR.getCode(),ResponseCode.ERROR.getMsg(),"錯誤的信息爲:"+e.getMessage());
    }
}

(五)測試

爲了看初效果,這裏手動拋出一個異常來測試,新建IndexController,手動拋出異常微信

@RestController
public class IndexController {

    @RequestMapping(value = "/index",method = RequestMethod.GET)
    public String index(){
        throw new MyException("測試");
    }
}

查看調用結果:app

在這裏插入圖片描述

(六)對實體類的校驗

有這樣一個場景,登錄註冊時用戶名和密碼有長度限制,手機號有格式限制,若是不知足要求就沒法註冊。這個功能前端能夠限制,可是對於後端接口而言,也須要進行限制,萬一前端沒有限制住呢。

導入兩個校驗依賴包:

<!--校驗-->
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.0.Final</version>
</dependency>

編寫實體類,在每一個屬性上加上校驗包的驗證參數。

@Data
public class Register {

    @Length(max = 20,min = 4,message = "用戶名長度須要在4到20個字符之間")
    @NotBlank(message = "用戶名不能爲空")
    private String username;

    @NotBlank(message = "手機號不能爲空")
    @Pattern(regexp = "^1[3|4|5|8][0-9]\\d{8}$",message = "電話號碼格式不正確")
    private String phone;

    @Length(max = 20,min = 4,message = "密碼長度須要在4到20個字符之間")
    @NotBlank(message = "密碼不能爲空")
    private String password;
}

咱們在須要使用的方法中增長@Valid註解進行校驗,好比這個post請求中我要校驗。

@PostMapping("/register")
public CommonResult register(@Valid @RequestBody Register register){
    //一連串註冊的業務
    userService.registerUser(register);
    return new CommonResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),"");
}

@Valid在校驗失敗的狀況下會報出參數不合法的異常,仍是在統一的異常處理類中捕獲異常,若是是MethodArgumentNotValidException,就取出對應的message數據。

@RestControllerAdvice
public class ExceptionController {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = Exception.class)
    public CommonResult exceptionHandler(Exception e){
        //若是屬於參數校驗異常,就拋出校驗的錯誤
        if (e instanceof MethodArgumentNotValidException){
            MethodArgumentNotValidException methodArgumentNotValidException= (MethodArgumentNotValidException) e;
            return new CommonResult(ResponseCode.ERROR.getCode(),ResponseCode.ERROR.getMsg(),
                    "校驗錯誤:"+methodArgumentNotValidException.getBindingResult().getFieldError().getDefaultMessage());
        }//若是是自定義的異常,就給出具體的異常緣由
        else if (e instanceof MyException){
            return new CommonResult(ResponseCode.ERROR.getCode(),ResponseCode.ERROR.getMsg(),"自定義的錯誤爲:"+e.getMessage());
        }
        //若是都不是就打印出異常的信息
        return new CommonResult(ResponseCode.ERROR.getCode(),ResponseCode.ERROR.getMsg(),"錯誤的信息爲:"+e.getMessage());
    }
}

(七)測試校驗

接下來就能夠測試校驗的功能了,經過postman訪問

在這裏插入圖片描述

若是輸入參數不知足以前的設置,就會給出具體的錯誤信息。而不是拋出讓人沒法接收的報錯:

在這裏插入圖片描述

(八)總結

許多人寫代碼時最不考慮的就是異常處理,簡單地實現需求就行了,因此纔會致使許多不可預估的bug出現。好了,本期文章就到這裏了,咱們下期再見。

相關文章
相關標籤/搜索