spring boot 異常(exception)處理



Spring Boot 集成教程


概述

異常處理注解

spring中處理異常可以通過2個注解:

  • @ControllerAdvice 全局,處理所有控制器中的異常
  • @ExceptionHandler 局部,只針對某個控制器中的異常

先有ExceptionHandler,再有ControllerAdviceExceptionHandler不能集中處理異常,ControllerAdvice為彌補此缺點引入,推薦使用ControllerAdvice。本文介紹ControllerAdvice的用法,對ExceptionHandler不作介紹,如需了解可參考相關資料。

錯誤處理頁面:ErrorController

ErrorController的作用是為servlet設置錯誤頁,默認錯誤頁是Whitelabel,訪問不存在的頁面就會顯示此錯誤頁。

image

通過繼承ErrorController接口可以設置自定義的錯誤頁。

@RestController
public class MyErrorController implements ErrorController {
	
	@RequestMapping(value = "/error")
	public ResponseEntity<Result> error() {
        Result res = new Result(404, "頁面未找到");
        return new ResponseEntity<Result>(res, HttpStatus.NOT_FOUND);
    }

	@Override
	public String getErrorPath() {
		return "/error";
	}
}

如果ControllerAdvice中沒有直接返回http響應,繼續拋出異常,將會調用ErrorController顯示錯誤頁。如果在ControllerAdvice中捕獲異常並直接返回http響應,就沒必要配置ErrorController中的錯誤頁了。

404 異常

spring boot默認不會拋出404異常(NoHandlerFoundException),所以在ControllerAdvice中捕獲不到該異常,導致404總是跳過ContollerAdvice,直接顯示ErrorController的錯誤頁。需要改變配置,讓404錯誤拋出異常(NoHandlerFoundException),這樣便可在ControllerAdvice中捕獲此異常。改變配置,在application.properties中添加:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

過濾器異常

ContollerAdvice不能捕獲過濾器拋出的異常,對於此類異常需要特別處理。

如 [spring boot rest 接口集成 spring security(2) – JWT配置] 章節中的jwt過濾器,異常處理需要設置特別的處理類。

使用ContollerAdvice可以實現對所有控制器異常的集中處理,下面通過一個實際項目介紹此過程。

項目內容

模擬一個用戶注冊的接口,拋出各類異常讓ContollerAdvice處理。

要求

  • JDK1.8或更新版本
  • Eclipse開發環境

如沒有開發環境,可參考前面章節 [spring boot 開發環境搭建(Eclipse)]。

項目創建

創建spring boot項目

打開Eclipse,創建spring boot的spring starter project項目,選擇菜單:File > New > Project ...,彈出對話框,選擇:Spring Boot > Spring Starter Project,在配置依賴時,勾選web,完成項目創建。

image

項目配置

application.properties配置

## 服務器端口,默認是8080
server.port=8096 

## 讓404錯誤拋出異常,需要同時設置spring.resources.add-mappings為false
#  讓404錯誤拋出異常
spring.mvc.throw-exception-if-no-handler-found=true
# 禁用靜態資源的自動映射,如不禁用,不存在的url將被映射到/**,servlet不有機會拋出異常
spring.resources.add-mappings=false

## log級別設置為debug, 通過log.debug打印異常信息
logging.level.root=DEBUG

