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

阿粉寫了八千多字,只為講透參數(shù)合法性驗(yàn)證

安全 數(shù)據(jù)安全
關(guān)于參數(shù)合法性驗(yàn)證的重要性就不多說了,即使前端對(duì)參數(shù)做了基本驗(yàn)證以外,后端依然還需要進(jìn)行驗(yàn)證,以防不合規(guī)的數(shù)據(jù)直接進(jìn)入后端,嚴(yán)重的甚至?xí)斐上到y(tǒng)直接崩潰!

 最近很多讀者給阿粉留言,說怎么好久沒看到我的文章了,這里說一下。

由于公眾號(hào)不再按時(shí)間線排序,所以你會(huì)發(fā)現(xiàn)有時(shí)候能看到幾天前的文章,這不是出BUG,是公眾號(hào)的一次改變。

至于排序的具體標(biāo)準(zhǔn)是啥,阿粉也不太清楚,大概和你打開某個(gè)公眾號(hào)的頻率有關(guān)。

所以如果你想第一時(shí)間收到阿粉的文章,可以點(diǎn)擊Java極客技術(shù)的的頭像,再點(diǎn)右上角三個(gè)點(diǎn),進(jìn)去設(shè)置一下【星標(biāo)】。

一、介紹

關(guān)于參數(shù)合法性驗(yàn)證的重要性就不多說了,即使前端對(duì)參數(shù)做了基本驗(yàn)證以外,后端依然還需要進(jìn)行驗(yàn)證,以防不合規(guī)的數(shù)據(jù)直接進(jìn)入后端,嚴(yán)重的甚至?xí)斐上到y(tǒng)直接崩潰!

本文結(jié)合自己在項(xiàng)目中的實(shí)際使用經(jīng)驗(yàn),主要以實(shí)用為主,對(duì)數(shù)據(jù)合法性驗(yàn)證做一次總結(jié),不了解的朋友可以學(xué)習(xí)一下,同時(shí)可以立馬實(shí)踐到項(xiàng)目上去。

下面我們通過幾個(gè)示例來演示如何判斷參數(shù)是否合法,不多說直接開擼!

二、斷言驗(yàn)證

