一、Spring MVC 驗證 JSR 303 是ajvaEE6 中的一項子規範 ,叫 Bean Validation 用於對javaBean中的欄位進行校驗。 官方的參考實現是: Hibernate Validator ,此實現和 Hibernate ORM 沒有任何關係 //http://hib ...
一、Spring MVC 驗證
JSR 303 是ajvaEE6 中的一項子規範 ,叫 Bean Validation 用於對javaBean中的欄位進行校驗。
官方的參考實現是: Hibernate Validator ,此實現和 Hibernate ORM 沒有任何關係 //http://hibernate.org/validator
Spring 3.x 也大力支持 JSR 303,使用的是 Hibernate Validator
1) 導包
4.3.1.Final 版本為例
// jboss-logging-3.1.0.CR2.jar
hibernate-validator-4.3.1.Final.jar
jboss-logging-3.1.0.jar、
validation-api-1.0.0.GA.jar
2) 配置文件
<mvc:annotation-driven validator="validator" /> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> <property name="validationMessageSource" ref="validatemessageSource"/> //不設置則預設為classpath下的 ValidationMessages.properties </bean> <bean id="validatemessageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:validatemessages"/> //配置驗證信息資源文件的路徑 <property name="fileEncodings" value="utf-8"/> <property name="cacheSeconds" value="120"/> //緩存時間 </bean>
3) 創建校驗信息用的資源文件,本例在類路徑(src或config)下直接創建即可
validatemessages.properties 內容
name.not.empty=名字不能為空
age.not.inrange=年齡要在合適的範圍內
email.not.correct=郵箱格式不正確
email.not.empty=郵箱不能為空
4) 在實體類上,添加校驗
public class UserInfo { private int id; @NotNull(message="用戶名不能為 null") @NotEmpty(message="{name.not.empty}") @Pattern(regexp="13\\d{9}",message="用戶名是按手機驗證的格式不對") private String userName; @Size(min=3,max=6,message="{password.not.inrange}") private String password; @NotEmpty(message="{email.not.empty}") @Email(message="{email.not.correct}") private String note; private String aihao; //也可以將規則添在get 方法上
5) 控制器
//在添加用戶這個請求提交上來的時候 @RequestMapping(value="/addUser" ,method=RequestMethod.POST) public String addUser(Model model, @Valid UserInfo user,BindingResult bResult){ if(bResult.hasErrors()){ List<ObjectError> errorList= bResult.getAllErrors(); for(ObjectError e:errorList){ System.out.println(e.getDefaultMessage()); } model.addAttribute("errorList",errorList); //把錯誤信息傳到前臺 return "user_add"; } else{ return "success"; } }
說明: BindingResult 是Errors的子類
@Valid 和 BindingResult 的位置不要寫反
@Valid 寫成了 @Validated 實測也可以
6) 前端頁面
<c:forEach var="e" items="${errorList }"> ${e.defaultMessage} </c:forEach> <form action="${pageContext.request.contextPath }/addUser" method="post"> 賬號: <input type="text" name="userName" /> 密碼: <input type="text" name="password" /> 備註: <input type="text" name="note" /> <input type=submit value="提交" /> </form>
例子 如何詳細的顯示出錯信息
新建頁面 user_add_validate.jsp
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="x" %> <x:form method="post" action="${pageContext.request.contextPath }/addUser"> 賬號:<x:input path="userName" /> <x:errors path="userName" /><br> 密碼:<x:input path="password" /> <x:errors path="password" /> <br> 備註:<x:input path="note" /><x:errors path="note" /> <br> <input type=submit value="提交" /> </x:form>
問題:
1) 如果直接訪問 user_add_validate.jsp 出現錯誤
//java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?
原因是Spring環境沒初始化
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
2) 需要指定 modelAttribute="user" 這個屬性
<x:form method="post" action="${pageContext.request.contextPath }/addUser" modelAttribute="user" > //上面也可以寫成 commandName="user"
3) 不能直接訪問 user_add_validate.jsp 要這樣:
@RequestMapping(value="/addUser",method=RequestMethod.GET) public String addUser(@ModelAttribute("user") UserInfo user){ //@ModelAttribute("user") 必加 return "user_add_validate"; }
4) 添加用戶的方法要如下
@RequestMapping(value="/addUser" ,method=RequestMethod.POST) public String addUser( @ModelAttribute("user") @Valid UserInfo user,BindingResult bResult){ if(bResult.hasErrors()){ return "user_add_validate"; } else{ return "success"; } }
附加:
分組校驗
問題: 當一個pojo類被多個業務方法使用,但它的驗證規則不同,如何處理?
可以採用分組校驗
1)定義分組 (其實就是定義介面,空介面就可以,一個介面就是一個分組)
public interface IVG1, IVG2
2)在pojo類上指定校驗屬於哪個分組
public class UserInfo( @NotEmpty(message="用戶名不能為 null",groups={IVG1.class}) @Size(min=3,max=6,message="{password.not.inrange}",groups={IVG1.class,IVG2.class}) private String password; ... }
3)在業務方法上指定驗證的時候使用哪個分組
@RequestMapping(value="/addUser" ,method=RequestMethod.POST) public String addUser( @ModelAttribute("user") @Validated(value=IVG1.class) UserInfo user,BindingResult bResult){ if(bResult.hasErrors()){ return "user_add_validate"; } else{ return "success"; } } //註意,用的是 @Validated 註解 不是 @Valid
二、Spring MVC 數據的回顯
(其實上例就是帶數據回顯的)
pojo類型的數據回顯
@RequestMapping(value="/addUser" ,method=RequestMethod.POST) public String addUser( @ModelAttribute("user") UserInfo user){ .... }
頁面的 from上也要加 modelAttribute 或 commondName
<x:form action="${pageContext.request.contextPath }/test" method="post" modelAttribute="user"> <x:input path="userName" /> //註意,要回顯示的數據必須用 這樣的標簽 <x:input path="password" /> </x:form>
對於簡單類型
用 model.addAttribute(...) 即可
三、Spring MVC 異常處理
SpringMVC 提供全局異常處理器進行統一的異常處理
1) 在controller(Action)內部處理異常
-- login.jsp
<form action="user/login" method="post" > 賬號:<input type="text" name="userName" /> 密碼:<input type="text" name="password" /> <input type="submit" />
-- 自定義異常類 //不定義也可以
public class MyException extends Exception{ private String msg; public MyException(String msg){ super(msg); this.msg=msg; } public void writeLog(){ System.out.println("---異常信息"+this.msg+"已經記錄-"); } }
-- action 中的處理
@RequestMapping(value="/login") public String login(HttpSession session,String userName,String password) throws MyException { UserInfo user=new UserDao().getLoginUser(userName, password); if(user==null){ throw new MyException("用戶登錄失敗"); //如果登錄失敗,拋出異常 } session.setAttribute("user", user); return "success"; } @ExceptionHandler(value={MyException.class}) //這裡是進行異常處理的地方 public String exectionProcessAAA(MyException e,HttpServletRequest request){ request.setAttribute("e_msg", e.getMessage()); e.writeLog(); return "error" ; //全局的一個異常處理頁面 }
2) 全局異常處理
--在配置文件中加入配置
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="cat.beans.MyException">error_process_1</prop> //可以看到,可以為每種異常指定一個result視圖 <prop key="java.lang.Exception">error_process_2</prop> <prop key="java.lang.Throwable">error_process_3</prop> </props> </property> </bean>
在異常處理頁面
${exception.message} //這樣即可取出異常信息
四、Spring MVC 文件上傳
1) 單文件上傳
== 在配置文件中
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" > <property name="defaultEncoding" value="UTF-8" /> //這裡面的這些屬性可以不用,一個都不寫也不會出錯 <property name="maxUploadSize" value="5400000" /> <property name="uploadTempDir" value="uploadTempDir" /> </bean>
== 頁面
<form method="post" action="${pageContext.request.contextPath }/addUserWithImg" enctype="multipart/form-data" > <input name="userName" /> <input name="password"/> <input name="note"/> <input type="file" name=upfile /> //註意,這個名字 <input type="submit" > </form>
== 控制層
@RequestMapping(value="/addUserWithImg",method=RequestMethod.POST) public String addUserWithImg(UserInfo user,MultipartFile upfile /*註意這個名字要和表單元素同名*/ ,HttpServletRequest request )
throws IllegalStateException, IOException{ System.out.println(upfile); System.out.println("contentType:"+upfile.getContentType()); //image/pjepg System.out.println("name:"+upfile.getName()); //photo System.out.println("getOriginalFilename:"+upfile.getOriginalFilename()); //zhangsan.jpg System.out.println("getSize:"+upfile.getSize()); new UserDao().addUser(user); String path=request.getServletContext().getRealPath("/upload_files"); File targetFile=new File(path, upfile.getOriginalFilename()); upfile.transferTo(targetFile); return "success"; }
2) 多文件上傳
<form method="post" action="${pageContext.request.contextPath }/addUserWithMultImg" enctype="multipart/form-data" > <input name="userName" /> <input name="password"/> <input name="note"/> <input type="file" name=upfiles /> <input type="file" name=upfiles /> <input type="file" name=upfiles /> <input type="file" name=upfiles /> <input type="submit" > </form>
//註意要加上 @RequestParam public String ddUserWithMultImg(UserInfo user,@RequestParam("upfiles") MultipartFile [] upfiles ,HttpServletRequest request )
throws IllegalStateException, IOException{ new UserDao().addUser(user); String path=request.getServletContext().getRealPath("/upload_files"); for(MultipartFile f:upfiles){ if(!f.isEmpty()){ File targetFile=new File(path, f.getOriginalFilename()); f.transferTo(targetFile); } } return "success"; }
五、Spring MVC 資源文件的引入
在配置文件中:
<mvc:resources location="/images/" mapping="/images/**" /> <mvc:resources location="/css/" mapping="css/**" /> //也可以放在一個文件夾中,統一的引入,如 <mvc:resources location="/resource/" mapping="resource/**" />
六、Spring MVC 關於json數據處理
1) 傳統方式的 ajax訪問和響應
//請求發起頁面 ajax_test.jsp $(function(){ $("#btn1").click(function(){ $.ajax({ url:"testAjax", type:"post", data:{userName:'admin',password:'123'}, async:false, cache:false, dataType:"json", success:function(data){ //alert(data); alert(data.id); alert(data.note); } }); }); }); @RequestMapping("/testAjax") public void testAjax(HttpServletRequest request,HttpServletResponse response) throws IOException{ String userName=request.getParameter("userName"); String password=request.getParameter("password"); System.out.println(userName+":"+password); response.setContentType("text/html;charset=utf-8"); //response.getWriter().print("這是普通數據"); String jsonStr="{\"note\":\"這是備註\",\"id\":\"90\"}"; response.getWriter().print(jsonStr); }
附註:
@RequestBody 用來處理application/json 類型的請求內容
使用 @ResponseBody 的方式返回json 格式的數據
要導入包 spring mvc 4.1.4
jackson-annotations-2.4.4.jar、
jackson-core-2.4.4.jar、
jackson-databind-2.4.4.jar。
附 3.1 用的是
jackson-core-asl-1.9.11.jar
jackson-mapper-asl-1.9.11.jar
//例一 從服務端查詢出一個userInfo對象返回 //服務端 @RequestMapping("/testAjax2") @ResponseBody //這裡用 @ResponseBody 註解聲明返回json數據 public UserInfo testAjax2(UserInfo u) { UserInfo user=new UserDao().getLoginUser(u.getUserName(), u.getPassword()); return user; } //客戶端 $.ajax({ url:"testAjax2", type:"post", data:{userName:'aaa',password:'aaa'}, //在服務端,用的是pojo類型的對象接收的參數 async:false, cache:false, dataType:"json", success:function(data){ alert(data.id); alert(data.note); } });
//例二 從服務端查詢一組對象返回 @RequestMapping("/testAjax3") @ResponseBody public List<UserInfo> testAjax2() { List<UserInfo> userList=new UserDao().getAllUser(); return userList; } $("#btn3").click(function(){ $.ajax({ url:"testAjax3", type:"post", async:false, cache:false, dataType:"json", success:function(userList){ //從服務端返回的是一組對象 $.each(userList,function(key,user){ $("#div1").append(user.id +"-"+user.userName+"-"+user.password+"<br />"); }); } }); });
七、Spring MVC中 RESTful 風格
REST這個詞,是Roy Thomas Fielding在他2000年的博士論文中提出的,HTTP協議(1.0版和1.1版)的主要設計者、Apache伺服器軟體的作者之一、Apache基金會的第一任主席。長期以來,軟體研究主要關註軟體設計的分類、設計方法的演化,很少客觀地評估不同的設計選擇對系統行為的影響。而相反地,網路研究主要關註系統之間通信行為的細節、如何改進特定通信機制的表現,常常忽視了一個事實,那就是改變應用程式的互動風格比改變互動協議,對整體表現有更大的影響。我這篇文章的寫作目的,就是想在符合架構原理的前提下,理解和評估以網路為基礎的應用軟體的架構設計,得到一個功能強、性能好、適宜通信的架構
REST,即Representational State Transfer的縮寫。我們對這個片語的翻譯是"表現層狀態轉化"。
如果一個架構符合REST原則,就稱它為RESTful架構。關於 Representational State Transfer (表現層狀態轉化) 的理解。
1)REST的名稱"表現層狀態轉化"中,省略了主語。"表現層"其實指的是"資源"(Resources)的"表現層"。
網路上的一個具體信息,可以用一個URI(統一資源定位符)指向它
2) Representation 表現層
"資源"是一種信息實體,它可以有多種外在表現形式。我們把"資源"具體呈現出來的形式,叫做它的"表現層"(Representation)。
比如,文本可以用txt格式表現,也可以用HTML格式、XML格式
URI只代表資源的實體,不代表它的形式。嚴格地說,有些網址最後的".html"尾碼名是不必要的 因為這個尾碼名錶示格式,屬於 "表現層" 範疇,而URI應該只代表"資源"的位置。它的具體表現形式,應該在HTTP請求的頭信息中用Accept和Content-Type欄位指定,這兩個欄位才是對"表現層"的描述。
3) State Transfer 狀態轉化
HTTP協議,是一個無狀態協議。這意味著,所有的狀態都保存在伺服器端。因此,如果客戶端想要操作伺服器,必須通過某種手段,讓伺服器端發生"狀態轉化"(State Transfer)。而這種轉化是建立在表現層之上的,所以就是"表現層狀態轉化"。客戶端用到的手段,只能是HTTP協議。具體來說,就是HTTP協議裡面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:
GET用來獲取資源,POST用來新建資源(也可以用於更新資源),PUT用來更新資源,DELETE用來刪除資源。
綜合上面的解釋,我們總結一下什麼是RESTful架構:
(1)每一個URI代表一種資源;
(2)客戶端和伺服器之間,傳遞這種資源的某種表現層;
(3)客戶端通過四個HTTP動詞,對伺服器端資源進行操作,實現"表現層狀態轉化"。
http://localhost:8080/myweb/userAction_show.action?id=5
rest 風格
http://www.douban.com/photos/album/49432287/ 這是豆瓣上的url rest風格
查詢用戶
http://localhost:8080/myweb/user/5
刪除用戶
http://localhost:8080/myweb/user/5/delete
//例子
@RequestMapping("/get_user/{id}/{flag}") public String getUser(@PathVariable("id") int idAAA, @PathVariable("flag") String flagAAA){ System.out.println(idAAA +":"+flagAAA); return "success"; }
請求的url
http://localhost:8080/springmvc-03/get_user/10/search
如果不傳 PathVariable 指定的參數,會報404
八、Spring MVC 攔截器
SpringMVC 中的Interceptor 攔截器也是相當重要和相當有用的,它的主要作用是攔截用戶的請求併進行相應的處理。比如通過它來進行許可權驗證,或者是來判斷用戶是否登陸,或者是像12306 那樣子判斷當前時間是否是購票時間。
方式一: 用實現 HandlerInterceptor 介面的方式
方式二: 繼承抽象類HandlerInterceptorAdapter (Spring 提供的類,它實現了 HandlerInterceptor)
方式三: 實現WebRequestInterceptor 介面或繼承實現了該介面的類
例子:
1) 攔截器聲明
public class MyInterceptor implements HandlerInterceptor { /*==在Controller 方法被調用之前執行,一般用來進行身份驗證,授權等 == SpringMVC中的Interceptor 攔截器是鏈式的,可以同時存在多個Interceptor,並根據聲明的前後順序依次執行 == 而且所有的Interceptor中的 preHandle 方法都會在 Controller方法調用之前調用 == SpringMVC的這種Interceptor鏈式結構也是可以進行中斷的,preHandle的返回值為false的時候整個請求就結束了 */ public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { System.out.println("preHandle"); return true; } /* 一般在處理公用的模型數據,統一指定視圖等操作的時候用 == 在preHandle方法返回值為true的時候才會執行。 == 它的執行時間是在是在Controller的方法調用之後,DispatcherServlet進行視圖的渲染之前執行 == 可以對ModelAndView進行操作。 == 這個方法的鏈式結構跟正常訪問的方向是相反的,也就是說先聲明的Interceptor攔截器該方法反而會後調用,這跟Struts2裡面的攔截器的執行過程有點像 */ public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); } /* == 在preHandle方法返回值為true的時候才會執行。 == 該方法將在整個請求完成之後,也就是DispatcherServlet渲染了視圖執行, == 這個方法的主要作用是用於清理資源的,統一的日誌及異常處理等 */ public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { System.out.println("afterCompletion"); } }
2) 在配置文件中加入配置
<mvc:interceptors> //如果有 多個攔截器,順序執行 <mvc:interceptor> // 如果不配置或/*,將攔截所有的Controller <mvc:mapping path="/searchall_forupdate" /> path 配置攔的是請求的url //<mvc:mapping path="/**" /> 所有的url包擴子url <bean class="cat.interceptor.MyInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
//例子,驗證用戶是否登錄 public class MySessionInterceptor implements HandlerInterceptor { public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { } public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object handler, ModelAndView arg3) throws Exception {} public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj) throws Exception { String url=request.getRequestURI(); System.out.println(url); if(url.contains("login")){ //如果用戶發的是登錄的請求,則直接放行 return true; } else{ UserInfo user=(UserInfo)request.getSession().getAttribute("user"); if(user!=null){ return true; } else{ request.setAttribute("msg", "請登錄"); request.getRequestDispatcher("login.jsp").forward(request, response); return false; } } }
上面的攔截器的配置文件
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="cat.interceptor.MyInterceptor"></bean> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="cat.interceptor.MySessionInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>