前言 本博文主要講解的知識點如下: 校驗器 統一處理異常 RESTful 攔截器 Validation 在我們的Struts2中,我們是繼承ActionSupport來實現校驗的...它有兩種方式來實現校驗的功能 手寫代碼 XML配置 這兩種方式也是可以特定處理方法或者整個Action的 而Spri ...
前言
本博文主要講解的知識點如下:
- 校驗器
- 統一處理異常
- RESTful
- 攔截器
Validation
在我們的Struts2中,我們是繼承ActionSupport來實現校驗的...它有兩種方式來實現校驗的功能
- 手寫代碼
- XML配置
- 這兩種方式也是可以特定處理方法或者整個Action的
而SpringMVC使用JSR-303(javaEE6規範的一部分)校驗規範,springmvc使用的是Hibernate Validator(和Hibernate的ORM無關)
快速入門
導入jar包
配置校驗器
<!-- 校驗器 -->
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- 校驗器 -->
<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
<!-- 指定校驗使用的資源文件,如果不指定則預設使用classpath下的ValidationMessages.properties -->
<property name="validationMessageSource" ref="messageSource" />
</bean>
錯誤信息的校驗文件配置
<!-- 校驗錯誤信息配置文件 -->
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!-- 資源文件名 -->
<property name="basenames">
<list>
<value>classpath:CustomValidationMessages</value>
</list>
</property>
<!-- 資源文件編碼格式 -->
<property name="fileEncodings" value="utf-8" />
<!-- 對資源文件內容緩存時間,單位秒 -->
<property name="cacheSeconds" value="120" />
</bean>
添加到自定義參數綁定的WebBindingInitializer中
<!-- 自定義webBinder -->
<bean id="customBinder"
class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
<!-- 配置validator -->
<property name="validator" ref="validator" />
</bean>
最終添加到適配器中
<!-- 註解適配器 -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<!-- 在webBindingInitializer中註入自定義屬性編輯器、自定義轉換器 -->
<property name="webBindingInitializer" ref="customBinder"></property>
</bean>
創建CustomValidationMessages配置文件
定義規則
package entity;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
public class Items {
private Integer id;
//商品名稱的長度請限制在1到30個字元
@Size(min=1,max=30,message="{items.name.length.error}")
private String name;
private Float price;
private String pic;
//請輸入商品生產日期
@NotNull(message="{items.createtime.is.notnull}")
private Date createtime;
private String detail;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
public String getPic() {
return pic;
}
public void setPic(String pic) {
this.pic = pic == null ? null : pic.trim();
}
public Date getCreatetime() {
return createtime;
}
public void setCreatetime(Date createtime) {
this.createtime = createtime;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail == null ? null : detail.trim();
}
}
測試:
<%--
Created by IntelliJ IDEA.
User: ozc
Date: 2017/8/11
Time: 9:56
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>測試文件上傳</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/validation.action" method="post" >
名稱:<input type="text" name="name">
日期:<input type="text" name="createtime">
<input type="submit" value="submit">
</form>
</body>
</html>
Controller需要在校驗的參數上添加@Validation註解...拿到BindingResult對象...
@RequestMapping("/validation")
public void validation(@Validated Items items, BindingResult bindingResult) {
List<ObjectError> allErrors = bindingResult.getAllErrors();
for (ObjectError allError : allErrors) {
System.out.println(allError.getDefaultMessage());
}
}
由於我在測試的時候,已經把日期轉換器關掉了,因此提示了字元串不能轉換成日期,但是名稱的校驗已經是出來了...
分組校驗
分組校驗其實就是為了我們的校驗更加靈活,有的時候,我們並不需要把我們當前配置的屬性都進行校驗,而需要的是當前的方法僅僅校驗某些的屬性。那麼此時,我們就可以用到分組校驗了...
步驟:
- 定義分組的介面【主要是標識】
- 定於校驗規則屬於哪一各組
- 在Controller方法中定義使用校驗分組
統一異常處理
在我們之前SSH,使用Struts2的時候也配置過統一處理異常...
當時候是這麼乾的:
- 在service層中自定義異常
- 在action層也自定義異常
- 對於Dao層的異常我們先不管【因為我們管不著,dao層的異常太致命了】
- service層拋出異常,Action把service層的異常接住,通過service拋出的異常來判斷是否讓請求通過
- 如果不通過,那麼接著拋出Action異常
- 在Struts的配置文件中定義全局視圖,頁面顯示錯誤信息
詳情可看:http://blog.csdn.net/hon_3y/article/details/72772559
那麼我們這次的統一處理異常的方案是什麼呢????
我們知道Java中的異常可以分為兩類
- 編譯時期異常
- 運行期異常
對於運行期異常我們是無法掌控的,只能通過代碼質量、在系統測試時詳細測試等排除運行時異常
而對於編譯時期的異常,我們可以在代碼手動處理異常可以try/catch捕獲,可以向上拋出。
我們可以換個思路,自定義一個模塊化的異常信息,比如:商品類別的異常
public class CustomException extends Exception {
//異常信息
private String message;
public CustomException(String message){
super(message);
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
我們在查看Spring源碼的時候發現:前端控制器DispatcherServlet在進行HandlerMapping、調用HandlerAdapter執行Handler過程中,如果遇到異常,在系統中自定義統一的異常處理器,寫系統自己的異常處理代碼。。
我們也可以學著點,定義一個統一的處理器類來處理異常...
定義統一異常處理器類
public class CustomExceptionResolver implements HandlerExceptionResolver {
//前端控制器DispatcherServlet在進行HandlerMapping、調用HandlerAdapter執行Handler過程中,如果遇到異常就會執行此方法
//handler最終要執行的Handler,它的真實身份是HandlerMethod
//Exception ex就是接收到異常信息
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
//輸出異常
ex.printStackTrace();
//統一異常處理代碼
//針對系統自定義的CustomException異常,就可以直接從異常類中獲取異常信息,將異常處理在錯誤頁面展示
//異常信息
String message = null;
CustomException customException = null;
//如果ex是系統 自定義的異常,直接取出異常信息
if(ex instanceof CustomException){
customException = (CustomException)ex;
}else{
//針對非CustomException異常,對這類重新構造成一個CustomException,異常信息為“未知錯誤”
customException = new CustomException("未知錯誤");
}
//錯誤 信息
message = customException.getMessage();
request.setAttribute("message", message);
try {
//轉向到錯誤 頁面
request.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(request, response);
} catch (ServletException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new ModelAndView();
}
}
配置統一異常處理器
<!-- 定義統一異常處理器 -->
<bean class="cn.itcast.ssm.exception.CustomExceptionResolver"></bean>
RESTful支持
我們在學習webservice的時候可能就聽過RESTful這麼一個名詞,當時候與SOAP進行對比的...那麼RESTful究竟是什麼東東呢???
RESTful(Representational State Transfer)軟體開發理念,RESTful對http進行非常好的詮釋。
如果一個架構支持RESTful,那麼就稱它為RESTful架構...
以下的文章供我們瞭解:
http://www.ruanyifeng.com/blog/2011/09/restful
綜合上面的解釋,我們總結一下什麼是RESTful架構:
- (1)每一個URI代表一種資源;
- (2)客戶端和伺服器之間,傳遞這種資源的某種表現層;
- (3)客戶端通過四個HTTP動詞,對伺服器端資源進行操作,實現"表現層狀態轉化"。
關於RESTful冪等性的理解:http://www.oschina.net/translate/put-or-post
簡單來說,如果對象在請求的過程中會發生變化(以Java為例子,屬性被修改了),那麼此是非冪等的。多次重覆請求,結果還是不變的話,那麼就是冪等的。
PUT用於冪等請求,因此在更新的時候把所有的屬性都寫完整,那麼多次請求後,我們其他屬性是不會變的
在上邊的文章中,冪等被翻譯成“狀態統一性”。這就更好地理解了。
其實一般的架構並不能完全支持RESTful的,因此,只要我們的系統支持RESTful的某些功能,我們一般就稱作為支持RESTful架構...
url的RESTful實現
非RESTful的http的url:http://localhost:8080/items/editItems.action?id=1&....
RESTful的url是簡潔的:http:// localhost:8080/items/editItems/1
更改DispatcherServlet的配置
從上面我們可以發現,url並沒有.action尾碼的,因此我們要修改核心分配器的配置
<!-- restful的配置 -->
<servlet>
<servlet-name>springmvc_rest</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 載入springmvc配置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- 配置文件的地址 如果不配置contextConfigLocation, 預設查找的配置文件名稱classpath下的:servlet名稱+"-serlvet.xml"即:springmvc-serlvet.xml -->
<param-value>classpath:spring/springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc_rest</servlet-name>
<!-- rest方式配置為/ -->
<url-pattern>/</url-pattern>
</servlet-mapping>
在Controller上使用PathVariable註解來綁定對應的參數
//根據商品id查看商品信息rest介面
//@RequestMapping中指定restful方式的url中的參數,參數需要用{}包起來
//@PathVariable將url中的{}包起參數和形參進行綁定
@RequestMapping("/viewItems/{id}")
public @ResponseBody ItemsCustom viewItems(@PathVariable("id") Integer id) throws Exception{
//調用 service查詢商品信息
ItemsCustom itemsCustom = itemsService.findItemsById(id);
return itemsCustom;
}
當DispatcherServlet攔截/開頭的所有請求,對靜態資源的訪問就報錯:我們需要配置對靜態資源的解析
<!-- 靜態資源 解析 -->
<mvc:resources location="/js/" mapping="/js/**" />
<mvc:resources location="/img/" mapping="/img/**" />
/**
就表示不管有多少層,都對其進行解析,/*
代表的是當前層的所有資源..
SpringMVC攔截器
在Struts2中攔截器就是我們當時的核心,原來在SpringMVC中也是有攔截器的
用戶請求到DispatherServlet中,DispatherServlet調用HandlerMapping查找Handler,HandlerMapping返回一個攔截的鏈兒(多個攔截),springmvc中的攔截器是通過HandlerMapping發起的。
實現攔截器的介面:
public class HandlerInterceptor1 implements HandlerInterceptor {
//在執行handler之前來執行的
//用於用戶認證校驗、用戶許可權校驗
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("HandlerInterceptor1...preHandle");
//如果返回false表示攔截不繼續執行handler,如果返回true表示放行
return false;
}
//在執行handler返回modelAndView之前來執行
//如果需要向頁面提供一些公用 的數據或配置一些視圖信息,使用此方法實現 從modelAndView入手
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("HandlerInterceptor1...postHandle");
}
//執行handler之後執行此方法
//作系統 統一異常處理,進行方法執行性能監控,在preHandle中設置一個時間點,在afterCompletion設置一個時間,兩個時間點的差就是執行時長
//實現 系統 統一日誌記錄
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("HandlerInterceptor1...afterCompletion");
}
}
配置攔截器
<!--攔截器 -->
<mvc:interceptors>
<!--多個攔截器,順序執行 -->
<!-- <mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="cn.itcast.ssm.controller.interceptor.HandlerInterceptor1"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="cn.itcast.ssm.controller.interceptor.HandlerInterceptor2"></bean>
</mvc:interceptor> -->
<mvc:interceptor>
<!-- /**可以攔截路徑不管多少層 -->
<mvc:mapping path="/**" />
<bean class="cn.itcast.ssm.controller.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
測試執行順序
如果兩個攔截器都放行
測試結果:
HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle
HandlerInterceptor2...postHandle
HandlerInterceptor1...postHandle
HandlerInterceptor2...afterCompletion
HandlerInterceptor1...afterCompletion
總結:
執行preHandle是順序執行。
執行postHandle、afterCompletion是倒序執行
1 號放行和2號不放行
測試結果:
HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle
HandlerInterceptor1...afterCompletion
總結:
如果preHandle不放行,postHandle、afterCompletion都不執行。
只要有一個攔截器不放行,controller不能執行完成
1 號不放行和2號不放行
測試結果:
HandlerInterceptor1...preHandle
總結:
只有前邊的攔截器preHandle方法放行,下邊的攔截器的preHandle才執行。
日誌攔截器或異常攔截器要求
- 將日誌攔截器或異常攔截器放在攔截器鏈兒中第一個位置,且preHandle方法放行
攔截器應用-身份認證
攔截器攔截
public class LoginInterceptor implements HandlerInterceptor {
//在執行handler之前來執行的
//用於用戶認證校驗、用戶許可權校驗
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//得到請求的url
String url = request.getRequestURI();
//判斷是否是公開 地址
//實際開發中需要公開 地址配置在配置文件中
//...
if(url.indexOf("login.action")>=0){
//如果是公開 地址則放行
return true;
}
//判斷用戶身份在session中是否存在
HttpSession session = request.getSession();
String usercode = (String) session.getAttribute("usercode");
//如果用戶身份在session中存在放行
if(usercode!=null){
return true;
}
//執行到這裡攔截,跳轉到登陸頁面,用戶進行身份認證
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
//如果返回false表示攔截不繼續執行handler,如果返回true表示放行
return false;
}
//在執行handler返回modelAndView之前來執行
//如果需要向頁面提供一些公用 的數據或配置一些視圖信息,使用此方法實現 從modelAndView入手
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("HandlerInterceptor1...postHandle");
}
//執行handler之後執行此方法
//作系統 統一異常處理,進行方法執行性能監控,在preHandle中設置一個時間點,在afterCompletion設置一個時間,兩個時間點的差就是執行時長
//實現 系統 統一日誌記錄
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("HandlerInterceptor1...afterCompletion");
}
}
Controller
@Controller
public class LoginController {
//用戶登陸提交方法
@RequestMapping("/login")
public String login(HttpSession session, String usercode,String password)throws Exception{
//調用service校驗用戶賬號和密碼的正確性
//..
//如果service校驗通過,將用戶身份記錄到session
session.setAttribute("usercode", usercode);
//重定向到商品查詢頁面
return "redirect:/items/queryItems.action";
}
//用戶退出
@RequestMapping("/logout")
public String logout(HttpSession session)throws Exception{
//session失效
session.invalidate();
//重定向到商品查詢頁面
return "redirect:/items/queryItems.action";
}
}
總結
- 使用Spring的校驗方式就是將要校驗的屬性前邊加上註解聲明。
- **在Controller中的方法參數上加上@Validation註解。那麼SpringMVC內部就會幫我們對其進行處理(創建對應的bean,載入配置文件)**
- BindingResult可以拿到我們校驗錯誤的提示
- 分組校驗就是將讓我們的校驗更加靈活:某方法需要校驗這個屬性,而某方法不用校驗該屬性。我們就可以使用分組校驗了。
- 對於處理異常,SpringMVC是用一個統一的異常處理器類的。實現了HandlerExceptionResolver介面。
- 對模塊細分多個異常類,都交由我們的統一異常處理器類進行處理。
- 對於RESTful規範,我們可以使用SpringMVC簡單地支持的。將SpringMVC的攔截.action改成是任意的。同時,如果是靜態的資源文件,我們應該設置不攔截。
- 對於url上的參數,**我們可以使用@PathVariable將url中的{}包起參數和形參進行綁定**
- SpringMVC的攔截器和Struts2的攔截器差不多。不過SpringMVC的攔截器配置起來比Struts2的要簡單。
- 至於他們的攔截器鏈的調用順序,和Filter的是沒有差別的。
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關註微信公眾號:Java3y