1. 程式人生 > >Spring MVC+mybatis 專案入門:旅遊網(三)使用者註冊——控制反轉以及Hibernate Validator資料驗證

Spring MVC+mybatis 專案入門:旅遊網(三)使用者註冊——控制反轉以及Hibernate Validator資料驗證

註冊原理

        其實很簡單,前端頁面顯示一個表單,然後由dispatcher傳遞到controller,controller呼叫資料庫驗證,如果ok,那就寫入資料庫,同時返回註冊成功的檢視,否則可以返回註冊頁,或者是到一個錯誤頁。

依賴注入與控制反轉

        這裡提一下,在最早接觸servlet的時候,應該有老師會說,Java的POJO應該只有屬性與構造方法,除此之外對於每個屬性必須寫其對應的getter、setter方法。而這裡就是為了依賴注入。具體的理論可以百度,這裡就簡單說明一下構造注入與setter注入:

// 構造注入
public class Test(){
    private B b;

    public Test(B b){
        this.b = b;
    }
}

// setter注入
public class Test(){
    private B b;

    public void setB(B b){
        this.b = b;
    }
}

為什麼要使用依賴注入或者說控制反轉?(實際上,兩者是相同的,只是在不同的角度闡述了上述操作)這裡應該有專門的文章論述了。篇幅有限,這裡不再解答,但是推薦搞懂這兩者再繼續閱讀,畢竟這個非常核心。否則就是隻會用而不知道具體實現了。

        現在給出jsp程式碼與controller的程式碼以及User類的bean:

package me.iwts.bean;

import org.hibernate.validator.constraints.Email;
import javax.validation.constraints.Size;

public class User {
    private String account;
    private String passwd;
    private String phone;
    private String email;
    private String userName;

    public User(){ }
    public User(String account,String passwd,String phone,String email,String userName){
        this.account = account;
        this.passwd = passwd;
        this.email = email;
        this.phone = phone;
        this.userName = userName;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    public void setAccount(String account) {
        this.account = account;
    }
    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getEmail() {
        return email;
    }
    public String getAccount() {
        return account;
    }
    public String getPasswd() {
        return passwd;
    }
    public String getPhone() {
        return phone;
    }
    public String getUserName() {
        return userName;
    }
}

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <p>註冊測試</p>
    <form:form modelAttribute="user" action="register.action" method="post">
        賬號:<input type="text" name="account">
        <br />
        密碼:<input type="password" name="passwd">
        <br />
        手機號:<input type="text" name="phone">
        <br />
        郵箱:<input type="email" name="email">
        <br />
        使用者暱稱:<input type="text" name="userName">
        <br />
        <input type="submit" name="submit" value="註冊">
    </form:form>
</body>
</html>

這個<form:form>標籤是Spring MVC的標籤,請當做正常的<form>標籤,為什麼寫這個標籤?後面的優化部分會說到。

package me.iwts.controller;

import me.iwts.bean.User;
import me.iwts.mapper.UserMapper;
import me.iwts.tools.ViewTool;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.io.Reader;

@Controller
public class UserController {
    // 註冊
    @RequestMapping("register.action")
    public ModelAndView register(@ModelAttribute User user, Model model){
        
    }
}

具體程式碼我沒有寫,這裡僅僅演示了Spring MVC如何處理依賴注入的情況。

        可以看到,form表單對應了User類的一部分,然後就直接action給提交了,並沒有對錶單輸入的資料進行封裝。而在controller類裡面,我們在形參列表卻傳遞了一個User類。這個User類使用了註解@ModelAttribute。

        其實這裡在Spring MVC,完成了控制反轉或者說依賴注入的操作。我們表單仍然還是隻傳遞了一堆值,而dispatcher在獲取請求以後,就利用setter注入,幫助我們封裝好了整個User類,然後是將這個User物件給傳遞到register方法裡面的。而只要我們的形參列表聲明瞭需要這個物件,那麼Spring MVC就能夠給我們這個物件。這個過程,就是控制反轉。

        所以說,從controller的角度看,叫控制反轉,從Spring MVC的角度看,叫依賴注入。而不管怎樣,我們都能夠獲取到這個物件,並且這個物件已經被封裝成為了model,之後的操作就是資料持久化了(當然需要先進行驗證)。

Hibernate Validator後端資料校驗

