SpringBoot的全局異常處理


前言

本篇文章主要介紹的是SpringBoot的全局異常處理。

GitHub源碼鏈接位於文章底部。

首先還是來看工程的結構

在pom文件中添加相關依賴

 <properties>
	 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	 <java.version>1.8</java.version>
	 <maven.compiler.source>1.8</maven.compiler.source>
	 <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<!--父級依賴,它用來提供相關的 Maven 默認依賴。使用它之后,常用的springboot包依賴可以省去version 標簽-->
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.1.3.RELEASE</version>
	<relativePath ></relativePath>
</parent>
<dependencies>
	<!-- Spring Boot Web 依賴 核心 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<!--lombok插件依賴-->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.16.20</version>
	</dependency>
</dependencies>

編碼

Spring Boot的全局異常處理有兩個很重要的注解,一個是ControllerAdvice,一個是ExceptionHandler,我們在類上使用ControllerAdvice注解,表示開啟了全局異常的捕獲;在方法上使用ExceptionHandler注解去捕獲具體某一個異常,然后再該方法體內對捕獲到的異常進行處理,進行輸出、返回等操作。我們來看看下面這個示例是怎么操作的:

@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(value =NullPointerException.class)
    public String exceptionHandler(NullPointerException e){
        System.out.println("發生空指針異常:"+e);
        return e.getMessage();
    }
	@ExceptionHandler(value = ArithmeticException.class)
	public String exceptionHandler(ArithmeticException e) {
		System.out.println("發生數學運算異常:"+e);
		return e.getMessage();
	}
}

上面這個類中就全局捕獲了兩種具體的異常,我們可以不斷地往這個類中添加一些異常。在實際運用過程中,我們會對方法體中捕獲的異常進行二次處理,封裝成需要的格式,比如一些通用的錯誤碼,返回消息,返回標識等等,這里通過自定義的異常類以及枚舉類來實現。

自定義枚舉類

首先自定義一個枚舉類,該枚舉類中枚舉一些數據操作錯誤定義。如果有需要,后期可以不斷地增加對應枚舉情況。

public enum CommonEnum {
	// 數據操作錯誤定義
	SUCCESS(true,200, "請求成功!"),
	ERROR(false,201, "請求失敗!"),
	BODY_NOT_MATCH(false,400,"請求的參數錯誤!"),
	SIGNATURE_NOT_MATCH(false,401,"請求的數字簽名不匹配!"),
	NOT_FOUND(false,404, "未找到該資源!"),
	INTERNAL_SERVER_ERROR(false,500, "服務器內部錯誤!"),
	SERVER_BUSY(false,503,"服務器正忙,請稍后再試!");

	/** 錯誤碼 */
	private Integer resultCode;
	/** 錯誤描述 */
	private String resultMsg;
	private Boolean flag;
	CommonEnum(Boolean flag, Integer resultCode, String resultMsg) {
		this.resultCode = resultCode;
		this.resultMsg = resultMsg;
		this.flag = flag;
	}

	/** 返回結果集中用到get方法*/
	public Boolean getFlag() {
		return flag;
	}
	public Integer getResultCode() {
		return resultCode;
	}
	public String getResultMsg() {
		return resultMsg;
	}
}

自定義一個異常類,用於處理發生的業務異常

@Data
public class BizException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	/** 錯誤碼*/
	protected Integer errorCode;
	/** 錯誤信息*/
	protected String errorMsg;
	public BizException(Integer errorCode, String errorMsg) {
		super(errorCode.toString());
		this.errorCode = errorCode;
		this.errorMsg = errorMsg;
	}
	@Override
	public String getMessage() {
		return errorMsg;
	}
	@Override
	public Throwable fillInStackTrace() {
		return this;
	}
}

自定義數據格式

定義返回的結果集的格式,也就是改造Result類,和枚舉類結合起來

@Data
public class Result {
    /**
     * 返回結構狀態
     */
    private Boolean flag;

    /**
     * 返回狀態碼
     */
    private Integer code;

    /**
     * 返回消息
     */
    private String message;

    /**
     * 返回數據內容
     */
    private Object data;

    public Result(CommonEnum commonEnum, Object data) {
        this.flag = commonEnum.getFlag();
        this.code = commonEnum.getResultCode();
        this.message = commonEnum.getResultMsg();
        this.data = data;
    }

    public Result(CommonEnum commonEnum) {
        this.flag = commonEnum.getFlag();
        this.code = commonEnum.getResultCode();
        this.message = commonEnum.getResultMsg();
    }

    /**
     * 自定義異常
     */
    public Result(Integer code, String message) {
        this.flag = false;
        this.code = code;
        this.message = message;
    }
}

自定義全局異常處理類

最后來自定義全局異常處理類,可以處理自己拋出的自定義的業務異常,也可以處理系統異常,如空指針等,或者一些其他異常。其實就是第一個例子的延伸。

@ControllerAdvice
public class GlobalExceptionHandler {
	private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

	/**
	 * 處理自定義的業務異常
	 * @param e
	 */
	@ExceptionHandler(value = BizException.class)
	@ResponseBody
	public Result bizExceptionHandler(BizException e) {
		logger.error("發生業務異常!原因是:{}", e.getErrorMsg());
		return new Result(e.getErrorCode(), e.getErrorMsg());
	}

	/**
	 * 處理空指針的異常
	 * @param e
	 */
	@ExceptionHandler(value = NullPointerException.class)
	@ResponseBody
	public Result exceptionHandler(NullPointerException e) {
		logger.error("發生空指針異常!原因是:", e);
		return new Result(CommonEnum.BODY_NOT_MATCH,"空指針異常");
	}

