自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java如何優(yōu)雅地實(shí)現(xiàn)接口數(shù)據(jù)校驗(yàn)

開發(fā) 后端
本篇文章給大家分享平時(shí)開發(fā)中總結(jié)的一點(diǎn)小技巧!在工作中寫過Java程序的朋友都知道,目前使用Java開發(fā)服務(wù)最主流的方式就是通過Spring MVC定義一個(gè)Controller層接口,并將接口請求或返回參數(shù)分別定義在一個(gè)Java實(shí)體類中,這樣Spring MVC在接收到Http請求(POST/GET)后,就會(huì)自動(dòng)將請求報(bào)文自動(dòng)映射成一個(gè)Java對象。

[[356232]]

本文轉(zhuǎn)載自微信公眾號「無敵碼農(nóng)」,作者無敵碼農(nóng)。轉(zhuǎn)載本文請聯(lián)系無敵碼農(nóng)公眾號。

本篇文章給大家分享平時(shí)開發(fā)中總結(jié)的一點(diǎn)小技巧!在工作中寫過Java程序的朋友都知道,目前使用Java開發(fā)服務(wù)最主流的方式就是通過Spring MVC定義一個(gè)Controller層接口,并將接口請求或返回參數(shù)分別定義在一個(gè)Java實(shí)體類中,這樣Spring MVC在接收到Http請求(POST/GET)后,就會(huì)自動(dòng)將請求報(bào)文自動(dòng)映射成一個(gè)Java對象。這樣的代碼通常是這樣寫的:

  1. @RestController 
  2. public class OrderController { 
  3.  
  4.     @Autowired 
  5.     private OrderService orderServiceImpl; 
  6.  
  7.     @PostMapping("/createOrder"
  8.     public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) { 
  9.         return orderServiceImpl.createOrder(createOrderDTO); 
  10.     } 

這樣的代碼相信大家并不陌生,但在后續(xù)的邏輯實(shí)現(xiàn)過程中卻會(huì)遇到這樣的問題:“在接收請求參數(shù)后如何實(shí)現(xiàn)報(bào)文對象數(shù)據(jù)值的合法性校驗(yàn)?”。一些同學(xué)也可能認(rèn)為這并不是什么問題,因?yàn)榫唧w某個(gè)參數(shù)字段是否為空、值的取值是否在約定范圍、格式是否合法等等,在業(yè)務(wù)代碼中校驗(yàn)就好了。例如可以在Service實(shí)現(xiàn)類中對報(bào)文格式進(jìn)行各種if-else的數(shù)據(jù)校驗(yàn)。

從功能上說冗余的if-else代碼沒啥毛病,但從代碼的優(yōu)雅性來說冗長的if-else代碼會(huì)顯得非常臃腫。接下來的內(nèi)容將給大家介紹一種處理此類問題的實(shí)用方法。具體將從以下幾個(gè)方面進(jìn)行介紹:

  • 使用@Validated注解實(shí)現(xiàn)Controller接口層數(shù)據(jù)直接綁定校驗(yàn);
  • 擴(kuò)展約束性注解實(shí)現(xiàn)數(shù)據(jù)取值范圍的校驗(yàn);
  • 更加靈活的對象數(shù)據(jù)合法性校驗(yàn)工具類封裝;
  • 數(shù)據(jù)合法性校驗(yàn)結(jié)果異常統(tǒng)一返回處理;

Controller接口層數(shù)據(jù)綁定校驗(yàn)

實(shí)際上在Java開發(fā)中目前普通使用的Bean數(shù)據(jù)校驗(yàn)工具是"hibernate-validator",它是一個(gè)hibernete獨(dú)立的jar包,所以使用這個(gè)jar包并不需要一定要集成Hibernete框架。該jar包主要實(shí)現(xiàn)并擴(kuò)展了javax.validation(是一個(gè)基于JSR-303標(biāo)準(zhǔn)開發(fā)出來的Bean校驗(yàn)規(guī)范)接口。

