SpringMVC數據校驗並通過國際化顯示錯誤信息
SpringMVC數據校驗
<mvc:annotation-driven/>
會默認裝配好一個LocalValidatorFactoryBean
,通過在處理方法的入參上標注@Valid 注解即可讓Spring MVC在完成數據綁定后執行數據校驗的工作。
首先,我們在實體類上標注JSR303校驗注解
public class User extends BaseDomain{
private int userId;
//將屬性值長度限定為2~10之間,但中文占兩個字符
@Length(min = 2, max = 10)
private String userName;
//匹配4~30個數字和字母以及下划線字符
@Pattern(regexp="w{4, 30}")
private String name;
@Length(min = 6)
private String password;
注解規則有多個可以參考下表
JSR 303注解
Hibernate Validator拓展注解
接着我們要讓springMVC使用這些規則對數據進行校驗,下面將使用LoginController控制器進行演示。
@Controller
public class LoginController {
@RequestMapping(value = "/login", produces="text/html;charset=UTF-8")
public String login(
@Valid @ModelAttribute("user") User user,
BindingResult result, HttpSession session){
if(result.hasErrors()){
return "forward:/loginPage";
}else{
session.setAttribute("user", user);
return "home";
}
}
在已經標注了JSR303注解的表單/命令對象前標注一個@Valid,Spring MVC框架在將請求數據綁定到該入參對象后,就會調用校驗框架根據注解聲明的校驗規則實施校驗。
在本例中我們對User對象標注@Valid注解。還有要記得標注@ModelAttribute,並顯示的指定其value值為user,在顯示錯誤信息時我們會用到它。
還有一個問題就是校驗所產生的校驗結果保存在什么地方呢,如何傳遞給請求處理方法?Spring MVC是通過對處理方法簽名的規約來保存校驗結果的:前一個表單/命令對象的校驗結果保存在其后的入參中,這個保存校驗結果的入參必須是BindingResult 或Errors類型,這兩個類都位於org.springframework.validation包中。
而且需校驗的表單/命令對象和其綁定結果對象或錯誤對象是成對出現的,它們之間不允許聲明其他的入參。
在頁面中顯示錯誤信息
由於表單/命令對象所對應的請求一般是從客戶端的網頁中傳送過來的,如果發生了錯誤,我們必須通過網頁顯示出錯誤,提示用戶更正錯誤。
<div class="login-container">
<form:form modelAttribute="user" action="login" method="post">
<div class="login-inner">
<div class="login-inner-item login-operaType">
<div class="sign-in active">登錄</div>
<div class="sign-up">注冊</div>
</div>
<div class="login-inner-item">
<input type="text" class="layui-input" placeholder="賬號" name="userName" value="${user.userName}">
<div class="message-error">
<form:errors path="userName" cssClass="message-error"/>
</div>
</div>
<div class="login-inner-item">
<input type="password" class="layui-input" placeholder="密碼" name="password" value="${user.password}">
<div class="message-error">
<form:errors path="password" cssClass="message-error"/>
</div>
</div>
<div class="login-inner-item message-error">
${errorMsg}
</div>
<div class="login-inner-link">
<a href="">忘記密碼</a>
</div>
<div class="login-inner-item .login-inner-submit">
<input type="submit" class="layui-input" value="登錄">
</div>
</div>
</form:form>
</div>
首先我們修改一下form標簽,並指定模型數據為user,與前面標注的@ModelAttribute一致,如下
<form:form modelAttribute="user" action="login" method="post">
...
</form:form>
然后使用<form:errors />
標簽來顯示錯誤信息,path用來指定實體類中的屬性,cssClass指定css樣式類
<form:errors path="userName" cssClass="message-error"/>
也可以使用如下標簽顯示所有的錯誤信息,但是不精確沒必要
<form:errors path="*"/>
接下來我們測試一下效果,如下就能顯示錯誤信息了
雖然能顯示錯誤信息,但是這些錯誤信息是springMVC默認的,在實際運用中我們希望使用更加人性化的設置來顯示錯誤信息。這時我們可以給校驗注解添加message屬性指定錯誤信息。
public class User extends BaseDomain{
private int userId;
@Length(min = 2, max = 10, message = "用戶名不正確,長度必須在2~10之間")
private String userName;
@Length(min = 6, message = "密碼長度不合法,長度必須大於6")
private String password;
這樣我們就能按照我們自己的想法去顯示不同的錯誤信息,如下
雖然這樣做成功了,能個性化的顯示錯誤信息,但是這使用硬編碼的方式依舊不推薦使用,我們希望在不修改源代碼的情況下來修改這些錯誤信息,這時就可以使用國際化的方式來顯示錯誤信息了。
通過國際化顯示錯誤信息
當一個屬性校驗失敗后,校驗框架默認會為該屬性生成4個消息代碼,這些代碼以校驗注解類名為前綴。例如User類中的password屬性標准了一個@Length注解,當該屬性值不滿足@pattern所定義的規則時,就會產生一下4個錯誤代碼:
Length.user.password
Length.password
Length.java.lang.String
Length
這里我們選擇最精確的一個Length.user.password
我們在類路徑下添加一個文件夾i18n
,用來存放國際化資源。然后再創建國際化資源,一個是message.properties,一個是message_zh_CN.properties,內容如下
接下來的工作是在springmvc-servlet中配置好這個國際化資源:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n.message"/>
</bean>
千萬要注意,需要給這個bean添加一個id,且其值只能是messageSource
,不然這個bean不會起作用。起初我配置好bean后,沒給他加上id,然后配置的國際化根本不起作用,這個問題困擾我好久,最后通過查閱資料得知:
對Spring容器啟動時的步驟進行剖析,①處的initMessageSource()方法所執行的工作就是初始化容器中的國際化信息資源:它根據反射機制從BeanDefinitionRegistry中找出名稱為“messageSource”且類型為org.springframework.context.MessageSource的Bean,將這個Bean定義的信息資源加載為容器級的國際化信息資源。
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
//①初始化消息源
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
然后看看MessageSource類圖結構
所以容器中存在ResourceBundleMessageSource這個bean,並且id為messageSource時,spring才會把這個Bean定義的信息資源加載為容器級的國際化信息資源
然后就可以把實體類中硬編碼的message屬性給去掉了,如下就是效果,和之前的一樣