對(duì)于參數(shù)的合法性驗(yàn)證,最初的做法比較簡(jiǎn)單,自定義一個(gè)異常類。

  1. public class CommonException extends RuntimeException { 
  2.  
  3.     /**錯(cuò)誤碼*/ 
  4.     private Integer code; 
  5.  
  6.     /**錯(cuò)誤信息*/ 
  7.     private String msg; 
  8.  
  9.     //...set/get 
  10.  
  11.     public CommonException(String msg) { 
  12.         super(msg); 
  13.         this.msg = msg; 
  14.     } 
  15.  
  16.     public CommonException(String msg, Throwable cause) { 
  17.         super(msg, cause); 
  18.         this.msg = msg; 
  19.     } 
  20.  

當(dāng)判斷某個(gè)參數(shù)不合法的時(shí)候,直接拋異常!

  1. @RestController 
  2. public class HelloController { 
  3.  
  4.  @RequestMapping("/upload"
  5.  public void upload(MultipartFile file) { 
  6.   if (file == null) { 
  7.    throw new CommonException("請(qǐng)選擇上傳文件!"); 
  8.   } 
  9.    
  10.   //..... 
  11.  } 

然后寫一個(gè)統(tǒng)一異常攔截器,對(duì)拋異常的程序進(jìn)行處理。

這種做法比較直觀,如果當(dāng)前參數(shù)既要判斷是否為空,又要判斷長(zhǎng)度是否超過最大長(zhǎng)度的時(shí)候,代碼就顯得有點(diǎn)多了!

于是,程序界的大佬想到了一個(gè)更加優(yōu)雅又能節(jié)省代碼的方式,創(chuàng)建一個(gè)斷言類工具類,專門用來判斷參數(shù)的是否合法,如果不合法,就拋異常!

  1. /** 
  2.  * 斷言工具類 
  3.  */ 
  4. public abstract class LocalAssert { 
  5.   
  6.  public static void isTrue(boolean expression, String message) throws CommonException { 
  7.   if (!expression) { 
  8.    throw new CommonException(message); 
  9.   } 
  10.  } 
  11.  public static void isStringEmpty(String param, String message) throws CommonException{ 
  12.   if(StringUtils.isEmpty(param)) { 
  13.    throw new CommonException(message); 
  14.   } 
  15.  } 
  16.  
  17.  public static void isObjectEmpty(Object object, String message) throws CommonException { 
  18.   if (object == null) { 
  19.    throw new CommonException(message); 
  20.   } 
  21.  } 
  22.  
  23.  public static void isCollectionEmpty(Collection coll, String message) throws CommonException { 
  24.   if (coll == null || (coll.size() == 0)) { 
  25.    throw new CommonException(message); 
  26.   } 
  27.  } 

當(dāng)我們需要對(duì)參數(shù)進(jìn)行驗(yàn)證的時(shí)候,直接通過這個(gè)類就可以完成基本操作,方式如下:

  1. @RestController 
  2. public class HelloController { 
  3.  
  4.  @RequestMapping("/save"
  5.  public void save(String name, String email) { 
  6.   LocalAssert.isStringEmpty(name"用戶名不能為空!"); 
  7.   LocalAssert.isStringEmpty(email, "郵箱不能為空!"); 
  8.    
  9.   //..... 
  10.  } 

相比上個(gè)步驟,當(dāng)要判斷的參數(shù)比較多時(shí),代碼明顯簡(jiǎn)潔多了!

類似這樣的工具類,spring也提供了一個(gè)名為Assert的斷言工具類,在開發(fā)的時(shí)候,可以直接使用!

 

三、注解驗(yàn)證

使用注解對(duì)數(shù)據(jù)進(jìn)行合法性驗(yàn)證,可以說是 java 界一項(xiàng)非常偉大的創(chuàng)新,使用這種方式不僅使的代碼變得很簡(jiǎn)潔,而且閱讀起來非常令人賞心悅目!

3.1、依賴包引入

下面我們一起來看看具體的實(shí)踐方式,以Spring Boot工程為例,如果需要使用注解校驗(yàn),直接引入spring-boot-starter-web依賴包即可,會(huì)自動(dòng)將注解驗(yàn)證相關(guān)的依賴包打入工程!

  1. <!-- spring boot web --> 
  2. <dependency> 
  3.     <groupId>org.springframework.boot</groupId> 
  4.     <artifactId>spring-boot-starter-web</artifactId> 
  5. </dependency> 

下面在創(chuàng)建實(shí)體類的時(shí)候,還會(huì)用到lombok插件,因此還需要引入lombok依賴包!

  1. <!-- lombok --> 
  2. <dependency> 
  3.     <groupId>org.projectlombok</groupId> 
  4.     <artifactId>lombok</artifactId> 
  5.     <version>1.18.4</version> 
  6.     <scope>provided</scope> 
  7. </dependency> 

如果是普通的Java工程,引入以下幾個(gè)依賴包即可!

  1. <dependency> 
  2.     <groupId>org.hibernate.validator</groupId> 
  3.     <artifactId>hibernate-validator</artifactId> 
  4.     <version>6.0.9.Final</version> 
  5. </dependency> 
  6. <dependency> 
  7.      <groupId>javax.el</groupId> 
  8.      <artifactId>javax.el-api</artifactId> 
  9.      <version>3.0.0</version> 
  10.  </dependency> 
  11.  <dependency> 
  12.     <groupId>org.glassfish.web</groupId> 
  13.     <artifactId>javax.el</artifactId> 
  14.     <version>2.2.6</version> 
  15.  </dependency> 

3.2、注解校驗(yàn)請(qǐng)求對(duì)象

緊接著我們來創(chuàng)建一個(gè)實(shí)體User,用于模擬用戶注冊(cè)時(shí)的請(qǐng)求實(shí)體對(duì)象!

  1. @Data 
  2. @EqualsAndHashCode(callSuper = false
  3. @Accessors(chain = true
  4. public class User { 
  5.  
  6.     @NotBlank(message = "用戶名不能為空!"
  7.     private String userName; 
  8.  
  9.     @Email(message = "郵箱格式不正確"
  10.     @NotBlank(message = "郵箱不能為空!"
  11.     private String email; 
  12.  
  13.     @NotBlank(message = "密碼不能為空!"
  14.     @Size(min = 8, max = 16,message = "請(qǐng)輸入長(zhǎng)度在8~16位的密碼"
  15.     private String userPwd; 
  16.  
  17.     @NotBlank(message = "確認(rèn)密碼不能為空!"
  18.     private String confirmPwd; 

在web層創(chuàng)建一個(gè)register()注冊(cè)接口方法,同時(shí)在請(qǐng)求參數(shù)上添加@Valid,如下:

  1. @RestController 
  2. public class UserController { 
  3.  
  4.     @RequestMapping("/register"
  5.     public boolean register(@RequestBody @Valid User user){ 
  6.         if(!user.getUserPwd().equals(user.getConfirmPwd())){ 
  7.             throw new CommonException("確認(rèn)密碼與密碼不相同,請(qǐng)確認(rèn)!"); 
  8.         } 
  9.   //業(yè)務(wù)處理... 
  10.         return true
  11.     } 

最后自定義一個(gè)異常全局處理器,用于處理異常消息,如下:

  1. @Slf4j 
  2. @Configuration 
  3. public class GlobalWebMvcConfig implements WebMvcConfigurer { 
  4.  
  5.     /** 
  6.      * 統(tǒng)一異常處理 
  7.      * @param resolvers 
  8.      */ 
  9.     @Override 
  10.     public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { 
  11.         resolvers.add(new HandlerExceptionResolver() { 
  12.             @Override 
  13.             public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) { 
  14.                 log.error("【統(tǒng)一異常攔截】請(qǐng)求出現(xiàn)異常,內(nèi)容如下:",e); 
  15.                 ModelAndView mv = new ModelAndView(new MappingJackson2JsonView()); 
  16.                 String uri = request.getRequestURI(); 
  17.                 if(e instanceof CommonException){ 
  18.                     //CommonExecption為自定義異常類拋出的異常 
  19.                     printWrite(((CommonException) e).getMsg(),((CommonException) e).getData(), uri, mv); 
  20.                 } else if(e instanceof MethodArgumentNotValidException){ 
  21.                     //MethodArgumentNotValidException為注解校驗(yàn)異常類 
  22.                     //獲取注解校驗(yàn)異常信息 
  23.                     String error = ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage(); 
  24.                     printWrite(error,null, uri, mv); 
  25.                 } else { 
  26.                     printWrite(e.getMessage(),null, uri, mv); 
  27.                 } 
  28.                 return mv; 
  29.             } 
  30.         }); 
  31.     } 
  32.  
  33.  
  34.     /** 
  35.      * 異常封裝相應(yīng)結(jié)果 
  36.      * @param object 
  37.      */ 
  38.     private void printWrite(String msg, Object object, String uri, ModelAndView mv){ 
  39.         ResResult resResult = new ResResult(uri, object); 
  40.         if(msg != null){resResult.setMsg(msg);} 
  41.         if(log.isDebugEnabled()){ 
  42.             log.debug("【response】異常輸出結(jié)果:" + JSONObject.toJSONString(resResult, SerializerFeature.WriteMapNullValue)); 
  43.         } 
  44.         Map resultMap = BeanToMapUtil.beanToMap(resResult); 
  45.         mv.addAllObjects(resultMap); 
  46.     } 

下面我們啟動(dòng)項(xiàng)目,使用postman來測(cè)試一把,看看效果如何?

  • 測(cè)試字段是否為空

 

  • 測(cè)試郵箱是否合法

 

  • 測(cè)試密碼長(zhǎng)度是否符合要求

 

  • 測(cè)試密碼與確認(rèn)密碼是否相同

 

3.3、注解校驗(yàn)請(qǐng)求參數(shù)

上面我們介紹了請(qǐng)求對(duì)象的驗(yàn)證方式,那如果直接在方法上對(duì)請(qǐng)求參數(shù)進(jìn)行驗(yàn)證是否同樣有效呢?

為了眼見為實(shí),下面我們就來模擬在方法上對(duì)請(qǐng)求參數(shù)進(jìn)行驗(yàn)證,看看結(jié)果如何。

新建一個(gè)查詢接口query,如下

  1. @RestController 
  2. public class UserController { 
  3.  
  4.     @PostMapping("/query"
  5.     public boolean query(@RequestParam("userId") @Valid @NotBlank(message = "用戶ID不能為空") String userId ){ 
  6.         return true
  7.     } 
  8.  

使用postman請(qǐng)求試一試,默認(rèn)給userId參數(shù)為null,結(jié)果如下:

 

很清晰的看到,query()方法中的參數(shù)注解驗(yàn)證無(wú)效!

當(dāng)我們?cè)赨serController類上加上@Validated注解!

  1. @RestController 
  2. @Validated 
  3. public class UserController { 
  4.  
  5.     @PostMapping("/query"
  6.     public boolean query(@RequestParam("userId") @Valid @NotBlank(message = "用戶ID不能為空") String userId ){ 
  7.         return true
  8.     } 
  9.  

使用postman請(qǐng)求再試一試,結(jié)果如下!

 

很清晰的看到,注解進(jìn)行了驗(yàn)證,同時(shí)還拋出異常ConstraintViolationException!

 

@Validated參數(shù)作用于類上時(shí),表示告訴Spring可以對(duì)方法中請(qǐng)求參數(shù)進(jìn)行校驗(yàn)!

所有在實(shí)際開發(fā)的時(shí)候,我們可以使用@Validated和@Valid注解的組合來對(duì)方法中的請(qǐng)求參數(shù)和請(qǐng)求對(duì)象進(jìn)行校驗(yàn)!

同時(shí),@Validated和@Valid注解不僅僅只是驗(yàn)證控制器級(jí)別,可以驗(yàn)證任何Spring組件,例如Service層方法入?yún)⒌尿?yàn)證!

  1. @Service 
  2. @Validated 
  3. public class UserService { 
  4.  
  5.     public void saveUser(@Valid User user){ 
  6.         //dao插入 
  7.     } 

3.4、自定義注解驗(yàn)證

默認(rèn)的情況下,依賴包已經(jīng)給我們提供了非常多的校驗(yàn)注解,如下!

  • JSR提供的校驗(yàn)注解!

 

  • Hibernate Validator提供的校驗(yàn)注解

 

但是某些情況,例如性別這個(gè)參數(shù)可能需要我們自己去驗(yàn)證,同時(shí)我們也可以自定義一個(gè)注解來完成參數(shù)的校驗(yàn),實(shí)現(xiàn)方式如下!

  • 新創(chuàng)建一個(gè)Sex注解,其中SexValidator類指的是具體的參數(shù)驗(yàn)證類
  1. @Target({FIELD}) 
  2. @Retention(RUNTIME) 
  3. @Constraint(validatedBy = SexValidator.class) 
  4. @Documented 
  5. public @interface Sex { 
  6.  
  7.     String message() default "性別值不在可選范圍內(nèi)"
  8.  
  9.     Class<?>[] groups() default {}; 
  10.  
  11.     Class<? extends Payload>[] payload() default {}; 
  • SexValidator類,實(shí)現(xiàn)自ConstraintValidator接口
  1. public class SexValidator implements ConstraintValidator<Sex, String> { 
  2.  
  3.     @Override 
  4.     public boolean isValid(String value, ConstraintValidatorContext context) { 
  5.         Set<String> sexSet = new HashSet<String>(); 
  6.         sexSet.add("男"); 
  7.         sexSet.add("女"); 
  8.         return sexSet.contains(value); 
  9.     } 

最后在User實(shí)體類上加入一個(gè)性別參數(shù),使用自定義注解進(jìn)行校驗(yàn)!

  1. @Data 
  2. @EqualsAndHashCode(callSuper = false
  3. @Accessors(chain = true
  4. public class User { 
  5.  
  6.     @NotBlank(message = "用戶名不能為空!"
  7.     private String userName; 
  8.  
  9.     @Email(message = "郵箱格式不正確"
  10.     @NotBlank(message = "郵箱不能為空!"
  11.     private String email; 
  12.  
  13.     @NotBlank(message = "密碼不能為空!"
  14.     @Size(min = 8, max = 16,message = "請(qǐng)輸入長(zhǎng)度在8~16位的密碼"
  15.     private String userPwd; 
  16.  
  17.     @NotBlank(message = "確認(rèn)密碼不能為空!"
  18.     private String confirmPwd; 
  19.  
  20.     /** 
  21.      * 自定義注解校驗(yàn) 
  22.      */ 
  23.     @Sex(message = "性別輸入有誤!"
  24.     private String sex; 

使用postman來請(qǐng)求試一試,結(jié)果如下!

  • 不傳sex參數(shù)

 

很清晰的看到,已經(jīng)生效!

3.5、手動(dòng)進(jìn)行注解校驗(yàn)

某些時(shí)候呢,假如有100個(gè)類需要用到校驗(yàn)注解,此時(shí)我們可能在每個(gè)類會(huì)加上注解@Validated或者@Valid,再增加100個(gè)這樣的類,就會(huì)造成很多大量的重復(fù)工作。

而此時(shí),我們的訴求是想對(duì)有校驗(yàn)注解的實(shí)體類進(jìn)行全局參數(shù)驗(yàn)證!

解決辦法就會(huì)用到Validator提供的手動(dòng)注解校驗(yàn)證工具類,實(shí)現(xiàn)方法如下!

  • 新建一個(gè)注解驗(yàn)證工具類
  1. /** 
  2.  * 注解校驗(yàn)工具類 
  3.  */ 
  4. public class ValidatorUtils { 
  5.  
  6.     /** 
  7.      * 獲取對(duì)象中所有注解校驗(yàn)證異常信息 
  8.      * @param object 
  9.      * @return 
  10.      */ 
  11.     public static String validated(Object object){ 
  12.         List<String> errorMessageList = new ArrayList<>(); 
  13.   //獲取注解校驗(yàn)工廠 
  14.         ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 
  15.         Validator validator = factory.getValidator(); 
  16.         Set<ConstraintViolation<Object>> violations = validator.validate(object); 
  17.  
  18.         for (ConstraintViolation<Object> constraintViolation : violations) { 
  19.             errorMessageList.add(constraintViolation.getMessage()); 
  20.         } 
  21.         return errorMessageList.toString(); 
  22.     } 

使用ValidatorUtils工具類,對(duì)參數(shù)進(jìn)行驗(yàn)證

  1. @Test 
  2. public void testUser(){ 
  3.     User user = new User(); 
  4.     System.out.println(ValidatorUtils.validated(user)); 

執(zhí)行之后,結(jié)果如下!

  1. [郵箱不能為空!, 用戶名不能為空!, 密碼不能為空!, 確認(rèn)密碼不能為空!, 性別輸入有誤!] 

當(dāng)然你還可以對(duì)ValidatorUtils類進(jìn)行改造,當(dāng)有異常信息的時(shí)候,直接拋異常!

同時(shí),你還可以通過@Autowired直接注入的方式來獲取Validator對(duì)象!

  1. @Autowired 
  2. Validator validator 

3.6、spring 注解校驗(yàn)原理

如果你對(duì)springmvc的方法參數(shù)解析器(HandlerMethodArgumentResolver)了解的話,就可能會(huì)想到參數(shù)校驗(yàn)這塊肯定是在對(duì)應(yīng)的方法參數(shù)解析器里執(zhí)行的。

直接定位到resolveArgument這個(gè)方法,先通過WebDataBinder進(jìn)行入?yún)傩越壎?,然后再進(jìn)行校驗(yàn)!

 

validateIfApplicable方法邏輯,會(huì)遍歷當(dāng)前參數(shù)methodParam所有的注解,如果注解是@Validated或者注解的名字以Valid開頭,則使用WebDataBinder對(duì)象執(zhí)行校驗(yàn)邏輯。

 

方法參數(shù)解析器只針對(duì)接口請(qǐng)求時(shí)入?yún)⑦M(jìn)行驗(yàn)證,如果想對(duì)任何組件中方法進(jìn)行注解校驗(yàn),似乎還缺了點(diǎn)什么!

而當(dāng)需要對(duì)一個(gè)類中的方法參數(shù)使用注解校驗(yàn)時(shí),在類上加上@Validated就是為了告訴Spring去校驗(yàn)方法參數(shù)!

底層核心是通過切面代理類并配合MethodValidationPostProcessor這個(gè)后置處理器進(jìn)行處理!

 

四、總結(jié)

參數(shù)驗(yàn)證,在開發(fā)中使用非常頻繁,如何優(yōu)雅的進(jìn)行驗(yàn)證,讓代碼變得更加可讀,是業(yè)界大佬一直在追求的目標(biāo)!

本文主要是對(duì)自己在項(xiàng)目中的實(shí)際使用到參數(shù)驗(yàn)證方式加一整理,希望能幫助到各位網(wǎng)友!

五、參考1、SpringMVC源碼

2、JavaGuide - 如何在 Spring/Spring Boot 中做參數(shù)校驗(yàn)?[1]

3、胡峻崢 - SpringMvc @Validated注解執(zhí)行原理[2]

參考資料

[1]JavaGuide - 如何在 Spring/Spring Boot 中做參數(shù)校驗(yàn)?: https://juejin.im/post/5dc8bc745188254e7a155ba0#heading-14[2]胡峻崢 - SpringMvc @Validated注解執(zhí)行原理: https://www.cnblogs.com/hujunzheng/p/12570921.html

 

 

責(zé)任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2020-06-04 07:55:33

ReentrantLo Java

2020-09-15 09:04:50

程序員參數(shù)檢查

2013-03-18 09:34:35

Office 365云計(jì)算

2021-07-26 18:14:58

人臉識(shí)別AI人工智能

2020-09-02 07:44:13

后端Long前端

2021-07-01 09:43:44

Python函數(shù)參數(shù)

2021-10-27 13:50:50

加密貨幣區(qū)塊鏈貨幣

2020-03-09 10:21:12

Java集合類 Guava

2020-07-09 07:54:35

ThreadPoolE線程池

2020-10-30 07:43:35

Jenkins配置前端

2020-03-31 08:37:31

遞歸單鏈表反轉(zhuǎn)

2023-02-14 08:18:43

2021-08-19 07:34:55

RabbitMQLinuxWindows

2020-10-19 07:50:32

Linux命令系統(tǒng)

2020-08-25 07:32:42

工具對(duì)象 Java

2020-10-19 06:47:05

爬蟲數(shù)據(jù)Jsoup

2020-03-12 09:02:34

數(shù)據(jù)思維統(tǒng)計(jì)學(xué)大數(shù)據(jù)

2020-03-26 09:18:54

高薪本質(zhì)因素

2020-09-11 07:38:50

內(nèi)存泄漏檢測(cè)

2023-11-03 08:27:46

點(diǎn)贊
收藏

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