由于Spring Boot在內(nèi)部默認(rèn)集成了"hibernate-validator",所以使用Spring Boot構(gòu)建的Java工程可以直接使用相關(guān)注解來實(shí)現(xiàn)Bean的數(shù)據(jù)校驗(yàn)。例如我們最常編寫的Controller層接口參數(shù)對象,可以在定義Bean類時(shí)直接編寫這樣的代碼:

  1. @Data 
  2. public class CreateOrderDTO { 
  3.  
  4.     @NotNull(message = "訂單號不能為空"
  5.     private String orderId; 
  6.     @NotNull(message = "訂單金額不能為空"
  7.     @Min(value = 1, message = "訂單金額不能小于0"
  8.     private Integer amount; 
  9.     @Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "用戶手機(jī)號不合法"
  10.     private String mobileNo; 
  11.     private String orderType; 
  12.     private String status; 

如上所示代碼,我們可以使用@NotNull注解來約束該字段必須不能為空,也可以使用@Min注解來約束字段的最小取值,或者還可以通過@Pattern注解來使用正則表達(dá)式來約束字段的格式(如手機(jī)號格式)等等。

以上這些注解都是“hibernate-validator”依賴包默認(rèn)提供的,更多常用的注解還有很多,例如:

利用這些約束注解,我們就可以很輕松的搞定接口數(shù)據(jù)校驗(yàn),而不需要在業(yè)務(wù)邏輯中編寫大量的if-else來進(jìn)行數(shù)據(jù)合法性校驗(yàn)。而定義好Bean參數(shù)對象并使用相關(guān)注解實(shí)現(xiàn)參數(shù)值約束后,在Controller層接口定義中只需要使用@Validated注解就可以實(shí)現(xiàn)在接收參數(shù)后自動(dòng)進(jìn)行數(shù)據(jù)綁定校驗(yàn)了,具體代碼如下:

  1. @PostMapping("/createOrder"
  2. public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) { 
  3.     return orderServiceImpl.createOrder(createOrderDTO); 

如上所示,在Controller層中通過Spring提供的@Validated注解可以自動(dòng)實(shí)現(xiàn)數(shù)據(jù)Bean的綁定校驗(yàn),如果數(shù)據(jù)異常則會(huì)統(tǒng)一拋出校驗(yàn)異常!

約束性注解擴(kuò)展

在“hibernate-validator”依賴jar包中,雖然提供了很多很方便的約束注解,但是也有不滿足某些實(shí)際需要的情況,例如我們想針對參數(shù)中的某個(gè)值約定其值的枚舉范圍,如orderType訂單類型只允許傳“pay”、“refund”兩種值,那么現(xiàn)有的約束注解可能就沒有特別適用的了。此外,如果對這樣的枚舉值,我們還想在約束定義中直接匹配代碼中的枚舉定義,以更好地統(tǒng)一接口參數(shù)與業(yè)務(wù)邏輯的枚舉定義。那么這種情況下,我們還可以自己擴(kuò)展定義相應(yīng)地約束注解邏輯。

接下來我們定義新的約束注解@EnumValue,來實(shí)現(xiàn)上面我們所說的效果,具體代碼如下:

  1. @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) 
  2. @Retention(RUNTIME) 
  3. @Documented 
  4. @Constraint(validatedBy = {EnumValueValidator.class}) 
  5. public @interface EnumValue { 
  6.  
  7.     //默認(rèn)錯(cuò)誤消息 
  8.     String message() default "必須為指定值"
  9.  
  10.     //支持string數(shù)組驗(yàn)證 
  11.     String[] strValues() default {}; 
  12.  
  13.     //支持int數(shù)組驗(yàn)證 
  14.     int[] intValues() default {}; 
  15.  
  16.     //支持枚舉列表驗(yàn)證 
  17.     Class<?>[] enumValues() default {}; 
  18.  
  19.     //分組 
  20.     Class<?>[] groups() default {}; 
  21.  
  22.     //負(fù)載 
  23.     Class<? extends Payload>[] payload() default {}; 
  24.  
  25.     //指定多個(gè)時(shí)使用 
  26.     @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) 
  27.     @Retention(RUNTIME) 
  28.     @Documented 
  29.     @interface List { 
  30.         EnumValue[] value(); 
  31.     } 
  32.  
  33.     /** 
  34.      * 校驗(yàn)類邏輯定義 
  35.      */ 
  36.     class EnumValueValidator implements ConstraintValidator<EnumValue, Object> { 
  37.  
  38.         //字符串類型數(shù)組 
  39.         private String[] strValues; 
  40.         //int類型數(shù)組 
  41.         private int[] intValues; 
  42.         //枚舉類 
  43.         private Class<?>[] enumValues; 
  44.  
  45.         /** 
  46.          * 初始化方法 
  47.          * 
  48.          * @param constraintAnnotation 
  49.          */ 
  50.         @Override 
  51.         public void initialize(EnumValue constraintAnnotation) { 
  52.             strValues = constraintAnnotation.strValues(); 
  53.             intValues = constraintAnnotation.intValues(); 
  54.             enumValues = constraintAnnotation.enumValues(); 
  55.         } 
  56.  
  57.         /** 
  58.          * 校驗(yàn)方法 
  59.          * 
  60.          * @param value 
  61.          * @param context 
  62.          * @return 
  63.          */ 
  64.         @SneakyThrows 
  65.         @Override 
  66.         public boolean isValid(Object value, ConstraintValidatorContext context) { 
  67.             //針對字符串?dāng)?shù)組的校驗(yàn)匹配 
  68.             if (strValues != null && strValues.length > 0) { 
  69.                 if (value instanceof String) { 
  70.                     for (String s : strValues) {//判斷值類型是否為Integer類型 
  71.                         if (s.equals(value)) { 
  72.                             return true
  73.                         } 
  74.                     } 
  75.                 } 
  76.             } 
  77.             //針對整型數(shù)組的校驗(yàn)匹配 
  78.             if (intValues != null && intValues.length > 0) { 
  79.                 if (value instanceof Integer) {//判斷值類型是否為Integer類型 
  80.                     for (Integer s : intValues) { 
  81.                         if (s == value) { 
  82.                             return true
  83.                         } 
  84.                     } 
  85.                 } 
  86.             } 
  87.             //針對枚舉類型的校驗(yàn)匹配 
  88.             if (enumValues != null && enumValues.length > 0) { 
  89.                 for (Class<?> cl : enumValues) { 
  90.                     if (cl.isEnum()) { 
  91.                         //枚舉類驗(yàn)證 
  92.                         Object[] objs = cl.getEnumConstants(); 
  93.                         //這里需要注意,定義枚舉時(shí),枚舉值名稱統(tǒng)一用value表示 
  94.                         Method method = cl.getMethod("getValue"); 
  95.                         for (Object obj : objs) { 
  96.                             Object code = method.invoke(obj, null); 
  97.                             if (value.equals(code.toString())) { 
  98.                                 return true
  99.                             } 
  100.                         } 
  101.                     } 
  102.                 } 
  103.             } 
  104.             return false
  105.         } 
  106.     } 

如上所示的@EnumValue約束注解,是一個(gè)非常實(shí)用的擴(kuò)展,通過該注解我們可以實(shí)現(xiàn)對參數(shù)取值范圍(不是大小范圍)的約束,它支持對int、string以及enum三種數(shù)據(jù)類型的約束,具體使用方式如下:

  1. /** 
  2.  * 定制化注解,支持參數(shù)值與指定類型數(shù)組列表值進(jìn)行匹配(缺點(diǎn)是需要將枚舉值寫死在字段定義的注解中) 
  3.  */ 
  4. @EnumValue(strValues = {"pay""refund"}, message = "訂單類型錯(cuò)誤"
  5. private String orderType; 
  6. /** 
  7.  * 定制化注解,實(shí)現(xiàn)參數(shù)值與枚舉列表的自動(dòng)匹配校驗(yàn)(能更好地與實(shí)際業(yè)務(wù)開發(fā)匹配) 
  8.  */ 
  9. @EnumValue(enumValues = Status.class, message = "狀態(tài)值不在指定范圍"
  10. private String status; 

如上所示代碼,該擴(kuò)展注解既可以使用strValues或intValues屬性來編程列舉取值范圍,也可以直接通過enumValues來綁定枚舉定義。但是需要注意,處于通用考慮,具體枚舉定義的屬性的名稱要統(tǒng)一匹配為value、desc,例如Status枚舉定義如下:

  1. public enum Status { 
  2.     PROCESSING(1, "處理中"), 
  3.     SUCCESS(2, "訂單已完成"); 
  4.     Integer value; 
  5.     String desc
  6.  
  7.     Status(Integer value, String desc) { 
  8.         this.value = value; 
  9.         this.desc = desc
  10.     } 
  11.  
  12.     public Integer getValue() { 
  13.         return value; 
  14.     } 
  15.  
  16.     public String getDesc() { 
  17.         return desc
  18.     } 

通過注解擴(kuò)展,就能實(shí)現(xiàn)更多方便的約束性注解!

更加靈活的數(shù)據(jù)校驗(yàn)工具類封裝

除了上面直接在Controller層使用@Validated進(jìn)行綁定數(shù)據(jù)校驗(yàn)外,在有些情況,例如你的參數(shù)對象中的某個(gè)字段是一個(gè)復(fù)合對象,或者業(yè)務(wù)層的某個(gè)方法所定義的入?yún)ο笠残枰M(jìn)行數(shù)據(jù)合法性校驗(yàn),那么這種情況下如何實(shí)現(xiàn)像Controller層一樣的校驗(yàn)效果呢?

需要說明在這種情況下@Validated已經(jīng)無法直接使用了,因?yàn)锧Validated注解發(fā)揮作用主要是Spring MVC在接收參數(shù)的過程中實(shí)現(xiàn)了自動(dòng)數(shù)據(jù)綁定校驗(yàn),而在普通的業(yè)務(wù)方法或者復(fù)合參數(shù)對象中是沒有辦法直接綁定校驗(yàn)的。這種情況下,我們可以通過定義ValidateUtils工具類來實(shí)現(xiàn)一樣的校驗(yàn)效果,具體代碼如下:

  1. public class ValidatorUtils { 
  2.  
  3.     private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); 
  4.  
  5.     /** 
  6.      * bean整體校驗(yàn),有不合規(guī)范,拋出第1個(gè)違規(guī)異常 
  7.      */ 
  8.     public static void validate(Object obj, Class<?>... groups) { 
  9.         Set<ConstraintViolation<Object>> resultSet = validator.validate(obj, groups); 
  10.         if (resultSet.size() > 0) { 
  11.             //如果存在錯(cuò)誤結(jié)果,則將其解析并進(jìn)行拼湊后異常拋出 
  12.             List<String> errorMessageList = resultSet.stream().map(o -> o.getMessage()).collect(Collectors.toList()); 
  13.             StringBuilder errorMessage = new StringBuilder(); 
  14.             errorMessageList.stream().forEach(o -> errorMessage.append(o + ";")); 
  15.             throw new IllegalArgumentException(errorMessage.toString()); 
  16.         } 
  17.     } 

如上所示,我們定義了一個(gè)基于"javax.validation"接口的工具類實(shí)現(xiàn),這樣就可以在非@Validated直接綁定校驗(yàn)的場景中通過校驗(yàn)工具類來實(shí)現(xiàn)對Bean對象約束注解的校驗(yàn)處理,具體使用代碼如下:

  1. public boolean orderCheck(OrderCheckBO orderCheckBO) { 
  2.     //對參數(shù)對象進(jìn)行數(shù)據(jù)校驗(yàn) 
  3.     ValidatorUtils.validate(orderCheckBO); 
  4.     return true

而方法入?yún)ο髣t還是可以繼續(xù)使用前面我們介紹的約束性注解進(jìn)行約定,例如上述方法的入?yún)ο蠖x如下:

  1. @Data 
  2. @Builder 
  3. public class OrderCheckBO { 
  4.  
  5.     @NotNull(message = "訂單號不能為空"
  6.     private String orderId; 
  7.     @Min(value = 1, message = "訂單金額不能小于0"
  8.     private Integer orderAmount; 
  9.     @NotNull(message = "創(chuàng)建人不能為空"
  10.     private String operator; 
  11.     @NotNull(message = "操作時(shí)間不能為空"
  12.     private String operatorTime; 

這樣在編程體驗(yàn)上就可以整體上保持一致!

數(shù)據(jù)合法性校驗(yàn)結(jié)果異常統(tǒng)一處理

通過前面我們所講的各種約束注解,我們實(shí)現(xiàn)了對Controller層接口以及業(yè)務(wù)方法參數(shù)對象的統(tǒng)一數(shù)據(jù)校驗(yàn)。而為了保持校驗(yàn)異常處理的統(tǒng)一處理和錯(cuò)誤報(bào)文統(tǒng)一輸出,我們還可以定義通用的異常處理機(jī)制,來保證各類數(shù)據(jù)校驗(yàn)錯(cuò)誤都能以統(tǒng)一錯(cuò)誤格式反饋給調(diào)用方。具體代碼如下:

  1. @Slf4j 
  2. @ControllerAdvice 
  3. public class GlobalExceptionHandler { 
  4.     /** 
  5.      * 統(tǒng)一處理參數(shù)校驗(yàn)錯(cuò)誤異常(非Spring接口數(shù)據(jù)綁定驗(yàn)證) 
  6.      * 
  7.      * @param response 
  8.      * @param e 
  9.      * @return 
  10.      */ 
  11.     @ExceptionHandler(BindException.class) 
  12.     @ResponseBody 
  13.     public ResponseResult<?> processValidException(HttpServletResponse response, BindException e) { 
  14.         response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); 
  15.         //獲取校驗(yàn)錯(cuò)誤結(jié)果信息,并將信息組裝 
  16.         List<String> errorStringList = e.getBindingResult().getAllErrors() 
  17.                 .stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList()); 
  18.         String errorMessage = String.join("; ", errorStringList); 
  19.         response.setContentType("application/json;charset=UTF-8"); 
  20.         log.error(e.toString() + "_" + e.getMessage(), e); 
  21.         return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(), 
  22.                 errorMessage); 
  23.     } 
  24.  
  25.     /** 
  26.      * 統(tǒng)一處理參數(shù)校驗(yàn)錯(cuò)誤異常 
  27.      * 
  28.      * @param response 
  29.      * @param e 
  30.      * @return 
  31.      */ 
  32.     @ExceptionHandler(IllegalArgumentException.class) 
  33.     @ResponseBody 
  34.     public ResponseResult<?> processValidException(HttpServletResponse response, IllegalArgumentException e) { 
  35.         response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); 
  36.         String errorMessage = String.join("; ", e.getMessage()); 
  37.         response.setContentType("application/json;charset=UTF-8"); 
  38.         log.error(e.toString() + "_" + e.getMessage(), e); 
  39.         return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(), 
  40.                 errorMessage); 
  41.     } 
  42.  
  43.     ... 

如上所示,我們定義了針對前面兩種數(shù)據(jù)校驗(yàn)方式的統(tǒng)一異常處理機(jī)制,這樣數(shù)據(jù)校驗(yàn)的錯(cuò)誤信息就能通過統(tǒng)一的報(bào)文格式反饋給調(diào)用端,從而實(shí)現(xiàn)接口數(shù)據(jù)報(bào)文的統(tǒng)一返回!

其中通用的接口參數(shù)對象ResponseResult的代碼定義如下:

  1. @Data 
  2. @Builder 
  3. @NoArgsConstructor 
  4. @AllArgsConstructor 
  5. @JsonPropertyOrder({"code""message""data"}) 
  6. public class ResponseResult<T> implements Serializable { 
  7.  
  8.     private static final long serialVersionUID = 1L; 
  9.  
  10.     /** 
  11.      * 返回的對象 
  12.      */ 
  13.     @JsonInclude(JsonInclude.Include.NON_NULL) 
  14.     private T data; 
  15.     /** 
  16.      * 返回的編碼 
  17.      */ 
  18.     private Integer code; 
  19.     /** 
  20.      * 返回的信息 
  21.      */ 
  22.     private String message; 
  23.  
  24.     /** 
  25.      * @param data 返回的數(shù)據(jù) 
  26.      * @param <T>  返回的數(shù)據(jù)類型 
  27.      * @return 響應(yīng)結(jié)果 
  28.      */ 
  29.     public static <T> ResponseResult<T> OK(T data) { 
  30.         return packageObject(data, GlobalCodeEnum.GL_SUCC_0); 
  31.     } 
  32.  
  33.     /** 
  34.      * 自定義系統(tǒng)異常信息 
  35.      * 
  36.      * @param code 
  37.      * @param message 自定義消息 
  38.      * @param <T> 
  39.      * @return 
  40.      */ 
  41.     public static <T> ResponseResult<T> systemException(Integer code, String message) { 
  42.         return packageObject(null, code, message); 
  43.     } 

當(dāng)然,這樣的統(tǒng)一報(bào)文格式也不僅僅只處理異常返回,正常的數(shù)據(jù)報(bào)文格式也可以通過該對象來進(jìn)行統(tǒng)一封裝!

本文內(nèi)容從實(shí)用的角度給大家演示了,如何在日常工作中編寫通用的數(shù)據(jù)校驗(yàn)邏輯.

原文鏈接:https://mp.weixin.qq.com/s/9kKIDZYB7bR7jiC5vj6qMg

 

責(zé)任編輯:武曉燕 來源: 無敵碼農(nóng)
相關(guān)推薦

2023-03-28 08:07:12

2021-05-12 22:07:43

并發(fā)編排任務(wù)

2023-06-06 08:51:06

2025-01-16 08:08:29

2020-07-07 07:33:12

Java單元集成

2021-03-24 10:20:50

Fonts前端代碼

2020-11-05 18:30:32

接口測試

2020-09-25 11:30:20

Java判空代碼

2024-11-13 16:37:00

Java線程池

2017-10-20 12:59:05

數(shù)據(jù)分層數(shù)據(jù)建設(shè)數(shù)據(jù)倉庫

2024-06-05 09:17:31

Python數(shù)據(jù)清洗開發(fā)

2022-08-03 07:07:10

Spring數(shù)據(jù)封裝框架

2020-04-10 10:22:12

Java判空編程語言

2020-02-05 14:05:21

Java技術(shù)數(shù)組

2020-03-26 11:04:00

Linux命令光標(biāo)

2021-01-18 13:17:04

鴻蒙HarmonyOSAPP

2022-05-13 21:20:23

組件庫樣式選擇器

2023-05-12 14:14:00

Java線程中斷

2021-01-28 14:53:19

PHP編碼開發(fā)

2022-05-24 06:07:48

JShack用戶代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號