在web應用中,請求處理時,出現異常是很是常見的。因此當應用出現各種異常時,進行異常的捕獲或者二次處理(好比sql異常正常是不能外拋)是很是必要的,好比在開發對外api服務時,約定了響應的參數格式,如
respCode
、respMsg
,調用方根據錯誤碼進行本身的業務邏輯。本章節就重點講解下統一異常和數據校驗處理。前端
springboot
中,默認在發送異常時,會跳轉值/error
請求進行錯誤的展示,根據不一樣的Content-Type
展示不一樣的錯誤結果,如json請求時,直接返回json格式參數。 瀏覽器訪問異常時:java
使用postman
訪問時:git
顯然,默認的異常頁是對用戶或者調用者而言都是不友好的,因此通常上咱們都會進行實現本身業務的異常提示信息。github
利用
@ControllerAdvice
和@ExceptionHandler
定義一個統一異常處理類web
@ControllerAdvice:控制器加強,使@ExceptionHandler、@InitBinder、@ModelAttribute註解的方法應用到全部的 @RequestMapping註解的方法。正則表達式
@ExceptionHandler:異常處理器,此註解的做用是當出現其定義的異常時進行處理的方法spring
建立異常類:CommonExceptionHandlersql
@ControllerAdvice public class CommonExceptionHandler { /** * 攔截Exception類的異常 * @param e * @return */ @ExceptionHandler(Exception.class) @ResponseBody public Map<String,Object> exceptionHandler(Exception e){ Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", "9999"); result.put("respMsg", e.getMessage()); //正常開發中,可建立一個統一響應實體,如CommonResp return result; } }
多餘不一樣異常(如自定義異常),須要進行不一樣的異常處理時,可編寫多個exceptionHandler方法,註解ExceptionHandler
指定處理的異常類,如編程
/** * 攔截 CommonException 的異常 * @param ex * @return */ @ExceptionHandler(CommonException.class) @ResponseBody public Map<String,Object> exceptionHandler(CommonException ex){ log.info("CommonException:{}({})",ex.getMsg(), ex.getCode()); Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", ex.getCode()); result.put("respMsg", ex.getMsg()); return result; }
因爲加入了@ResponseBody
,因此返回的是json
格式,json
說明異常已經被攔截了。 可攔截不一樣的異常,進行不一樣的異常提示,好比NoHandlerFoundException
、HttpMediaTypeNotSupportedException
、AsyncRequestTimeoutException
等等,這裏就不列舉了,讀者可本身加入後實際操做下。
對於返回頁面時,返回ModelAndView
便可,如
@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()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; }
因爲工做中都是纔有先後端分離開發模式,因此通常上都沒有直接返回資源頁的需求了,通常上都是返回固定的響應格式,如respCode
、respMsg
、data
,前端經過判斷respCode
的值進行業務判斷,是彈窗仍是跳轉頁面。
在web開發時,對於請求參數,通常上都須要進行參數合法性校驗的,原先的寫法時一個個字段一個個去判斷,這種方式太不通用了,因此java的
JSR 303: Bean Validation
規範就是解決這個問題的。
JSR 303
只是個規範,並無具體的實現,目前一般都是纔有hibernate-validator
進行統一參數校驗。
JSR303定義的校驗類型
Constraint | 詳細信息 |
---|---|
@Null |
被註釋的元素必須爲 null |
@NotNull |
被註釋的元素必須不爲 null |
@AssertTrue |
被註釋的元素必須爲 true |
@AssertFalse |
被註釋的元素必須爲 false |
@Min(value) |
被註釋的元素必須是一個數字,其值必須大於等於指定的最小值 |
@Max(value) |
被註釋的元素必須是一個數字,其值必須小於等於指定的最大值 |
@DecimalMin(value) |
被註釋的元素必須是一個數字,其值必須大於等於指定的最小值 |
@DecimalMax(value) |
被註釋的元素必須是一個數字,其值必須小於等於指定的最大值 |
@Size(max, min) |
被註釋的元素的大小必須在指定的範圍內 |
@Digits (integer, fraction) |
被註釋的元素必須是一個數字,其值必須在可接受的範圍內 |
@Past |
被註釋的元素必須是一個過去的日期 |
@Future |
被註釋的元素必須是一個未來的日期 |
@Pattern(value) |
被註釋的元素必須符合指定的正則表達式 |
Hibernate Validator 附加的 constraint
Constraint | 詳細信息 |
---|---|
@Email |
被註釋的元素必須是電子郵箱地址 |
@Length |
被註釋的字符串的大小必須在指定的範圍內 |
@NotEmpty |
被註釋的字符串的必須非空 |
@Range |
被註釋的元素必須在合適的範圍內 |
建立實體類
@Data @NoArgsConstructor @AllArgsConstructor public class DemoReq { @NotBlank(message="code不能爲空") String code; @Length(max=10,message="name長度不能超過10") String name; }
而後在控制層方法裏,加入@Valid
便可,這樣在訪問前,會對請求參數進行檢驗。
@GetMapping("/demo/valid") public String demoValid(@Valid DemoReq req) { return req.getCode() + "," + req.getName(); }
啓動,後訪問http://127.0.0.1:8080/demo/valid
加上正確參數後,http://127.0.0.1:8080/demo/valid?code=3&name=s
這樣數據的統一校驗就完成了,對於其餘註解的使用,你們可自行谷歌下,基本上都很簡單的,對於已有的註解沒法知足校驗須要時,也可進行自定義註解的開發,一下簡單講解下,自定義註解的編寫
不使用@valid的狀況下,也可利用編程的方式編寫一個工具類,進行實體參數校驗
public class ValidatorUtil { private static Validator validator = ((HibernateValidatorConfiguration) Validation .byProvider(HibernateValidator.class).configure()).failFast(true).buildValidatorFactory().getValidator(); /** * 實體校驗 * * @param obj * @throws CommonException */ public static <T> void validate(T obj) throws CommonException { Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj, new Class[0]); if (constraintViolations.size() > 0) { ConstraintViolation<T> validateInfo = (ConstraintViolation<T>) constraintViolations.iterator().next(); // validateInfo.getMessage() 校驗不經過時的信息,即message對應的值 throw new CommonException("0001", validateInfo.getMessage()); } } }
使用
@GetMapping("/demo/valid") public String demoValid(@Valid DemoReq req) { //手動校驗 ValidatorUtil.validate(req); return req.getCode() + "," + req.getName(); }
自定義註解,主要時實現
ConstraintValidator
的處理類便可,這裏已編寫一個校驗常量的註解爲例:參數值只能爲特定的值。
自定義註解
@Documented //指定註解的處理類 @Constraint(validatedBy = {ConstantValidatorHandler.class }) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) public @interface Constant { String message() default "{constraint.default.const.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String value(); }
註解處理類
public class ConstantValidatorHandler implements ConstraintValidator<Constant, String> { private String constant; @Override public void initialize(Constant constraintAnnotation) { //獲取設置的字段值 this.constant = constraintAnnotation.value(); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { //判斷參數是否等於設置的字段值,返回結果 return constant.equals(value); } }
使用
@Constant(message = "verson只能爲1.0",value="1.0") String version;
運行:
此時,自定義註解已生效。你們可根據實際需求進行開發。
你們看到在校驗不經過時,返回的異常信息是不友好的,此時可利用統一異常處理,對校驗異常進行特殊處理,特別說明下,對於異常處理類 共有如下幾種狀況(被@RequestBody和@RequestParam註解的請求實體,校驗異常類是不一樣的)
@ExceptionHandler(MethodArgumentNotValidException.class) public Map<String,Object> handleBindException(MethodArgumentNotValidException ex) { FieldError fieldError = ex.getBindingResult().getFieldError(); log.info("參數校驗異常:{}({})", fieldError.getDefaultMessage(),fieldError.getField()); Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", "01002"); result.put("respMsg", fieldError.getDefaultMessage()); return result; } @ExceptionHandler(BindException.class) public Map<String,Object> handleBindException(BindException ex) { //校驗 除了 requestbody 註解方式的參數校驗 對應的 bindingresult 爲 BeanPropertyBindingResult FieldError fieldError = ex.getBindingResult().getFieldError(); log.info("必填校驗異常:{}({})", fieldError.getDefaultMessage(),fieldError.getField()); Map<String,Object> result = new HashMap<String,Object>(); result.put("respCode", "01002"); result.put("respMsg", fieldError.getDefaultMessage()); return result; }
啓動後,提示就友好了
因此統一異常仍是頗有必要的。
本章節主要是闡述了統一異常處理和數據的合法性校驗,同時簡單實現了一個自定義的註解類,你們在遇見已有註解沒法解決時,可經過自定義的形式進行,固然對於通用而已,利用
@Pattern(正則表達式)
基本上都是能夠實現的。
目前互聯網上不少大佬都有
SpringBoot
系列教程,若有雷同,請多多包涵了。本文是做者在電腦前一字一句敲的,每一步都是實踐的。若文中有所錯誤之處,還望提出,謝謝。
499452441
lqdevOps
完整示例:chapter-8
原文地址:http://blog.lqdev.cn/2018/07/20/springboot/chapter-eight/