大家好询筏,我是二哥呀榕堰。最近正在開發(fā)一個知識庫學(xué)習(xí)網(wǎng)站編程貓,需要對請求參數(shù)進(jìn)行校驗嫌套,比如說非空啊逆屡、長度限制啊等等,可選的解決方案有兩種:
- 一種是用 Hibernate Validator 來處理
- 一種是用全局異常來處理
兩種方式踱讨,我們一一來實踐體驗一下魏蔗。
一、Hibernate Validator
Spring Boot 已經(jīng)內(nèi)置了 Hibernate Validator 校驗框架痹筛,這個可以通過 Spring Boot 官網(wǎng)查看和確認(rèn)莺治。
第一步,進(jìn)入 Spring Boot 官網(wǎng)帚稠,點擊 learn 這個面板谣旁,點擊參考文檔。
第二步滋早,在參考文檔頁點擊「依賴的版本」榄审。
第三步,在依賴版本頁就可以查看到所有的依賴了杆麸,包括版本號搁进。
PS:如果發(fā)現(xiàn)沒有起效浪感,可能是依賴版本沖突了,手動把 Hibernate Validator 依賴添加到 pom.xml 文件就可以了饼问。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
通過 Hibernate Validator 校驗框架篮撑,我們可以直接在請求參數(shù)的字段上加入注解來完成校驗。
具體該怎么做呢匆瓜?
第一步赢笨,在需要驗證的字段上加上 Hibernate Validator 提供的校驗注解。
比如說我現(xiàn)在有一個用戶名和密碼登錄的請求參數(shù) UsersLoginParam 類:
@Data
@ApiModel(value="用戶登錄", description="用戶表")
public class UsersLoginParam implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "登錄名")
@NotBlank(message="登錄名不能為空")
private String userLogin;
@ApiModelProperty(value = "密碼")
@NotBlank(message="密碼不能為空")
private String userPass;
}
就可以通過 @NotBlank
注解來對用戶名和密碼進(jìn)行判空校驗驮吱。除了 @NotBlank
注解茧妒,Hibernate Validator 還提供了以下常用注解:
-
@NotNull
:被注解的字段不能為 null; -
@NotEmpty
:被注解的字段不能為空左冬; -
@Min
:被注解的字段必須大于等于其value值桐筏; -
@Max
:被注解的字段必須小于等于其value值; -
@Size
:被注解的字段必須在其min和max值之間拇砰; -
@Pattern
:被注解的字段必須符合所定義的正則表達(dá)式梅忌; -
@Email
:被注解的字段必須符合郵箱格式。
第二步除破,在對應(yīng)的請求接口(UsersController.login()
)中添加 @Validated
注解牧氮,并注入一個 BindingResult
參數(shù)。
@Controller
@Api(tags="用戶")
@RequestMapping("/users")
public class UsersController {
@Autowired
private IUsersService usersService;
@ApiOperation(value = "登錄以后返回token")
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public ResultObject login(@Validated UsersLoginParam users, BindingResult result) {
String token = usersService.login(users.getUserLogin(), users.getUserPass());
if (token == null) {
return ResultObject.validateFailed("用戶名或密碼錯誤");
}
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("token", token);
tokenMap.put("tokenHead", tokenHead);
return ResultObject.success(tokenMap);
}
}
第三步瑰枫,為控制層(UsersController)創(chuàng)建一個切面踱葛,將通知注入到 BindingResult 對象中,然后再判斷是否有校驗錯誤光坝,有錯誤的話返回校驗提示信息尸诽,否則放行。
@Aspect
@Component
@Order(2)
public class BindingResultAspect {
@Pointcut("execution(public * com.codingmore.controller.*.*(..))")
public void BindingResult() {
}
@Around("BindingResult()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof BindingResult) {
BindingResult result = (BindingResult) arg;
if (result.hasErrors()) {
FieldError fieldError = result.getFieldError();
if(fieldError!=null){
return ResultObject.validateFailed(fieldError.getDefaultMessage());
}else{
return ResultObject.validateFailed();
}
}
}
}
return joinPoint.proceed();
}
}
這里涉及到了 SpringBoot AOP 的知識盯另,我在前面的文章里講解過了性含,戳這個鏈接可以直達(dá):SpringBoot AOP 掃盲
第四步,訪問登錄接口鸳惯,用戶名和密碼都不傳入的情況下商蕴,就會返回“用戶名不能為空”的提示信息。
通過 debug 的形式悲敷,體驗一下整個工作流程究恤。
可以看得出,Hibernate Validator 帶來的優(yōu)勢有這些:
- 驗證邏輯與業(yè)務(wù)邏輯進(jìn)行了分離后德,降低了程序耦合度;
- 統(tǒng)一且規(guī)范的驗證方式抄腔,無需再次編寫重復(fù)的驗證代碼瓢湃。
不過理张,也帶來一些弊端,比如說:
- 需要在請求接口的方法中注入 BindingResult 對象
- 只能校驗一些非常簡單的邏輯绵患,涉及到數(shù)據(jù)查詢就無能為力了雾叭。
二、全局異常處理
使用全局異常處理的優(yōu)點就是比較靈活落蝙,可以處理比較復(fù)雜的邏輯校驗织狐,在校驗失敗的時候直接拋出異常,然后進(jìn)行捕獲處理就可以了筏勒。
第一步移迫,新建一個自定義異常類 ApiException。
public class ApiException extends RuntimeException {
private IErrorCode errorCode;
public ApiException(IErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public ApiException(String message) {
super(message);
}
public ApiException(Throwable cause) {
super(cause);
}
public ApiException(String message, Throwable cause) {
super(message, cause);
}
public IErrorCode getErrorCode() {
return errorCode;
}
}
第二步管行,新建一個斷言處理類 Asserts厨埋,簡化拋出 ApiException 的步驟。
public class Asserts {
public static void fail(String message) {
throw new ApiException(message);
}
public static void fail(IErrorCode errorCode) {
throw new ApiException(errorCode);
}
}
第三步捐顷,新建一全局異常處理類 GlobalExceptionHandler荡陷,對異常信息進(jìn)行解析,并封裝到統(tǒng)一的返回對象 ResultObject 中迅涮。
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(value = ApiException.class)
public ResultObject handle(ApiException e) {
if (e.getErrorCode() != null) {
return ResultObject.failed(e.getErrorCode());
}
return ResultObject.failed(e.getMessage());
}
}
全局異常處理類用到了兩個注解废赞,@ControllerAdvice
和 @ExceptionHandler
。
@ControllerAdvice
是一個特殊的 @Component
(可以通過源碼看得到)叮姑,用于標(biāo)識一個類蛹头,這個類中被以下三種注解標(biāo)識的方法:@ExceptionHandler
,@InitBinder
戏溺,@ModelAttribute
渣蜗,將作用于所有@Controller
類的接口上。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
}
@ExceptionHandler
注解的作用就是標(biāo)識統(tǒng)一異常處理旷祸,它可以指定要統(tǒng)一處理的異常類型耕拷,比如說我們自定義的 ApiException。
第四步托享,在需要校驗的地方通過 Asserts 類拋出異常 ApiException骚烧。還拿用戶登錄這個接口來說明吧。
@Controller
@Api(tags="用戶")
@RequestMapping("/users")
public class UsersController {
@ApiOperation(value = "登錄以后返回token")
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public ResultObject login(@Validated UsersLoginParam users, BindingResult result) {
String token = usersService.login(users.getUserLogin(), users.getUserPass());
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("token", token);
tokenMap.put("tokenHead", tokenHead);
return ResultObject.success(tokenMap);
}
}
該接口需要查詢數(shù)據(jù)庫驗證密碼是否正確闰围,如果密碼不正確就拋出校驗信息“密碼不正確”赃绊。
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements IUsersService {
public String login(String username, String password) {
String token = null;
//密碼需要客戶端加密后傳遞
UserDetails userDetails = loadUserByUsername(username);
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
Asserts.fail("密碼不正確");
}
// 其他代碼省略
return token;
}
}
第五步,通過 ApiPost 來測試一下接口羡榴,故意把密碼輸錯碧查。
也可以通過 debug 的形式,體驗一下整個工作流程。
三忠售、總結(jié)
實際開發(fā)中把兩者結(jié)合在一起用传惠,就可以彌補(bǔ)彼此的短板了,簡單校驗用 Hibernate Validator稻扬,復(fù)雜一點的邏輯校驗卦方,比如說需要數(shù)據(jù)庫查詢用全局異常處理來實現(xiàn)。
源碼地址:https://github.com/itwanger/coding-more
參考鏈接:http://www.macrozheng.com
本篇已收錄至 GitHub 上星標(biāo) 1.6k+ star 的開源專欄《Java 程序員進(jìn)階之路》泰佳,據(jù)說每一個優(yōu)秀的 Java 程序員都喜歡她盼砍,風(fēng)趣幽默、通俗易懂逝她。內(nèi)容包括 Java 基礎(chǔ)浇坐、Java 并發(fā)編程、Java 虛擬機(jī)汽绢、Java 企業(yè)級開發(fā)吗跋、Java 面試等核心知識點。學(xué) Java宁昭,就認(rèn)準(zhǔn) Java 程序員進(jìn)階之路??跌宛。
https://github.com/itwanger/toBeBetterJavaer
star 了這個倉庫就等于你擁有了成為了一名優(yōu)秀 Java 工程師的潛力。也可以戳下面的鏈接跳轉(zhuǎn)到《Java 程序員進(jìn)階之路》的官網(wǎng)網(wǎng)址积仗,開始愉快的學(xué)習(xí)之旅吧疆拘。
沒有什么使我停留——除了目的,縱然岸旁有玫瑰、有綠蔭、有寧靜的港灣销睁,我是不系之舟响鹃。