如果使用靜態資源的自動映射,不存在的url將被映射到/**,servlet不有機會拋出異常。在rest api的項目中沒有靜態資源的處理,完全可以禁止。

代碼實現

項目目錄結構如下圖,我們添加了幾個類,下面將詳細介紹。

image

異常處理類 ErrorHandler.java

這個類就是加ControllerAdvice注解的異常處理類,所有控制器的異常,都在這里集中處理。這里我們實現了2類特殊異常的處理函數,以及1個缺省的異常處理函數。

  • 輸入參數校驗異常處理
  • 404異常處理
  • 缺省的異常處理函數,處理所有其他異常

@ControllerAdvice //表明這是控制器的異常處理類
public class ErrorHandler {
	
	private static final org.slf4j.Logger log = LoggerFactory.getLogger(ErrorHandler.class);
	
	/**
	 * 輸入參數校驗異常
	 */
	@ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity<Result> NotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException e) throws Exception {
		
		log.debug("異常詳情", e);
		BindingResult bindingResult = e.getBindingResult();
		
		//rfc4918 - 11.2. 422: Unprocessable Entity			 
        Result res = MiscUtil.getValidateError(bindingResult);
        return new ResponseEntity<Result>(res, HttpStatus.UNPROCESSABLE_ENTITY);
    }
	
	/**
	 * 404異常處理
	 */
	@ExceptionHandler(value = NoHandlerFoundException.class)
    public ResponseEntity<Result> NoHandlerFoundExceptionHandler(HttpServletRequest req, Exception e) throws Exception {
		
		log.debug("異常詳情", e);
				
		Result res = new Result(404, "頁面不存在");
        return new ResponseEntity<Result>(res, HttpStatus.NOT_FOUND);
    }
	
	/**
	 *  默認異常處理,前面未處理
	 */
	@ExceptionHandler(value = Throwable.class)
    public ResponseEntity<Result> defaultHandler(HttpServletRequest req, Exception e) throws Exception {
		        
        Result res = new Result(500, "服務器內部錯誤");
        log.debug("異常詳情", e);
        
        return new ResponseEntity<Result>(res, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

輸入參數校驗異常處理,在 [spring boot輸入數據校驗(validation)] 章節里,是直接在控制器返回bindingResult,現在放在這里統一處理,好處是無需再在每個接口里重復寫返回bindingResult的代碼。

控制器 AuthController

AuthController實現了一個模擬用戶注冊的接口,數據校驗返回bindingResult的代碼被去除,spring boot將直接拋出MethodArgumentNotValidException,然后由ErrorHandler捕獲處理。

@RestController
@RequestMapping("/auth")
public class AuthController {
	
    @RequestMapping(value = "/register", method = RequestMethod.POST, produces="application/json")
    public ResponseEntity<Result> register(
    	@Valid @RequestBody RegisterRequest register
//    	, BindingResult bindingResult
    ) throws Exception {
    	
//		if(bindingResult.hasErrors()) {	
//			
//			Result res1 = MiscUtil.getValidateError(bindingResult);
//			return new ResponseEntity<Result>(res1, HttpStatus.UNPROCESSABLE_ENTITY);
//		}
    	
		Result res = new Result(200, "ok");
        return ResponseEntity.ok(res);
    }
}

RegisterRequest (DTO/數據傳輸對象)

RegisterRequest類接受並校驗用戶注冊時輸入的信息。關於數據校驗可參考教程 [spring boot輸入數據校驗(validation)]。


public class RegisterRequest {
	
	@SuppressWarnings("unused")
	private static final org.slf4j.Logger log = LoggerFactory.getLogger(RegisterRequest.class);
	
	@NotNull(message="手機號必須填")
	@Pattern(regexp = "^[1]([3][0-9]{1}|59|58|88|89)[0-9]{8}$", message="賬號請輸入11位手機號") // 手機號
	private String mobile;
	
	@NotNull(message="昵稱必須填")
	@Size(min=1, max=20, message="昵稱1~20個字")
	private String nickname;
	
    @NotNull(message="密碼必須填")
    @Size(min=6, max=16, message="密碼6~16位")
	private String password;

	public String getMobile() {
		return mobile;
	}

	public void setMobile(String mobile) {
		this.mobile = mobile;
	}

	public String getNickname() {
		return nickname;
	}

	public void setNickname(String nickname) {
		this.nickname = nickname;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
    	
}

輔助類

  • Result 結果封裝類
  • MiscUtil 雜項功能

運行

Eclipse左側,在項目根目錄上點擊鼠標右鍵彈出菜單,選擇:run as -> spring boot app 運行程序。 打開Postman訪問接口,運行結果如下:

用戶注冊,輸入錯誤的信息

image

訪問不存在的網址

image

總結

完整代碼


免責聲明!

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



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