2020-01-19 從零開始用Spring Boot開發(fā)一個個人網(wǎng)站(7)參數(shù)合法性校驗與全局異常處理

前言

參數(shù)校驗其實是開發(fā)中一個很重要的點,而做參數(shù)校驗的方法也是千差萬別昭娩。

當(dāng)然,作為開發(fā)者完域,我們肯定希望參數(shù)校驗的過程越簡單越好,不到萬不得已肯定是不會一個一個手寫參數(shù)校驗的瘩将。

參數(shù)校驗失敗也是需要給出提示的吟税,而參數(shù)校驗失敗的提示大多都比較相似,過程也很像姿现,如果有全局處理方法肠仪,肯定會大大減小工作量和出現(xiàn)bug的可能。

關(guān)于參數(shù)校驗的需求以及解決方案

對于參數(shù)校驗备典,我們可以提出以下需求:

  1. 參數(shù)校驗最好可以自動化進(jìn)行异旧,沒有特殊情況不需要手寫參數(shù)校驗。
  2. 參數(shù)校驗失敗的異常需要統(tǒng)一的處理方法提佣。

解決方案也很簡單:

  1. 自動化參數(shù)校驗可以使用java.validation包下的一些注解來進(jìn)行泽艘,這樣既可靠欲险,又不會太麻煩。
  2. 參數(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)的顯示錯誤信息。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末爵赵,一起剝皮案震驚了整個濱河市吝秕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌空幻,老刑警劉巖烁峭,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡约郁,警方通過查閱死者的電腦和手機(jī)缩挑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鬓梅,“玉大人供置,你說我怎么就攤上這事〖喊梗” “怎么了士袄?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谎僻。 經(jīng)常有香客問我,道長寓辱,這世上最難降的妖魔是什么艘绍? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮秫筏,結(jié)果婚禮上诱鞠,老公的妹妹穿的比我還像新娘。我一直安慰自己这敬,他們只是感情好航夺,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著崔涂,像睡著了一般阳掐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冷蚂,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天缭保,我揣著相機(jī)與錄音,去河邊找鬼蝙茶。 笑死艺骂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隆夯。 我是一名探鬼主播钳恕,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蹄衷!你這毒婦竟也來了忧额?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤宦芦,失蹤者是張志新(化名)和其女友劉穎宙址,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體调卑,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡抡砂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年大咱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片注益。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡碴巾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丑搔,到底是詐尸還是另有隱情厦瓢,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布啤月,位于F島的核電站煮仇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谎仲。R本人自食惡果不足惜浙垫,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望郑诺。 院中可真熱鬧夹姥,春花似錦、人聲如沸辙诞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽飞涂。三九已至旦部,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間封拧,已是汗流浹背志鹃。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留泽西,地道東北人曹铃。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像捧杉,于是被迫代替她去往敵國和親陕见。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容