- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- Java中怎么實(shí)現接口數據校驗
今天就跟大家聊聊有關(guān)Java中怎么實(shí)現接口數據校驗,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
Controller接口層數據綁定校驗
實(shí)際上在Java開(kāi)發(fā)中目前普通使用的Bean數據校驗工具是"hibernate-validator",它是一個(gè)hibernete獨立的jar包,所以使用這個(gè)jar包并不需要一定要集成Hibernete框架。該jar包主要實(shí)現并擴展了javax.validation(是一個(gè)基于JSR-303標準開(kāi)發(fā)出來(lái)的Bean校驗規范)接口。
由于Spring Boot在內部默認集成了"hibernate-validator",所以使用Spring Boot構建的Java工程可以直接使用相關(guān)注解來(lái)實(shí)現Bean的數據校驗。例如我們最常編寫(xiě)的Controller層接口參數對象,可以在定義Bean類(lèi)時(shí)直接編寫(xiě)這樣的代碼:
@Data public class CreateOrderDTO { @NotNull(message = "訂單號不能為空") private String orderId; @NotNull(message = "訂單金額不能為空") @Min(value = 1, message = "訂單金額不能小于0") private Integer amount; @Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "用戶(hù)手機號不合法") private String mobileNo; private String orderType; private String status; }
如上所示代碼,我們可以使用@NotNull注解來(lái)約束該字段必須不能為空,也可以使用@Min注解來(lái)約束字段的最小取值,或者還可以通過(guò)@Pattern注解來(lái)使用正則表達式來(lái)約束字段的格式(如手機號格式)等等。
以上這些注解都是“hibernate-validator”依賴(lài)包默認提供的,更多常用的注解還有很多,例如:
利用這些約束注解,我們就可以很輕松的搞定接口數據校驗,而不需要在業(yè)務(wù)邏輯中編寫(xiě)大量的if-else來(lái)進(jìn)行數據合法性校驗。而定義好Bean參數對象并使用相關(guān)注解實(shí)現參數值約束后,在Controller層接口定義中只需要使用@Validated注解就可以實(shí)現在接收參數后自動(dòng)進(jìn)行數據綁定校驗了,具體代碼如下:
@PostMapping("/createOrder") public CreateOrderBO validationTest(@Validated CreateOrderDTO createOrderDTO) { return orderServiceImpl.createOrder(createOrderDTO); }
如上所示,在Controller層中通過(guò)Spring提供的@Validated注解可以自動(dòng)實(shí)現數據Bean的綁定校驗,如果數據異常則會(huì )統一拋出校驗異常!
約束性注解擴展
在“hibernate-validator”依賴(lài)jar包中,雖然提供了很多很方便的約束注解,但是也有不滿(mǎn)足某些實(shí)際需要的情況,例如我們想針對參數中的某個(gè)值約定其值的枚舉范圍,如orderType訂單類(lèi)型只允許傳“pay”、“refund”兩種值,那么現有的約束注解可能就沒(méi)有特別適用的了。此外,如果對這樣的枚舉值,我們還想在約束定義中直接匹配代碼中的枚舉定義,以更好地統一接口參數與業(yè)務(wù)邏輯的枚舉定義。那么這種情況下,我們還可以自己擴展定義相應地約束注解邏輯。
接下來(lái)我們定義新的約束注解@EnumValue,來(lái)實(shí)現上面我們所說(shuō)的效果,具體代碼如下:
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = {EnumValueValidator.class}) public @interface EnumValue { //默認錯誤消息 String message() default "必須為指定值"; //支持string數組驗證 String[] strValues() default {}; //支持int數組驗證 int[] intValues() default {}; //支持枚舉列表驗證 Class<?>[] enumValues() default {}; //分組 Class<?>[] groups() default {}; //負載 Class<? extends Payload>[] payload() default {}; //指定多個(gè)時(shí)使用 @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { EnumValue[] value(); } /** * 校驗類(lèi)邏輯定義 */ class EnumValueValidator implements ConstraintValidator<EnumValue, Object> { //字符串類(lèi)型數組 private String[] strValues; //int類(lèi)型數組 private int[] intValues; //枚舉類(lèi) private Class<?>[] enumValues; /** * 初始化方法 * * @param constraintAnnotation */ @Override public void initialize(EnumValue constraintAnnotation) { strValues = constraintAnnotation.strValues(); intValues = constraintAnnotation.intValues(); enumValues = constraintAnnotation.enumValues(); } /** * 校驗方法 * * @param value * @param context * @return */ @SneakyThrows @Override public boolean isValid(Object value, ConstraintValidatorContext context) { //針對字符串數組的校驗匹配 if (strValues != null && strValues.length > 0) { if (value instanceof String) { for (String s : strValues) {//判斷值類(lèi)型是否為Integer類(lèi)型 if (s.equals(value)) { return true; } } } } //針對整型數組的校驗匹配 if (intValues != null && intValues.length > 0) { if (value instanceof Integer) {//判斷值類(lèi)型是否為Integer類(lèi)型 for (Integer s : intValues) { if (s == value) { return true; } } } } //針對枚舉類(lèi)型的校驗匹配 if (enumValues != null && enumValues.length > 0) { for (Class<?> cl : enumValues) { if (cl.isEnum()) { //枚舉類(lèi)驗證 Object[] objs = cl.getEnumConstants(); //這里需要注意,定義枚舉時(shí),枚舉值名稱(chēng)統一用value表示 Method method = cl.getMethod("getValue"); for (Object obj : objs) { Object code = method.invoke(obj, null); if (value.equals(code.toString())) { return true; } } } } } return false; } } }
如上所示的@EnumValue約束注解,是一個(gè)非常實(shí)用的擴展,通過(guò)該注解我們可以實(shí)現對參數取值范圍(不是大小范圍)的約束,它支持對int、string以及enum三種數據類(lèi)型的約束,具體使用方式如下:
/** * 定制化注解,支持參數值與指定類(lèi)型數組列表值進(jìn)行匹配(缺點(diǎn)是需要將枚舉值寫(xiě)死在字段定義的注解中) */ @EnumValue(strValues = {"pay", "refund"}, message = "訂單類(lèi)型錯誤") private String orderType; /** * 定制化注解,實(shí)現參數值與枚舉列表的自動(dòng)匹配校驗(能更好地與實(shí)際業(yè)務(wù)開(kāi)發(fā)匹配) */ @EnumValue(enumValues = Status.class, message = "狀態(tài)值不在指定范圍") private String status;
如上所示代碼,該擴展注解既可以使用strValues或intValues屬性來(lái)編程列舉取值范圍,也可以直接通過(guò)enumValues來(lái)綁定枚舉定義。但是需要注意,處于通用考慮,具體枚舉定義的屬性的名稱(chēng)要統一匹配為value、desc,例如Status枚舉定義如下:
public enum Status { PROCESSING(1, "處理中"), SUCCESS(2, "訂單已完成"); Integer value; String desc; Status(Integer value, String desc) { this.value = value; this.desc = desc; } public Integer getValue() { return value; } public String getDesc() { return desc; } }
通過(guò)注解擴展,就能實(shí)現更多方便的約束性注解!
更加靈活的數據校驗工具類(lèi)封裝
除了上面直接在Controller層使用@Validated進(jìn)行綁定數據校驗外,在有些情況,例如你的參數對象中的某個(gè)字段是一個(gè)復合對象,或者業(yè)務(wù)層的某個(gè)方法所定義的入參對象也需要進(jìn)行數據合法性校驗,那么這種情況下如何實(shí)現像Controller層一樣的校驗效果呢?
需要說(shuō)明在這種情況下@Validated已經(jīng)無(wú)法直接使用了,因為@Validated注解發(fā)揮作用主要是Spring MVC在接收參數的過(guò)程中實(shí)現了自動(dòng)數據綁定校驗,而在普通的業(yè)務(wù)方法或者復合參數對象中是沒(méi)有辦法直接綁定校驗的。這種情況下,我們可以通過(guò)定義ValidateUtils工具類(lèi)來(lái)實(shí)現一樣的校驗效果,具體代碼如下:
public class ValidatorUtils { private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); /** * bean整體校驗,有不合規范,拋出第1個(gè)違規異常 */ public static void validate(Object obj, Class<?>... groups) { Set<ConstraintViolation<Object>> resultSet = validator.validate(obj, groups); if (resultSet.size() > 0) { //如果存在錯誤結果,則將其解析并進(jìn)行拼湊后異常拋出 List<String> errorMessageList = resultSet.stream().map(o -> o.getMessage()).collect(Collectors.toList()); StringBuilder errorMessage = new StringBuilder(); errorMessageList.stream().forEach(o -> errorMessage.append(o + ";")); throw new IllegalArgumentException(errorMessage.toString()); } } }
如上所示,我們定義了一個(gè)基于"javax.validation"接口的工具類(lèi)實(shí)現,這樣就可以在非@Validated直接綁定校驗的場(chǎng)景中通過(guò)校驗工具類(lèi)來(lái)實(shí)現對Bean對象約束注解的校驗處理,具體使用代碼如下:
public boolean orderCheck(OrderCheckBO orderCheckBO) { //對參數對象進(jìn)行數據校驗 ValidatorUtils.validate(orderCheckBO); return true; }
而方法入參對象則還是可以繼續使用前面我們介紹的約束性注解進(jìn)行約定,例如上述方法的入參對象定義如下:
@Data @Builder public class OrderCheckBO { @NotNull(message = "訂單號不能為空") private String orderId; @Min(value = 1, message = "訂單金額不能小于0") private Integer orderAmount; @NotNull(message = "創(chuàng )建人不能為空") private String operator; @NotNull(message = "操作時(shí)間不能為空") private String operatorTime; }
這樣在編程體驗上就可以整體上保持一致!
數據合法性校驗結果異常統一處理
通過(guò)前面我們所講的各種約束注解,我們實(shí)現了對Controller層接口以及業(yè)務(wù)方法參數對象的統一數據校驗。而為了保持校驗異常處理的統一處理和錯誤報文統一輸出,我們還可以定義通用的異常處理機制,來(lái)保證各類(lèi)數據校驗錯誤都能以統一錯誤格式反饋給調用方。具體代碼如下:
@Slf4j @ControllerAdvice public class GlobalExceptionHandler { /** * 統一處理參數校驗錯誤異常(非Spring接口數據綁定驗證) * * @param response * @param e * @return */ @ExceptionHandler(BindException.class) @ResponseBody public ResponseResult<?> processValidException(HttpServletResponse response, BindException e) { response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); //獲取校驗錯誤結果信息,并將信息組裝 List<String> errorStringList = e.getBindingResult().getAllErrors() .stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList()); String errorMessage = String.join("; ", errorStringList); response.setContentType("application/json;charset=UTF-8"); log.error(e.toString() + "_" + e.getMessage(), e); return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(), errorMessage); } /** * 統一處理參數校驗錯誤異常 * * @param response * @param e * @return */ @ExceptionHandler(IllegalArgumentException.class) @ResponseBody public ResponseResult<?> processValidException(HttpServletResponse response, IllegalArgumentException e) { response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); String errorMessage = String.join("; ", e.getMessage()); response.setContentType("application/json;charset=UTF-8"); log.error(e.toString() + "_" + e.getMessage(), e); return ResponseResult.systemException(GlobalCodeEnum.GL_FAIL_9998.getCode(), errorMessage); } ... }
如上所示,我們定義了針對前面兩種數據校驗方式的統一異常處理機制,這樣數據校驗的錯誤信息就能通過(guò)統一的報文格式反饋給調用端,從而實(shí)現接口數據報文的統一返回!
其中通用的接口參數對象ResponseResult的代碼定義如下:
@Data @Builder @NoArgsConstructor @AllArgsConstructor @JsonPropertyOrder({"code", "message", "data"}) public class ResponseResult<T> implements Serializable { private static final long serialVersionUID = 1L; /** * 返回的對象 */ @JsonInclude(JsonInclude.Include.NON_NULL) private T data; /** * 返回的編碼 */ private Integer code; /** * 返回的信息 */ private String message; /** * @param data 返回的數據 * @param <T> 返回的數據類(lèi)型 * @return 響應結果 */ public static <T> ResponseResult<T> OK(T data) { return packageObject(data, GlobalCodeEnum.GL_SUCC_0); } /** * 自定義系統異常信息 * * @param code * @param message 自定義消息 * @param <T> * @return */ public static <T> ResponseResult<T> systemException(Integer code, String message) { return packageObject(null, code, message); } }
當然,這樣的統一報文格式也不僅僅只處理異常返回,正常的數據報文格式也可以通過(guò)該對象來(lái)進(jìn)行統一封裝!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站