        這裡也是需要大篇幅講解的部分。。。推薦百度搜一下,提醒一下,這裡坑略多,自己搜的時候學得會好一點,就是可能有很多錯,博主也是踩了很多坑。

        Spring是沒有資料校驗的。但是資料校驗是比較重要的一環。可能比較多的同學學的是JavaScript校驗,這個是在前端控制資料的正確性,例如格式問題。但是,如果某些不懷好意的同學惡意操作呢?例如直接使用http提交資料,這樣就能繞過前端直接給後端傳遞資料。當然安全問題遠遠沒有這麼低階,但是在現階段,這樣的問題應該被我們考慮,而更難的安全問題就以後在進行處理。所以,解決這個簡單的安全問題就是進行後端的資料校驗——無論你怎麼傳,只要我能在後端校驗,就能防止惡意傳遞資料。

        比較簡單的方法就是直接處理——我們已經將物件封裝好利用控制反轉給獲取到了,那麼我們就能獲取其各個屬性的值,然後直接一頓操作就行了。但是我們現在想要逼格高一點的,同時還想節省程式碼量,所以我們選擇利用其它技術來實現這個功能。

        上面也說了Spring是沒有資料校驗的。簡而言之,Java只提供了一些規範,說,只要你能實現這個規範,就能進行資料校驗了,而hibernate validator就是實現了這個規範。那麼我們就只用獲取其jar包,然後一頓呼叫,就能利用其來實現資料校驗的操作。hibernate validator是實現了兩套規範的,我們下面講的主要依據最新的規範,比較簡單,也更強大。

        首先,jar包自然是需要的。hibernate validator所必須的jar包是2個:hibernate-validator.jar和validation-api.jar。但是個人推薦多增加兩個,可以杜絕大部分錯誤:

但是如果還是有錯的話,就只能看log了,具體缺什麼jar包就去下載什麼jar包。這些jar包都可以直接百度下載,或者在我的專案裡面/lib/ext下查詢。而具體怎麼在IDE裡面新增就不多說了。

約束註解

        之後,我們需要對bean進行一次升級,就是添加註解。而這個註解,就是對某個屬性進行約束,規定這個屬性必須滿足怎麼樣的條件,否則就會返回錯誤。先看一下bean的程式碼:

package me.iwts.bean;

import org.hibernate.validator.constraints.Email;
import javax.validation.constraints.Size;

public class User {
    @Size(min = 6,max = 16,message = "賬號不能為空,位數要為6-16位")
    private String account;
    @Size(min = 6,max = 16,message = "密碼不能為空,位數要為6-14位")
    private String passwd;
    @Size(min = 11,max = 11,message = "手機不能為空,手機號碼格式錯誤")
    private String phone;
    @Email(message = "郵箱格式錯誤")
    private String email;
    @Size(min = 0,max = 10,message = "暱稱不能大於10位")
    private String userName;

    public User(){ }
    public User(String account,String passwd,String phone,String email,String userName){
        this.account = account;
        this.passwd = passwd;
        this.email = email;
        this.phone = phone;
        this.userName = userName;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    public void setAccount(String account) {
        this.account = account;
    }
    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getEmail() {
        return email;
    }
    public String getAccount() {
        return account;
    }
    public String getPasswd() {
        return passwd;
    }
    public String getPhone() {
        return phone;
    }
    public String getUserName() {
        return userName;
    }
}

可以看到,每個屬性上面對應的@Size、@Email等就是註解。不同的註解有不同的作用,這裡提供一些圖,是從以前的部落格上截的:

利用註解,就能比較方便地進行約束。

        現在只是聲明瞭約束,而如果違反這個約束會有什麼操作這個是在controller裡面執行的,但是我們需要告訴controller這裡違反了約束,也就是需要提醒資訊。可以看到,我們在註解裡面寫了message屬性,而裡面的內容就是我們自定義的錯誤資訊。其實不加也行,如果我們不想讓使用者看到這個資訊的話,預設情況下也會有錯誤資訊,不過是英文的。但是我們選擇讓使用者看到,這樣能提醒他們你寫錯了。怎麼讓他們看?這個我們留到最後說。

後端處理

        那麼現在,就看我們怎麼在controller裡面處理這個約束了,看一下controller裡面的程式碼:

package me.iwts.controller;

import me.iwts.bean.User;
import me.iwts.mapper.UserMapper;
import me.iwts.tools.ViewTool;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.io.Reader;

@Controller
public class UserController {
    // 註冊
    @RequestMapping("register.action")
    public ModelAndView register(@Valid @ModelAttribute User user, BindingResult bindingResult, Model model){
        if(bindingResult.hasErrors()){
            model.addAttribute("user",user);
            return new ModelAndView(ViewTool.REGISTER);
        }
    }
}

可以與上面程式碼進行一些比較,其實主要是引數部分有變化:

1.對於傳入的物件,需要用註解@Valid宣告。

2.增加一個BindingResult物件。

第一個操作,主要是宣告,在進行依賴注入的時候,需要對這個類的屬性進行資料驗證,而驗證方式就是根據其對應的註解。而BindingResult物件,就是在進行資料驗證的時候,如果有錯誤,就將其message給新增到BindingResult物件裡面。而呼叫其hasErrors()方法,就能判定是否是有錯誤的。

        而具體如何處理,這個就根據實際情況判定了。例如我們對於無所謂的資料,例如使用者暱稱。我們允許使用者不寫暱稱,但是我們看論壇的話,發現這個暱稱會預設是使用者名稱。這就是我們處理的結果了,如果發現有使用者暱稱為空,我們就將使用者名稱給賦值進去。

        當然,我們這裡的邏輯就是告訴使用者:你錯了,請重新輸入。所以可以看到,我們直接返回了一個檢視,同時將user物件封裝進model裡面,和檢視一起返回到註冊頁面,所以下面就是看前端如何處理了。

前端處理

        現在,我們將model返回到了前端,同時檢視也返回回來了,這裡先上一個完整未刪減的程式碼:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <p>註冊測試</p>
    <form:form modelAttribute="user" action="register.action" method="post">
        賬號:<input type="text" name="account" value=${requestScope.user.account}>
        <form:errors path="account"></form:errors>
        <span>${accountError}</span>
        <br />
        密碼:<input type="password" name="passwd">
        <form:errors path="passwd"></form:errors>
        <br />
        手機號:<input type="text" name="phone" value=${requestScope.user.phone}>
        <form:errors path="phone"></form:errors>
        <br />
        郵箱:<input type="email" name="email" value="${requestScope.user.email}">
        <form:errors path="email"></form:errors>
        <br />
        使用者暱稱:<input type="text" name="userName" value=${requestScope.user.userName}>
        <form:errors path="userName"></form:errors>
        <br />
        <input type="submit" name="submit" value="註冊">
    </form:form>
</body>
</html>

同樣,可以跟最早的jsp頁面比較,看多了點什麼東西。

        首先,類似於${requestScope.user.account}這樣的程式碼是EL表示式。這個是非常好用的,推薦大家先去看一下什麼是EL表示式,然後再回頭看這裡的程式碼。只能說,用EL表示式很爽。

        然後,下面就預設大家會一點EL表示式了。首先可以看到,多的一部分是value值。這個部分是完成了記憶功能。例如剛開始登錄檔單是什麼都沒有的,而我們註冊以後,如果有錯誤返回,會發現表單是我們上次提交的資訊,除了密碼。這裡就是利用value進行記憶功能,value的值就是EL表示式,而剛開始EL表示式是找不到user物件的,因為我們只有在model裡面將user返回,才有這個物件,所以EL表示式的結果是空。而如果第二次返回,那麼就有user物件了,從而能夠將上次輸入的結果給顯示在介面上。

        這個不是最重要的,重要的是下面的標籤:<form:errors>,這個標籤能夠顯示hibernate validator捕獲的錯誤資料。並且將其message給顯示出來。而path屬性就指定了,其顯示哪一個屬性出現的錯誤。請注意:想要使用這個標籤,那麼就必須使用<form:form>標籤,這也是我放棄<form>標籤的原因。

        所以,如果你想要讓使用者看到哪裡錯了,就需要在message屬性寫想讓使用者看到的資訊,如果不想,就可以使用預設message了。給一個效果圖吧:

下一章連結

未完待續——缺哥哥里什麼時候出黑暗劍22啊?