前言
參數(shù)校驗其實是開發(fā)中一個很重要的點,而做參數(shù)校驗的方法也是千差萬別昭娩。
當(dāng)然,作為開發(fā)者完域,我們肯定希望參數(shù)校驗的過程越簡單越好,不到萬不得已肯定是不會一個一個手寫參數(shù)校驗的瘩将。
參數(shù)校驗失敗也是需要給出提示的吟税,而參數(shù)校驗失敗的提示大多都比較相似,過程也很像姿现,如果有全局處理方法肠仪,肯定會大大減小工作量和出現(xiàn)bug的可能。
關(guān)于參數(shù)校驗的需求以及解決方案
對于參數(shù)校驗备典,我們可以提出以下需求:
- 參數(shù)校驗最好可以自動化進(jìn)行异旧,沒有特殊情況不需要手寫參數(shù)校驗。
- 參數(shù)校驗失敗的異常需要統(tǒng)一的處理方法提佣。
解決方案也很簡單:
- 自動化參數(shù)校驗可以使用
java.validation
包下的一些注解來進(jìn)行泽艘,這樣既可靠欲险,又不會太麻煩。 - 參數(shù)校驗失敗的全局異常處理可以使用
@ControllerAdvice
注解和@ExceptionHandler
注解進(jìn)行匹涮。
參數(shù)校驗
以User
實體類為例:
/**
* 用戶實體類
*
* @author jiangwen
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
@ToString
@Entity
@Table(name = "wendev_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
@NotNull(message = "用戶名不能為空")
@NotEmpty(message = "用戶名不能為空字符串")
@NotBlank(message = "用戶名不能為空")
private String username;
@Column
@NotNull(message = "用戶昵稱不能為空")
@NotEmpty(message = "用戶昵稱不能為空字符串")
@NotBlank(message = "用戶昵稱不能為空")
private String nickname;
@Column
@NotNull(message = "郵箱不能為空")
@NotEmpty(message = "郵箱不能為空字符串")
private String email;
@Column
@NotNull(message = "密碼不能為空")
@NotEmpty(message = "密碼不能為空字符串")
private String password;
/**
* 用戶權(quán)限
* 這個字段是為了以后可能對接Spring Security保留的
*/
@Column
@NotNull(message = "用戶角色不能為空")
@NotEmpty(message = "用戶角色不能為空字符串")
private String role;
@Temporal(TemporalType.TIMESTAMP)
private Date createTime;
@Temporal(TemporalType.TIMESTAMP)
private Date updateTime;
/**
* 該用戶所發(fā)表的全部評論
*/
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Comment> comments = new ArrayList<>();
@Version
private Long version;
}
因為我們的參數(shù)校驗需求比較簡單——非空、非空字符串槐壳,所以也就需要兩個注解:@NotNull
表示這個屬性不能沒有然低,@NotEmpty
表示不可以為空字符串。
用戶名和昵稱上使用了@NotBlank
注解务唐,這個注解表示調(diào)用trim()
方法后不為空雳攘,防止有人注冊全是空格的用戶名和昵稱。密碼沒有調(diào)用是因為一句話不說隨便把別人密碼里的空格弄沒了太不厚道了枫笛。
除此之外吨灭,還有針對數(shù)值范圍的@Min
,@Max
刑巧,@Positive
喧兄,@Negative
等注解、表示必須為空的@Null
注解等啊楚,都可以用來很方便地規(guī)定數(shù)值的合法范圍吠冤,具體可以看文檔。
有了這些注解恭理,我們就不用一個一個if地寫參數(shù)合法性校驗了拯辙。
而在Controller里接受參數(shù)時,加上@Valid
注解就可以校驗了颜价。當(dāng)然最后發(fā)現(xiàn)不加其實也可以校驗涯保。
那么參數(shù)校驗失敗會發(fā)生什么呢?會拋出一個參數(shù)校驗失敗的異常javax.validation.ConstraintViolationException
周伦,內(nèi)含我們在message
里寫明的提示信息夕春,比如:
所以,針對參數(shù)校驗失敗的情況横辆,我們只要寫一個統(tǒng)一的方法撇他,把這里面的message
在前端顯示出來就可以了。
參數(shù)校驗異常的統(tǒng)一處理
在Spring Boot
中進(jìn)行全局異常處理狈蚤,可以使用一個叫@ControllerAdvice
的注解困肩。這個注解一看就與Controller有關(guān)——加上它的類就變成了Controller的增強(qiáng)器,可以做一些很神奇的事情脆侮,比如全局?jǐn)?shù)據(jù)處理锌畸、全局異常處理等。在這里我們就用它做全局異常處理靖避。
當(dāng)然潭枣,全局異常處理自然少不了@ExceptionHandler
注解比默,可以指定用它處理哪一種異常。
@ControllerAdvice
public class ControllerExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest request, Exception e) throws Exception {
logger.error("Request URL: {}; Exception: {}", request.getRequestURL(), e);
e.printStackTrace();
// 如果是指定404盆犁、500等狀態(tài)碼的返回頁面命咐,就不處理直接把e拋出,交給SpringBoot處理
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
var mv = new ModelAndView();
mv.addObject("url", request.getRequestURL());
mv.addObject("exception", e);
mv.setViewName("error/error");
return mv;
}
@ExceptionHandler(ConstraintViolationException.class)
public ModelAndView validationExceptionHandler(HttpServletRequest request,
ConstraintViolationException e,
RedirectAttributes attributes) {
var mv = new ModelAndView("redirect:" + request.getRequestURI());
attributes.addFlashAttribute("errInfo", e.getConstraintViolations().iterator().next().getMessage());
return mv;
}
}
第一個方法是用來處理所有異常的——出現(xiàn)異常后重定向到一個專門的錯誤頁面并顯示提示信息谐岁。用來做參數(shù)校驗異常處理的是第二個方法醋奠。
處理邏輯其實很簡單:
首先根據(jù)用戶的請求獲取到用戶請求的URI,并以此新建一個包含重定向的ModelAndView
伊佃,出現(xiàn)參數(shù)校驗異常時把用戶重定向回去窜司;然后給頁面添加一個重定向?qū)傩裕?code>errInfo,這個是我們在頁面中預(yù)先放置好的(下面會有頁面的代碼)航揉,把異常信息中的第一條拿出來(e.getConstraintViolations()
返回的是一個HashSet
塞祈,后面的方法是把它的第一項取出來闰挡,再取出消息试伙。因為參數(shù)校驗失敗的信息可能有多條,但是最好還是一次返回一條懦底,所以就只把第一條拿出來返回漠秋,當(dāng)然返回多條也是可以的笙蒙,那就變成了對HashSet
的遍歷)。
最后庆锦,把這個ModelAndView
返回捅位,也就相當(dāng)于把用戶重定向回參數(shù)校驗失敗的頁面,并且在頁面上顯示一條錯誤消息搂抒。這就達(dá)到了我們統(tǒng)一處理異常的目的艇搀。
當(dāng)然,這樣寫有一個約定條件:頁面上需要有一個標(biāo)簽來顯示錯誤信息求晶,且錯誤信息的名稱必須是errInfo
焰雕。這也是約定大于配置這種思想的好處——方便快捷。
頁面上顯示錯誤信息的標(biāo)簽如下(Thymeleaf
語法):
<div class="alert alert-danger" role="alert" th:if="${errInfo != null}" th:text="${errInfo}"></div>
這樣出現(xiàn)參數(shù)校驗失敗的情況下芳杏,就可以很方便地處理并返回錯誤信息了矩屁。
實際效果:
注冊頁面:
文章類型管理頁面:
都是用了上面那一行代碼實現(xiàn)的顯示錯誤信息。