	/**
	 * 格式轉換異常
	 * @param e
	 */
	@ExceptionHandler(value = ParseException.class)
	@ResponseBody
	public Result exceptionHandler(ParseException e) {
		logger.error("發生格式轉換異常!原因是:", e);
		return new Result(CommonEnum.BODY_NOT_MATCH,"格式轉換錯誤");
	}

	/**
	 * 方法安全權限異常
	 * @param e
	 */
	@ExceptionHandler(value = IllegalAccessException.class)
	@ResponseBody
	public Result exceptionHandler(IllegalAccessException e) {
		logger.error("發生方法安全權限異常!原因是反射了private方法:", e);
		return new Result(CommonEnum.INTERNAL_SERVER_ERROR,"方法安全權限異常!不能反射private方法");
	}

	/**
	 * 數學運算異常
	 * @param e
	 * @return
	 */
	@ExceptionHandler(value = ArithmeticException.class)
	@ResponseBody
	public Result exceptionHandler(ArithmeticException e) {
		logger.error("發生數學運算異常:", e);
		return new Result(CommonEnum.INTERNAL_SERVER_ERROR,"數學運算異常");
	}

	/**
	 * 處理其他異常
	 * @param e
	 */
	@ExceptionHandler(value = Exception.class)
	@ResponseBody
	public Result exceptionHandler(Exception e) {
		logger.error("未知異常!原因是:", e);
		return new Result(CommonEnum.INTERNAL_SERVER_ERROR,未知異常!);
	}
}

logger.error是輸出語句,為了在控制台看到結果,等同於System.out,等同於e.printStackTrace()。
可以看到,這里返回的是之前自定義的結果集,該結果集里定義了多個構造方法,比如new Result(枚舉,Obj),new Result(枚舉)以及其他,如果需要,可再自行增加。此外,其他遇到的異常也可以在這里按照格式增加。

創建User實體類

因為這里不連接數據庫,所以只定義簡單的屬性,pom文件中也沒有添加數據庫依賴。

@Data
public class User implements Serializable {
    /** id */
    private String id;
    /** 姓名 */
    private String name;
    /** 年齡 */
    private Integer age;
}

UserController控制層

控制層也使用不同的請求方式,不一樣的是,這里請求方式和具體邏輯沒有關系,只是為了方便操作api,同時在各個api中故意制造了一些異常,進行測試用,可以參考代碼注釋。

@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping
    public Result insert(@RequestBody User user) {
        if (user.getName() == null) {
            //拋出一個自定義異常
            throw new BizException(-1, "用戶名不能為空!!!");
        }
        //如果沒有異常,則返回數據為上傳的user
        return new Result(CommonEnum.SUCCESS,user);
    }

    @DeleteMapping
    public Result delete() {
        //制造一個空指針異常,並且不進行處理,會被全局捕獲
        String str=null;
        str.equals("test");
        //如果沒有異常,則返回請求成功
        return new Result(CommonEnum.SUCCESS);
    }

    @PutMapping
    public Result update() {
        //這里故意造成數字異常,並且不進行處理
        int i = 1 / 0;
        //如果沒有異常,則返回請求成功,返回數據為i
        return new Result(CommonEnum.SUCCESS,i);
    }

    @GetMapping
    public Result find() {
        /**
         * 這里故意造成索引越界異常,並且不進行處理,
         * 因為沒有進行處理,全局異常中也沒有進行具體異常的捕獲
         * 所以被最后的Exception捕獲了
         */
        List<String> list = new ArrayList<>();
        String str = list.get(0);
        //如果沒有異常,則返回請求成功,返回數據為str
        return new Result(CommonEnum.SUCCESS,str);
    }
}

程序入口

@SpringBootApplication
public class ExceptionHandlerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExceptionHandlerApplication.class, args);
    }
}

配置文件

在application.yml中設置訪問端口

server:
  port: 8080

測試

在postman中調用接口測試

捕獲自定義的業務異常(post方法)


這里沒有傳入name參數,所以捕獲了自己拋出的自定義異常
在controller中的post方法中throw new BizException(-1, "用戶名不能為空!!!");,可以看到輸出了我們封裝的返回結果。這里的錯誤碼和錯誤信息可以自己修改。


在傳入name參數后,沒有產生異常,自然也無從捕獲,此時返回的結果集正常為傳入的user對象,id和age沒有傳入,因此為null。

捕獲系統異常(delete、put方法)



因為沒有傳入參數,這里的body就不截出來了。

可以看到,這里的flag,code,message,data的值其實都是全局異常類中封裝的結果集,前三項是枚舉類中的項。如delete方法出現空指針異常,返回的是new Result(CommonEnum.BODY_NOT_MATCH,"空指針異常");這里的CommonEnum.BODY_NOT_MATCH定義了flag,code,message的值,data就是"空指針異常"。
可以在枚舉類中增加NullPointer(false,203,"空指針異常"),然后在全局異常中返回new Result(CommonEnum.NullPointer,"空指針異常"),返回的結果集就是

{
    "flag": false,
    "code": 203,
    "message": "空指針異常",
    "data": "空指針異常"
}

put方法同理。

捕獲未在全局異常類中定義的其他異常(get方法)


這里是捕獲了索引越界異常,但是因為全局異常類中沒有捕獲該具體異常,因此被最后一個Exception異常捕獲到了。
因為這里使用了logger.error進行輸出,因此可以在控制台中看到該具體異常,將其按照上面的格式加入到全局異常處理類中,下次再遇到這種異常就能順利捕獲了。

本文GitHub源碼:https://github.com/lixianguo5097/springboot/tree/master/springboot-exceptionHandler

CSDN:https://blog.csdn.net/qq_27682773
簡書:https://www.jianshu.com/u/e99381e6886e
博客園:https://www.cnblogs.com/lixianguo
個人博客:https://www.lxgblog.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM