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啊?