SpringMVC【校驗器、統一處理異常、RESTful、攔截器】

前言

本博文主要講解的知識點以下:html

  • 校驗器
  • 統一處理異常
  • RESTful
  • 攔截器

Validation

在咱們的Struts2中,咱們是繼承ActionSupport來實現校驗的...它有兩種方式來實現校驗的功能前端

  • 手寫代碼
  • XML配置
    • 這兩種方式也是能夠特定處理方法或者整個Action的

而SpringMVC使用JSR-303(javaEE6規範的一部分)校驗規範,springmvc使用的是Hibernate Validator(和Hibernate的ORM無關)java

快速入門

導入jar包web

 

這裏寫圖片描述

 

配置校驗器spring

<!-- 校驗器 -->
	<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配置文件restful

 

這裏寫圖片描述

 

定義規則session

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的配置文件中定義全局視圖,頁面顯示錯誤信息

詳情可看:blog.csdn.net/hon_3y/arti…

那麼咱們此次的統一處理異常的方案是什麼呢????

咱們知道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架構...

如下的文章供咱們瞭解:

www.ruanyifeng.com/blog/2011/0…

綜合上面的解釋,咱們總結一下什麼是RESTful架構:

  •   (1)每個URI表明一種資源;
  •   (2)客戶端和服務器之間,傳遞這種資源的某種表現層;
  •   (3)客戶端經過四個HTTP動詞,對服務器端資源進行操做,實現"表現層狀態轉化"

關於RESTful冪等性的理解:www.oschina.net/translate/p…

簡單來講,若是對象在請求的過程當中會發生變化(以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

相關文章
相關標籤/搜索