JAVAWEB開發之SpringMVC詳解(二)——高級開發、數據回顯、參數綁定集合、圖片上傳、json交互、validation校驗、異常處理、RESTful支持、攔截器

知識回顧

springmvc框架
用戶請求url到DispatcherServlet前端控制器,相當於中央調度器,降低系統各組件之間的耦合度。
DispatcherServlet前端控制器通過HandlerMapping處理器映射器根據url找到Handler。
DispatcherServlet前端控制器通過HandlerAdapter處理器適配器執行Handler。
DispatcherServlet前端控制器得到Handler返回的ModelAndView通過視圖解析器ViewResolver進行視圖解析。
視圖解析:將程序中寫的邏輯視圖名,轉成真正的視圖(springmvc通過view表示各個類型不同的視圖)。DispatcherServlet前端控制器調用View的渲染方法進行視圖渲染(將ModelAndView中的model放到request域)

重點:SpringMVC的註解開發,企業中常用springmvc的註解開發
使用專門註解處理器映射器(RequestMappingHandlerMapping)和處理器適配器(RequestMappingHandlerAdapter)
注意:使用<mvc:annotation-driven/>可以替代上邊的註解處理器映射器和註解處理器適配器的配置。
在Handler(controller)中定義很多的方法,一個方法通過@RequestMapping對url進行映射。
方法返回值:ModelAndView、String(jsp的邏輯視圖名)、void(通過response將數據輸出成json)。
方法輸入參數(形參):springmvc需要將請求的key/value(串,id=001&id=002)、解析綁定到Handler(controller)中方法的形參上。
springmvc默認支持多類型的參數綁定。
默認支持的類型:HttpServletRequest、HttpServletResponse、HttpSession、Model(用於將數據填充到request域)。
@RequestParam註解:用於綁定單個請求參數,常用於簡單類型參數(Integer、String、Float......)綁定。
不使用@ReuestParam要求請求參數的名稱和方法形參名一致方可進行綁定。
對於簡單類型參數中的日期型,建議使用自定義參數綁定,對日期類型數據個性化定義日期的格式。
自定義參數綁定:建議使用Converter進行參數綁定。
還可以綁定pojo、包裝的pojo.

高級知識清單

註解開發:
  數據回顯:表單提交錯誤,重新回到表單,用戶重新填寫數據,剛纔提交的參數在頁面上回顯。
  集合類型(String[]、List<>、Map)的參數綁定
  springmvc上傳圖片(重點)
  json數據交互(提交json數據、響應json數據)(重點)
  Validation(springmvc使用校驗方式: 使用Hibernate Validator(和Hibernate的ORM沒有任何關係) )
攔截器(用於權限控制)

數據回顯

需求  

表單提交出現錯誤,重新回到表單,用戶重新填寫數據,剛纔提交的數據在頁面上顯示。

對簡單類型的數據回顯

對商品修改數據回顯:
注意在進入修改頁面的controller方法中和提交修改商品信息方法model.addAttribute方法設置key一致。

修改商品顯示方法:
 @RequestMapping(value="/editItems",method={RequestMethod.GET}) 
	  public String editItems(Model model,Integer id) throws Exception{
	  
	 // 將id傳遞到頁面
	  model.addAttribute("id",id);
		  
	  // 調用Service查詢商品信息 
	  ItemsCustom itemsCustom = itemsService.findItemsById(id); //將模型數據傳到jsp 
	  model.addAttribute("item",itemsCustom);
	  
	  //return "editItem_2";
	  return "editItem"; 
	  }
修改頁面
<form id="itemForm" action="${pageContext.request.contextPath }/items/editItemSubmit.action" method="post" >
<input type="hidden" name="id" value="${id }"/>
修改商品信息:
<table width="100%" border=1>
<tr>
	<td>商品名稱</td>
	<td><input type="text" name="name" value="${item.name }"/></td>
</tr>
 ......
修改商品提交方法
@RequestMapping("/editItemSubmit")
	public String editItemSubmit(Model model,Integer id, ItemsCustom itemsCustom) throws Exception {

		// 調用Service接口更新商品信息
		itemsService.updateItems(id, itemsCustom);

		// 進行數據回顯
		model.addAttribute("id", id);
		
		// 提交後回到修改頁面
		return "editItem";
	}
提交後查看網頁源碼

可以發現只有id回顯到了修改頁面
其過程原理是這樣的:
第一次點擊修改跳轉到修改頁面,從request域取值爲name的id的屬性賦值。
點擊提交後將name的值(請求參數)提交到提交方法中爲形參id賦值。
然後在提交方法內將id存入request域,再跳轉到編輯頁面,進行回顯。

pojo數據類型回顯

回顯方法1:

使用model.addAttribute方法進行數據回顯
@RequestMapping("/editItemSubmit")
	public String editItemSubmit(Model model,Integer id, ItemsCustom itemsCustom) throws Exception {

		// 調用Service接口更新商品信息
		itemsService.updateItems(id, itemsCustom);

		// 進行數據回顯
		model.addAttribute("id", id);
		model.addAttribute("item",itemsCustom);
		
		// 提交後回到修改頁面
		return "editItem";
}
過程就是:提交後表單數據傳入到提交方法的形參id和itemsCustom中。
然後將形參存入到request域,屬性命名保持與編輯頁面取數據的名稱一致。
跳轉到編輯頁面即可從request取值 進行回顯。

回顯方法2:

使用@ModelAttribute, 作用於將請求pojo數據放到Model中回顯到頁面
	public String editItemSubmit(Model model,Integer id, @ModelAttribute(value="item")ItemsCustom itemsCustom) throws Exception {
在@ModelAttribute方法指定的名稱就是要填充到Model中的key,在頁面上就要通過key取數據。

@ModelAttribute將方法返回值傳到頁面

需求:商品類別信息在商品信息頁面展示
// 單獨將商品類型的方法提出來,將方法返回值填充到request,在頁面顯示
	@ModelAttribute("itemsType")
	public Map<String, String> getItemsType() throws Exception{
		HashMap<String, String> itemsType = new HashMap<String, String>();
		itemsType.put("001", "數碼");
		itemsType.put("002", "服裝");
		return itemsType;
	}
頁面
商品類別:
	<select> 
		<c:forEach items="${itemsType }" var="item">
			<option value="${item.key }">${item.value }</option>
		</c:forEach>
	</select>
使用@ModelAttribute將公用的取數據的方法返回值傳遞到頁面,不用在controller的每一個方法中通過Model將數據傳到頁面。

參數綁定集合類型

綁定數組

需求:在商品查詢列表頁面,用戶選擇要刪除的商品,批量刪除商品。
在controller方法中如何將批量提交的數據綁定成數組類型。

頁面定義

  <td><input type="checkbox" name="delete_id" value="${item.id }"/></td>

controller方法定義

// 刪除商品
	@RequestMapping("/deleteItems")
	public String deleteItems(Integer[] delete_id) throws Exception{
		System.out.println(delete_id);
		return "success";
	}
運行結果:

綁定List<Object>

需求:批量修改商品信息提交
先進入批量修改商品頁面,填寫信息,點擊提交

頁面定義

<c:forEach items="${itemsList }" var="item" varStatus="s">
<tr>
	<td><input type="text" name="itemsList[${s.index }].name" value="${item.name }"/></td>
	<td><input type="text" name="itemsList[${s.index }].price" value="${item.price }"/></td>
	......
</tr>
註釋:name值中的itemList
itemsList:controller方法形參包裝類型中list的屬性名
itemsList[0]或itemsList[0]......[]中是序號,從0開始
itemsList[index].name就是controller方法形參包裝類型中list中pojo的屬性名。

Controller方法定義

使用包裝類型接收頁面批量提交的數據,綁定成list
public class ItemsQueryVo {

	// 商品信息
	private ItemsCustom itemsCustom;

	// 定義一個List
	private List<ItemsCustom> itemsList;
        ......
}
使用包裝類型作爲形參 接收參數
// 批量修改商品提交
	@RequestMapping("/editItemsListSubmit")
    public String editItemsListSubmit(ItemsQueryVo itemsQueryVo){
		return "success";
	}

springmvc和struts的區別

SpringMVC是通過方法的形參接受參數,在使用時可以以單例方式使用,建議使用單例。
Struts是通過成員變量接收參數,在使用時必須以多例方式使用。

SpringMVC是基於方法開發,Struts是基於類開發。
SpringMVC將一個請求的Method和Handler進行關聯綁定,一個method對應一個Handler。
SpringMVC開發是以方法爲單位進行開發,方法更貼近Service(業務方法)。
經過實際測試,發現Struts標籤解析速度比較慢,建議在實際開發中使用jstl。

商品圖片上傳

需求

在商品修改頁面,增加圖片上傳的功能。
操作流程:
    用戶進入商品修改頁面
    上傳圖片
    點擊提交(提交的是圖片和商品的信息)
    再次進入修改頁面,圖片在商品修改頁面展示

圖片存儲的問題

切記:不要將圖片上傳到工程目錄,不方便進行工程維護。
實際電商項目中使用專門的圖片服務器(比如Apache、Tomcat)
在Tomcat中進行虛擬目錄的設置
設置方法如下:
在Tomcat下,找到conf文件下的server.xml,打開,在<Host>和</host>之間加上如下代碼,然後就部署完成,重啓服務器,瀏  覽器可以訪問:
  <Context path="/虛擬目錄名" docBase="目標目錄位置" debug="0" reloadable="true" ></Context>
 虛擬目錄名:瀏覽器訪問的地址
 目標目錄位置:項目所在目錄的絕對路徑

 reloadable="true" :服務器配置動態加載

測試:拖進目錄中一張圖片,啓動服務器進行測試:


注意:圖片目錄中儘量進行目錄分級存儲,提高IO的訪問速度。

配置圖片上傳解析器

springmvc使用commons-fileupload進行圖片上傳
commons-fileupload對應的springmvc的圖片上傳解析器:org.springframework.web.multipart.commons.CommonsMultipartResolver
在springmvc.xml中配置如下:
<!-- 文件上傳 -->
	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<!-- 設置上傳文件的最大尺寸爲5MB -->
		<property name="maxUploadSize">
			<value>5242880</value>
		</property>
	</bean>
加入commons-fileupload的jar包

編寫上傳圖片的頁面

表單屬性的enctype要設置爲multipart/form-data
<form id="itemForm" action="${pageContext.request.contextPath }/items/editItemSubmit.action" method="post" enctype="multipart/form-data">
添加圖片上傳組件<input type="file" name=""> 其中name的屬性值要與控制器中接收圖片的方法中的形參一致。
<td>商品圖片</td>
	<td>
		<c:if test="${item.pic != null }">
			<img src="/pic/${item.pic}" width="100px" height="100px"/>
			<br/>
		</c:if>
		<input type="file" name="pictureFile"/>
	</td>

編寫控制器方法

@RequestMapping("/editItemSubmit")
	// public String editItemSubmit(Integer id,ItemsCustom
	// itemsCustom,ItemsQueryVo itemsQueryVo) throws Exception{
	public String editItemSubmit(Model model,Integer id, 
			@ModelAttribute(value="item")ItemsCustom itemsCustom,
			// 上傳圖片
			MultipartFile pictureFile
			) throws Exception {
		// 進行數據回顯
		model.addAttribute("id", id);
		//model.addAttribute("item",itemsCustom);
		
		//進行圖片上傳
		if (pictureFile!=null && pictureFile.getOriginalFilename()!=null && pictureFile.getOriginalFilename().trim().length()>0) {
			// 圖片上傳成功後,將圖片的地址寫到數據庫
			String filePath = "/Users/liuxun/Desktop/pictures";
			// 上傳文件原始名稱
			String originFileName = pictureFile.getOriginalFilename();
			// 新的圖片的名稱
			String newFileName = UUID.randomUUID()+originFileName.substring(originFileName.lastIndexOf("."));
			// 新文件
			File file = new File(filePath+File.separator+newFileName);
			
			// 將內存中的文件寫入磁盤
			pictureFile.transferTo(file);
			
			// 圖片上傳成功,將圖片地址寫入數據庫
			itemsCustom.setPic(newFileName);
		}
		
		// 調用Service接口更新商品信息
		itemsService.updateItems(id, itemsCustom);
		
		// 提交後回到修改頁面
		return "editItem";
		// 請求重定向
		//return "redirect:queryItems.action";
		// 轉發
		// return "forward:queryItems.action";
	}
提交後頁面效果如下


json數據交互

需求

json數據格式是比較簡單和容易理解的,json數據格式常用於遠程接口傳輸,http傳輸json數據,非常方便頁面進行 提交/請求結果解析,對json數據的解析。

SpringMVC解析json加入json解析包

springmvc默認採用MappingJacksonHttpMessageConverter對json數據進行轉換,需要加入jackson的包如下所示:

在處理器適配器中注入MappingJacksonHttpMessageConverter

讓處理器適配器支持json數據解析,需要注入MappingJacksonHttpMessageConverter
 <!-- 註解適配器 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    	<!-- 在webBindingInitializer中注入自定義屬性編輯器,自定義轉換器 -->
    	<property name="webBindingInitializer" ref="customBinder"/>
    	<!-- 加入json數據的消息轉換器 MappingJacksonHttpMessageConverter依賴的Jackson包 -->
    	<property name="messageConverters">
    		<list>
    			<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
    		</list>
    	</property>
    </bean>
需要將支持json數據解析的消息轉換器注入到註解適配器中。

@RequestBody和@RespoonseBody

@RequestBody:將請求的json數據轉成Java對象
@ResponseBody:將Java對象轉成json數據輸出。

請求json響應json

controller方法
//請求的json響應json,請求商品信息,商品信息使用json格式,輸出商品信息
	@RequestMapping("/requestJson")
	public @ResponseBody ItemsCustom requestJson(@RequestBody ItemsCustom itemsCustom) throws Exception{
		return itemsCustom;
	}
頁面(需要引入jQuery)
// 請求json響應json
	function requestJson(){
		$.ajax({
			url:"${pageContext.request.contextPath}/requestJson.action",
			type:"post",
			contentType:"application/json;charset=utf-8",
			//請求json數據 使用json表示商品信息
			data:'{"name":"手機","price":1999}',
			success:function(data){
				alert(data.name);
			}
		});
	}
測試:

請求key/value響應json

controller方法
@RequestMapping("/responseJson")
	public @ResponseBody ItemsCustom responseJson(ItemsCustom itemsCustom) throws Exception{
		return itemsCustom;
	}
頁面
// 請求key/value響應json
	function responseJson(){
		$.ajax({
			url:"${pageContext.request.contextPath}/responseJson.action",
			type:"post",
			//contentType:"application/json;charset=utf-8",
			//請求key/value數據 使用地址拼接表示商品信息
			data:"name=手機&price=1999",
			success:function(data){
				alert(data.name);
			}
		});
	}
測試:

總結:

如果前端處理沒有特殊要求建議使用第二種,請求key/value, 返回json, 方便客戶端解析請求結果。

validation校驗

對前臺的校驗大多數通過js在頁面校驗,這種方法比較簡單,如果出於安全性考慮,還需要在後臺進行校驗。
SpringMVC使用JSR-303(JavaEE規範第一部分)校驗規範,SpringMVC使用的是Hibernate Validator

加入Hibernate Validator的jar


在處理器適配器中配置校驗器

在註解適配器中注入自定義的webBinder
  <!-- 註解適配器 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    	<!-- 在webBindingInitializer中注入自定義屬性編輯器,自定義轉換器 -->
    	<property name="webBindingInitializer" ref="customBinder"/>
    	<!-- 加入json數據的消息轉換器 MappingJacksonHttpMessageConverter依賴的Jackson包 -->
    	<property name="messageConverters">
    		<list>
    			<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
    		</list>
    	</property>
    </bean>
在webBinder中注入自定義的校驗器
<!-- 自定義webBinder -->
	<bean id="customBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
		<!-- 使用converter進行參數轉換 -->
		<property name="conversionService" ref="conversionService"/>
		<!-- 配置Validator -->
		<property name="validator" ref="validator"/>
	</bean>
配置自定義校驗器
<!-- 校驗器 -->
	<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>

創建CustomValidationMessages

在classpath下新建CustomValidationMessages.properties

校驗規則

需求:編輯商品信息提交時校驗,商品生產日期不能爲空,商品名稱在1-30個字符之間
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;
    ......
}

捕獲錯誤

需要修改controller方法,在需要校驗的形參pojo前邊加@Validated, 在pojo的後邊加上參數BindingResult用來接收錯誤信息
@RequestMapping("/editItemSubmit")
    //注意:每個校驗pojo的前邊必須加@Validated, 每個校驗的pojo後邊必須加BindingResult接收錯誤信息
	public String editItemSubmit(Model model,Integer id, 
			@Validated @ModelAttribute(value="item")ItemsCustom itemsCustom,
			BindingResult bindingResult,
			// 上傳圖片
			MultipartFile pictureFile
			) throws Exception {
錯誤信息輸出
// 輸出錯誤信息
		// 如果參數綁定時有錯誤
		if (bindingResult.hasErrors()) {
			// 獲取錯誤
			List<ObjectError> errors = bindingResult.getAllErrors();
			// 準備在頁面輸出errors,頁面使用jstl遍歷
			model.addAttribute("errors",errors);
			for (ObjectError error : errors) {
				// 輸出錯誤信息
				System.out.println(error.getDefaultMessage());
			}
			// 如果校驗錯誤,回到商品修改頁面
			return "editItem";
		}

在頁面上展示錯誤

<!-- 錯誤信息 -->
<div style="color: red;">
 <c:forEach items="${errors }" var="error">
	${error.defaultMessage }<br>
</c:forEach>
</div>
效果如下:

分組校驗

需求:針對不同的controller方法通過分組校驗達到個性化校驗的目的,修改商品的修改功能,只校驗商品的生產日期不能爲空。
第一步:創建分組接口
package liuxun.ssm.controller.validation;
/**
 * 校驗分組: 用於商品修改的校驗分組
 * @author liuxun
 *
 */
public interface ValidGroup1 {
	//接口不定義方法,就是隻標識 哪些校驗規則屬於ValidGroup1分組
}
第二步:定義校驗規則屬於哪個分組
 //請輸入商品生產日期
    //通過groups指定此校驗屬於哪個分組,可以指定多個分組 之間用逗號隔開groups={ValidGroup1.class,ValidGroup2.class}
    @NotNull(message="{items.createtime.is.notnull}",groups={ValidGroup1.class})
    private Date createtime;
第三步:在controller方法中使用定義的校驗分組
public String editItemSubmit(Model model,Integer id, 
			@Validated(value={ValidGroup1.class}) @ModelAttribute(value="item")ItemsCustom itemsCustom,
			BindingResult bindingResult,
			// 上傳圖片
			MultipartFile pictureFile
			) throws Exception {
運行如下:


統一異常處理

需求

一般項目中都需要做異常處理,基於系統架構的設計考慮,使用統一的異常處理方法。
系統中的異常類型:
包括預期可能發生的異常、運行時異常(RuntimeException),運行時異常不是預期會發生的。
針對預期可能發生的異常,在代碼中手動處理異常可以try/catch捕獲,可以向上拋出。
針對運行時異常,只能通過規範代碼質量、在系統測試時詳細測試等排除運行時異常。

統一異常處理解決方案

自定義異常

針對預期可能發生的異常,定義很多異常類型,這些異常類型通常繼承於Exception。
這裏定義一個系統自定義異常類。
CustomException:用於測試。
public class CustomException extends Exception {
	
	//異常信息
	private String message;

	public CustomException(String message) {
		super();
		this.message = message;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
}

異常處理

要在一個統一異常處理的類中要處理系統拋出的所有異常,根據異常類型處理。
統一異常處理類是什麼?
前端控制器DispatcherServlet在進行HandlerMapping、調用HandlerAdapter執行Handler的過程中,如果遇到異常進行異常處理

在系統中自定義統一的異常處理器,寫系統自己的異常處理代碼。

自定義異常處理器類

統一異常處理器實現HandlerExceptionResolver接口
public class CustomExceptionResolver implements HandlerExceptionResolver {

	//前端控制器DispatcherServlet在進行HandlerMapping、調用HandlerAdapter執行Handler過程中,如果遇到異常就會執行此方法
	@Override
	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
			Exception ex) {
		// 統一處理異常代碼
		// ......
		return null;
	}

}

配置統一異常處理類

<!-- 定義統一異常處理器類 -->
	<bean class="liuxun.ssm.exception.CustomExceptionResolver"/>

異常處理邏輯

根據不同的異常類型進行異常處理。
系統自定義的異常類是CustomException,在Controller方法中、Service方法中手動拋出此類異常。
針對系統自定義的CustomException異常,就可以直接從異常類中獲取異常信息,將異常處理在錯誤頁面展示。
針對非CustomException異常,對這類異常重新構造成一個CustomException,異常信息爲"未知錯誤", 此類錯誤需要在系統測試階段進行排除。
在統一異常處理器CustomExceptionResolver中實現上邊邏輯。
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 | IOException e) {
			e.printStackTrace();
		}
		
		return new ModelAndView();
	}
展示錯誤信息頁面
<body>
${message }
</body>

測試拋出異常由統一異常處理器捕獲

可以在controller方法、service方法、dao實現類中拋出異常,要求dao、service、controller遇到異常全部向上拋出異常,方法向上拋出異常throws Exception
public ItemsCustom findItemsById(int id) throws Exception {
		Items items = itemsMapper.selectByPrimaryKey(id);
		//如果查詢的商品信息爲空,拋出系統自定義異常
		if (items == null) {
			throw new CustomException("修改商品信息不存在");
		}
		// 在這裏隨着需求的變量,需要查詢商品的其他相關信息,返回到controller

		ItemsCustom itemsCustom = new ItemsCustom();
		// 將items的屬性拷貝到itemsCustom
		BeanUtils.copyProperties(items, itemsCustom);

		return itemsCustom;
	}
測試修改方法 參數改成一個不存在的值

流程圖解如下:

RESTful支持

什麼是RESTful

一、REST,即Representational State Transfer的縮寫。我對這個詞組的翻譯是"表現層狀態轉化"。
如果一個架構符合REST原則,就稱它爲RESTful架構。
要理解RESTful架構,最好的方法就是去理解Representational State Transfer這個詞組到底是什麼意思,它的每一個詞代表了什麼涵義。如果你把這個名稱搞懂了,也就不難體會REST是一種什麼樣的設計。
二、資源(Resources)
REST的名稱"表現層狀態轉化"中,省略了主語。"表現層"其實指的是"資源"(Resources)的"表現層"。
所謂"資源",就是網絡上的一個實體,或者說是網絡上的一個具體信息。它可以是一段文本、一張圖片、一首歌曲、一種服務,總之就是一個具體的實在。你可以用一個URI(統一資源定位符)指向它,每種資源對應一個特定的URI。要獲取這個資源,訪問它的URI就可以,因此URI就成了每一個資源的地址或獨一無二的識別符。
所謂"上網",就是與互聯網上一系列的"資源"互動,調用它的URI。
三、表現層(Representation)
"資源"是一種信息實體,它可以有多種外在表現形式。我們把"資源"具體呈現出來的形式,叫做它的"表現層"(Representation)
比如,文本可以用txt格式表現,也可以用HTML格式、XML格式、JSON格式表現,甚至可以採用二進制格式;圖片可以用JPG格式表現,也可以用PNG格式表現。
URI只代表資源的實體,不代表它的形式。嚴格地說,有些網址最後的".html"後綴名是不必要的,因爲這個後綴名錶示格式,屬於"表現層"範疇,而URI應該只代表"資源"的位置。它的具體表現形式,應該在HTTP請求的頭信息中用Accept和Content-Type字段指定,這兩個字段纔是對"表現層"的描述。
四、狀態轉化(State Transfer)
訪問一個網站,就代表了客戶端和服務器的一個互動過程。在這個過程中,勢必涉及到數據和狀態的變化。
互聯網通信協議HTTP協議,是一個無狀態協議。這意味着,所有的狀態都保存在服務器端。因此,如果客戶端想要操作服務器,必須通過某種手段,讓服務器端發生"狀態轉化"(State Transfer)。而這種轉化是建立在表現層之上的,所以就是"表現層狀態轉化"
客戶端用到的手段,只能是HTTP協議。具體來說,就是HTTP協議裏面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:GET用來獲取資源,POST用來新建資源(也可以用於更新資源),PUT用來更新資源,DELETE用來刪除資源
五、綜述
綜合上面的解釋,我們總結一下什麼是RESTful架構:
  (1)每一個URI代表一種資源;
  (2)客戶端和服務器之間,傳遞這種資源的某種表現層;
  (3)客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現"表現層狀態轉化"。

六、誤區
RESTful架構有一些典型的設計誤區。
最常見的一種設計錯誤,就是URI包含動詞。因爲"資源"表示一種實體,所以應該是名詞,URI不應該有動詞,動詞應該放在HTTP協議中。
舉例來說,某個URI是/posts/show/1,其中show是動詞,這個URI就設計錯了,正確的寫法應該是/posts/1,然後用GET方法表示show。
如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。比如網上匯款,從賬戶1向賬戶2匯款500元,錯誤的URI是:
  POST /accounts/1/transfer/500/to/2
正確的寫法是把動詞transfer改成名詞transaction,資源不能是動詞,但是可以是一種服務:
  POST /transaction HTTP/1.1
  Host: 127.0.0.1
  
  from=1&to=2&amount=500.00
另一個設計誤區,就是在URI中加入版本號:
  http://www.example.com/app/1.0/foo
  http://www.example.com/app/1.1/foo
  http://www.example.com/app/2.0/foo
因爲不同的版本,可以理解成同一種資源的不同表現形式,所以應該採用同一個URI。版本號可以在HTTP請求頭信息的Accept字段中進行區分
  Accept: vnd.example-com.foo+json; version=1.0
  Accept: vnd.example-com.foo+json; version=1.1
  Accept: vnd.example-com.foo+json; version=2.0

url的RESTful實現

非RESTful的http的url:http://localhost:8080/items/editItems.action?id=1&....
RESTful的url是簡潔的:http:// localhost:8080/items/editItems/1
參數通過url傳遞,rest接口返回json數據

需求

根據id查看商品信息,商品信息查看的連接使用RESTful方式實現,商品信息以json方式返回。

第一步更改DispatcherServlet配置

在web.xml中添加如下配置
<!-- 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>

第二步參數通過url傳遞

//根據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攔截/開頭的所有請求,對靜態資源的訪問就會報錯

需要在springmvc.xml中通過設置對靜態資源解析
<!-- 靜態資源解析 -->
	<mvc:resources location="/js/" mapping="/js/**"/>
	<mvc:resources location="/img/" mapping="/img/**"/>
訪問/js/**的url從工程下/js/下解析
測試結果:

注意:使用<mvc:annotation-driven/>可以替代註解映射器和註解適配器的配置,而且原先需要向註解適配器中注入webBinder(包含validator校驗器以及converter參數轉換器或者屬性編輯器) 使用<mvc:annotation-driven/>替代後不用webBinder 可以直接設置到<mvc:annotation-driven/>標籤的節點屬性中,如下所示:

springmvc攔截器

攔截器的通常場合

用戶請求到DIspatcherServlet中,DispatcherServlet調用HandlerMapping查找Handler,HandlerMapping返回一個攔截器的鏈,springmvc的攔截器是通過HandlerMapping發起的。
在企業開發中,使用攔截器實現用戶認證(用戶登陸後進行身份校驗攔截器),用戶權限攔截。

springmvc攔截器方法

自定義攔截器,需要實現HandlerInterceptor接口
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之後執行此方法
	//作爲系統統一異常處理,進行方法執行性能監控,在preHandler中設置一個時間點 在afterCompletion設置一個時間點 二者時間差就是執行時長
	//實現系統,統一日誌記錄
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
			throws Exception {
		System.out.println("HandlerInterceptor1...afterCompletion");
	}

}

測試攔截器

定義兩個攔截器


配置攔截器

配置全局攔截器,DispatcherServlet將配置的全局攔截器加載到所有的HandlerMapping映射器。
在springmvc.xml中配置:
<!-- 攔截器 -->
	<mvc:interceptors>
		<!-- 多個攔截器,順序執行 -->
		<mvc:interceptor>
			<mvc:mapping path="/**"/>
			<bean class="liuxun.ssm.controller.interceptor.HandlerInterceptor1"></bean>
		</mvc:interceptor>
		<mvc:interceptor>
			<mvc:mapping path="/**"/>
			<bean class="liuxun.ssm.controller.interceptor.HandlerInterceptor2"></bean>
		</mvc:interceptor>
	</mvc:interceptors>

測試1:1號和2號都放行

測試結果
HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle

HandlerInterceptor2...postHandle
HandlerInterceptor1...postHandle

HandlerInterceptor2...afterCompletion
HandlerInterceptor1...afterCompletion
總結:
執行preHandler是順序執行的
執行postHandler、afterHandler是倒序執行的

測試2:1號放行,2號不放行

HandlerInterceptor1...preHandle
HandlerInterceptor2...preHandle
HandlerInterceptor1...afterCompletion
總結:
如果當前攔截器的preHandler不放行,postHandler、afterHandler都不放行。
攔截器鏈中只要有一個攔截器不放行,所有攔截器的postHandler都不會執行,controller就不能執行完成

測試3:1號和2號都不放行

測試結果:
HandlerInterceptor1...preHandle
總結:
只有前邊的攔截器preHandler方法放行,下邊的攔截器的preHandler纔會執行

日誌攔截器或異常攔截器要求

將日誌攔截器或異常攔截器放在攔截器鏈中的第一個位置,且preHandler方法放行。

攔截器應用(用戶認證攔截器)

需求

用戶訪問系統的資源(url),如果用戶沒有進行身份認證,進行攔截,系統跳轉登錄頁面,如果用戶已經認證通過,用戶可以訪問系統資源。

用戶登錄及退出功能開發

@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";
	}
}

用戶身份認證攔校驗截器

攔截器實現思路:http請求URL,如果URL是公開地址(不需要認證即可訪問的URL) 放行,如果用戶在Session中存在 放行,如果用戶在Session中不存在,跳轉到登錄頁面。

自定義登錄攔截器

@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;
	}
在springmvc.xml中配置攔截器
    <mvc:interceptors>
	    <mvc:interceptor>
		   	<!-- /**可以攔截多層路徑 -->
		   	<mvc:mapping path="/**"/>
		   	<bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean>
	    </mvc:interceptor>
	</mvc:interceptors>
效果如下:


實例源代碼如下

源碼已經上傳GitHub  https://github.com/LX1993728/springmvc_mybatis_1
工程目錄結構如下:




web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>springmvc_mybatis_1</display-name>

	<!-- 配置Spring容器監聽器 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring/applicationContext-*.xml</param-value> 
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- 前端控制器 -->
	<servlet>
		<servlet-name>springmvc</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</servlet-name>
		<!-- 可以配置/ ,此工程 所有請求全部由springmvc解析,此種方式可以實現 RESTful方式,需要特殊處理對靜態文件的解析不能由springmvc解析 
			可以配置*.do或*.action,所有請求的url擴展名爲.do或.action由springmvc解析,此種方法常用 不可以/*,如果配置/*,返回jsp也由springmvc解析,這是不對的。 -->
		<url-pattern>*.action</url-pattern>
	</servlet-mapping>
	
	<!-- 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>
	
	<!-- post亂碼處理 -->
	<filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>utf-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>


	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.htm</welcome-file>
		<welcome-file>index.jsp</welcome-file>
		<welcome-file>default.html</welcome-file>
		<welcome-file>default.htm</welcome-file>
		<welcome-file>default.jsp</welcome-file>
	</welcome-file-list>
</web-app>
SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

	<!-- 定義別名 -->
	<typeAliases>
	 	<!-- 批量別名定義 指定包路徑,自動掃描包下邊的pojo, 定義別名,別名默認爲類名(首字母小寫或大寫) -->
		<package name="liuxun.ssm.po" />
	</typeAliases>

	<!-- 由於使用Spring和mybatis整合的mapper掃描器,這裏可以不用配置了 
	<mappers>
		<package name="liuxun.ssm.mapper"/>
	</mappers>
	-->
</configuration>
applicationContext-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
		http://www.springframework.org/schema/mvc 
		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.2.xsd 
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

	<!-- 加載配置文件 -->
	<context:property-placeholder location="classpath:db.properties" />
	<!-- 數據庫連接池 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
      <property name="driverClassName" value="${jdbc.driver}"/>
      <property name="url" value="${jdbc.url}"/>
      <property name="username" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
      <property name="maxActive" value="10"/>
      <property name="maxIdle" value="5"/>
	</bean>
	
	<!-- SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	    <!-- 數據源 -->
	    <property name="dataSource" ref="dataSource"/>
	    <!-- mybatis配置文件 -->
	    <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml"/>
    </bean>
    
    <!-- 
    MapperScannerConfigurer: mapper掃描器,將包下邊的mapper接口自動創建代理對象,
    自動創建到Spring容器中,bean的id是mapper的類名(首字母小寫)
     -->
     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	     <!-- 配置掃描包的路徑
	     如果要掃描多個包,中間使用半角逗號隔開
	     要求mapper.xml和mapper.java同名且在同一目錄
	      -->
	     <property name="basePackage" value="liuxun.ssm.mapper"/>
	     <!-- 使用SqlSessionFactoryBeanName -->
	     <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
     </bean>
</beans>
applicationContext-service.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
		http://www.springframework.org/schema/mvc 
		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.2.xsd 
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
      
   <!-- 商品管理的Service -->
   <bean  id="itemsService" class="liuxun.ssm.service.impl.ItemsServiceImpl"></bean>
</beans>
applicationContext-transaction.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
		http://www.springframework.org/schema/mvc 
		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.2.xsd 
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">

	<!-- 使用聲明式事務,可以有效規範代碼 -->

	<!-- 事務管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 通知 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="save*" propagation="REQUIRED" />
			<tx:method name="insert*" propagation="REQUIRED" />
			<tx:method name="update*" propagation="REQUIRED" />
			<tx:method name="delete*" propagation="REQUIRED" />
			<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
			<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
			<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
		</tx:attributes>
	</tx:advice>

	<!-- aop -->
	<aop:config>
		<aop:advisor advice-ref="txAdvice" pointcut="execution(* liuxun.ssm.service.impl.*.*(..))" />
	</aop:config>
</beans>
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
		http://www.springframework.org/schema/mvc 
		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.2.xsd 
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
	<!-- 使用spring組件掃描 -->
	<context:component-scan base-package="liuxun.ssm.controller" />
	
	<!-- 靜態資源解析 -->
	<mvc:resources location="/js/" mapping="/js/**"/>
	<mvc:resources location="/img/" mapping="/img/**"/>
	
	<!-- 通過使用mvc的annotation-driven 可以替代下邊的處理器映射器和適配器 -->
	<!--  <mvc:annotation-driven conversion-service="conversionService" >
	 </mvc:annotation-driven> -->

	<!-- 註解處理器映射器 -->
	<bean
		class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>

    <!-- 註解適配器 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    	<!-- 在webBindingInitializer中注入自定義屬性編輯器,自定義轉換器 -->
    	<property name="webBindingInitializer" ref="customBinder"/>
    	<!-- 加入json數據的消息轉換器 MappingJacksonHttpMessageConverter依賴的Jackson包 -->
    	<property name="messageConverters">
    		<list>
    			<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
    		</list>
    	</property>
    </bean>

    <!-- 配置視圖解析器 要求將jstl的包加到classpath -->
	<!-- ViewResolver -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/jsp/"/>
		<property name="suffix" value=".jsp"/>
	</bean>
	
	<!-- 自定義webBinder -->
	<bean id="customBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
		<!-- 配置Validator -->
		<property name="validator" ref="validator"/>
		
		<!-- 使用converter進行參數轉換 -->
		<property name="conversionService" ref="conversionService"/>
	
		<!-- propertyEditorRegistrars用於屬性編輯器 -->
		<!--  
		<property name="propertyEditorRegistrars">
			<list>
				<ref bean="customPropertyEditor"/>
			</list>
		</property>
		-->
	</bean>
	
	<!-- 註冊屬性編輯器 -->
	<bean id="customPropertyEditor" class="liuxun.ssm.controller.propertyeditor.CustomPropertyEditor"/>

	<!-- 註冊轉換器 -->
	<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
		<property name="converters">
			<list>
				<bean class="liuxun.ssm.controller.converter.CustomDateConverter"/>
				<bean class="liuxun.ssm.controller.converter.StringTrimConverter"/>
			</list>
		</property>
	</bean>
	
	<!-- 攔截器 -->
	<mvc:interceptors>
		<!-- 多個攔截器,順序執行 -->
   <!-- <mvc:interceptor>
			<mvc:mapping path="/**"/>
			<bean class="liuxun.ssm.controller.interceptor.HandlerInterceptor1"></bean>
		</mvc:interceptor>
		<mvc:interceptor>
			<mvc:mapping path="/**"/>
			<bean class="liuxun.ssm.controller.interceptor.HandlerInterceptor2"></bean>
		</mvc:interceptor>  -->	
		
	    <mvc:interceptor>
		   	<!-- /**可以攔截多層路徑 -->
		   	<mvc:mapping path="/**"/>
		   	<bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean>
	    </mvc:interceptor>
	</mvc:interceptors>
	
	<!-- 文件上傳 -->
	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<!-- 設置上傳文件的最大尺寸爲5MB -->
		<property name="maxUploadSize">
			<value>5242880</value>
		</property>
	</bean>
	
	<!-- 定義統一異常處理器類 -->
	<bean class="liuxun.ssm.exception.CustomExceptionResolver"/>
	
	<!-- 校驗器 -->
	<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>
	
</beans>
CustomValidationMessages.properties

db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
log4j.properties
# Global logging configuration\uff0c\u5efa\u8bae\u5f00\u53d1\u73af\u5883\u4e2d\u8981\u7528debug
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
ItemsController.java
package liuxun.ssm.controller;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import liuxun.ssm.controller.validation.ValidGroup1;
import liuxun.ssm.po.ItemsCustom;
import liuxun.ssm.po.ItemsQueryVo;
import liuxun.ssm.service.ItemsService;

/**
 * 商品管理
 * 
 * @author liuxun
 *
 */
@Controller
// 定義url的根路徑,訪問時 根路徑+方法的url
@RequestMapping("/items")
public class ItemsController {

	// 注入Service
	@Autowired
	private ItemsService itemsService;

	// 單獨將商品類型的方法提出來,將方法返回值填充到request,在頁面顯示
	@ModelAttribute("itemsType")
	public Map<String, String> getItemsType() throws Exception{
		HashMap<String, String> itemsType = new HashMap<String, String>();
		itemsType.put("001", "數碼");
		itemsType.put("002", "服裝");
		return itemsType;
	}
	
	// 查詢商品信息方法
	@RequestMapping("/queryItems")
	public ModelAndView queryItems(HttpServletRequest request) throws Exception {

		// 調用Service查詢商品列表
		List<ItemsCustom> itemsList = itemsService.findItemsList(null);

		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addObject("itemsList", itemsList);
		// 指定邏輯視圖名
		modelAndView.setViewName("itemsList");

		return modelAndView;
	}

	// 批量修改商品查詢
	@RequestMapping("/editItemsList")
	public ModelAndView editItemsList(HttpServletRequest request) throws Exception {
		
		// 調用Service查詢商品列表
		List<ItemsCustom> itemsList = itemsService.findItemsList(null);
		
		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addObject("itemsList", itemsList);
		// 指定邏輯視圖名
		modelAndView.setViewName("editItemsList");
		
		return modelAndView;
	}
	// 批量修改商品提交
	@RequestMapping("/editItemsListSubmit")
    public String editItemsListSubmit(ItemsQueryVo itemsQueryVo){
		return "success";
	}
	// 商品修改頁面
	// 使用method=RequestMethod.GET限制使用get方法
	/*
	  @RequestMapping(value="/editItems",method={RequestMethod.GET}) public
	  ModelAndView editItems() throws Exception{
	  
	  ModelAndView modelAndView = new ModelAndView();
	  
	  // 調用Service查詢商品信息 ItemsCustom itemsCustom =
	  itemsService.findItemsById(1); //將模型數據傳到jsp
	  modelAndView.addObject("item", itemsCustom); // 指定邏輯視圖名
	  modelAndView.setViewName("editItem");
	  
	  return modelAndView; }
	 */

	// 方法返回字符串,字符串就是邏輯視圖名,Model作用就是將數據填充到request域,在頁面展示
	  @RequestMapping(value="/editItems",method={RequestMethod.GET}) 
	  public String editItems(Model model,Integer id) throws Exception{
	  
	 // 將id傳遞到頁面
	  model.addAttribute("id",id);
		  
	  // 調用Service查詢商品信息 
	  ItemsCustom itemsCustom = itemsService.findItemsById(id); //將模型數據傳到jsp 
	  model.addAttribute("item",itemsCustom);
	  
	  //return "editItem_2";
	  return "editItem"; 
	 }
	 
	 //根據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;
	  }

	// 方法返回void
	/*
	@RequestMapping(value = "/editItems", method = { RequestMethod.GET })
	public void editItems(HttpServletRequest request, HttpServletResponse response,
			// @RequestParam(value="item_id",required=false,defaultValue="1")Integer id
			Integer id) throws Exception {

		// 調用Service查詢商品信息
		ItemsCustom itemsCustom = itemsService.findItemsById(id);
		request.setAttribute("item", itemsCustom);
		// 注意如果使用request轉向頁面,這裏指定頁面的完整路徑
		request.getRequestDispatcher("/WEB-INF/jsp/editItem.jsp").forward(request, response);
	}
	*/

	// 商品修改提交
	// itemsQueryVo是包裝類型的pojo
	@RequestMapping("/editItemSubmit")
	// public String editItemSubmit(Integer id,ItemsCustom
	// itemsCustom,ItemsQueryVo itemsQueryVo) throws Exception{
    //注意:每個校驗pojo的前邊必須加@Validated, 每個校驗的pojo後邊必須加BindingResult接收錯誤信息
	public String editItemSubmit(Model model,Integer id, 
			@Validated(value={ValidGroup1.class}) @ModelAttribute(value="item")ItemsCustom itemsCustom,
			BindingResult bindingResult,
			// 上傳圖片
			MultipartFile pictureFile
			) throws Exception {
		// 輸出錯誤信息
		// 如果參數綁定時有錯誤
		if (bindingResult.hasErrors()) {
			// 獲取錯誤
			List<ObjectError> errors = bindingResult.getAllErrors();
			// 準備在頁面輸出errors,頁面使用jstl遍歷
			model.addAttribute("errors",errors);
			for (ObjectError error : errors) {
				// 輸出錯誤信息
				System.out.println(error.getDefaultMessage());
			}
			// 如果校驗錯誤,回到商品修改頁面
			return "editItem";
		}
		
		// 進行數據回顯
		model.addAttribute("id", id);
		//model.addAttribute("item",itemsCustom);
		
		//進行圖片上傳
		if (pictureFile!=null && pictureFile.getOriginalFilename()!=null && pictureFile.getOriginalFilename().trim().length()>0) {
			// 圖片上傳成功後,將圖片的地址寫到數據庫
			String filePath = "/Users/liuxun/Desktop/pictures";
			// 上傳文件原始名稱
			String originFileName = pictureFile.getOriginalFilename();
			// 新的圖片的名稱
			String newFileName = UUID.randomUUID()+originFileName.substring(originFileName.lastIndexOf("."));
			// 新文件
			File file = new File(filePath+File.separator+newFileName);
			
			// 將內存中的文件寫入磁盤
			pictureFile.transferTo(file);
			
			// 圖片上傳成功,將圖片地址寫入數據庫
			itemsCustom.setPic(newFileName);
		}
		
		// 調用Service接口更新商品信息
		itemsService.updateItems(id, itemsCustom);
		
		// 提交後回到修改頁面
		return "editItem";
		// 請求重定向
		//return "redirect:queryItems.action";
		// 轉發
		// return "forward:queryItems.action";
	}
	
	// 刪除商品
	@RequestMapping("/deleteItems")
	public String deleteItems(Integer[] delete_id) throws Exception{
		System.out.println(delete_id);
		return "success";
	}

	//自定義屬性編輯器
	/*
	@InitBinder
	public void initBinder(WebDataBinder binder) throws Exception {
		// Date.class必須是與controler方法形參pojo屬性一致的date類型,這裏是java.util.Date
		binder.registerCustomEditor(Date.class, new CustomDateEditor(
				new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true));
	}
	*/

}
JsonTest.java
package liuxun.ssm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import liuxun.ssm.po.ItemsCustom;

/**
 * json測試
 * @author liuxun
 *
 */
@Controller
public class JsonTest {
	//請求的json響應json,請求商品信息,商品信息使用json格式,輸出商品信息
	@RequestMapping("/requestJson")
	public @ResponseBody ItemsCustom requestJson(@RequestBody ItemsCustom itemsCustom) throws Exception{
		return itemsCustom;
	}
	
	@RequestMapping("/responseJson")
	public @ResponseBody ItemsCustom responseJson(ItemsCustom itemsCustom) throws Exception{
		return itemsCustom;
	}
}
LoginController.java
package liuxun.ssm.controller;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 登錄和退出
 * @author liuxun
 *
 */
@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";
	}
}
CustomDateConverter.java
package liuxun.ssm.controller.converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.core.convert.converter.Converter;

/**
 * 自定義日期轉換器
 * @author liuxun
 *
 */
public class CustomDateConverter implements Converter<String, Date> {

	@Override
	public Date convert(String source) {
		if (source!=null&&source.trim().length()>0) {
			// 進行日期轉換
			try {
				return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(source);
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
		
		return null;
	}

}
StringTrimConverter.java
package liuxun.ssm.controller.converter;

import org.springframework.core.convert.converter.Converter;
/**
 * 自定義去除字符串前後空格的轉換器
 * @author liuxun
 *
 */
public class StringTrimConverter implements Converter<String, String>{

	@Override
	public String convert(String source) {
		
		//去掉字符串兩邊空格,如果去除後爲空設置爲null
		if (source!=null) {
			source = source.trim();
			if (source.equals("")) {
				return null;
			}
		}
		
		return source;
	}

}
LoginInterceptor.java
package liuxun.ssm.controller.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * 測試攔截器1
 * @author liuxun
 *
 */
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("HandlerInterceptor2...postHandle");
	}

	//執行handler之後執行此方法
	//作爲系統統一異常處理,進行方法執行性能監控,在preHandler中設置一個時間點 在afterCompletion設置一個時間點 二者時間差就是執行時長
	//實現系統,統一日誌記錄
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
			throws Exception {
		System.out.println("HandlerInterceptor2...afterCompletion");
	}

}
CustomPropertyEditor.java
package liuxun.ssm.controller.propertyeditor;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.propertyeditors.CustomDateEditor;

/**
 * 自定義屬性編輯器
 * @author liuxun
 *
 */
public class CustomPropertyEditor implements PropertyEditorRegistrar {

	public void registerCustomEditors(PropertyEditorRegistry binder) {
		binder.registerCustomEditor(Date.class,
				new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), true));
	}

}
ValidGroup1.java
package liuxun.ssm.controller.validation;
/**
 * 校驗分組: 用於商品修改的校驗分組
 * @author liuxun
 *
 */
public interface ValidGroup1 {
	//接口不定義方法,就是隻標識 哪些校驗規則屬於ValidGroup1分組
}
CustomException.java
package liuxun.ssm.exception;

public class CustomException extends Exception {
	
	//異常信息
	private String message;

	public CustomException(String message) {
		super();
		this.message = message;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
}
CustomExceptionResolver.java
package liuxun.ssm.exception;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

public class CustomExceptionResolver implements HandlerExceptionResolver {

	//前端控制器DispatcherServlet在進行HandlerMapping、調用HandlerAdapter執行Handler過程中,如果遇到異常就會執行此方法
	@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 | IOException e) {
			e.printStackTrace();
		}
		
		return new ModelAndView();
	}

}
ItemsMapperCustom.java
package liuxun.ssm.mapper;

import java.util.List;

import liuxun.ssm.po.ItemsCustom;
import liuxun.ssm.po.ItemsQueryVo;

/**
 * 商品自定義Mapper
 * @author liuxun
 *
 */

public interface ItemsMapperCustom {
  // 商品查詢列表
	public List<ItemsCustom> findItemsList(ItemsQueryVo itemsQueryVo) throws Exception;
}
ItemsMapperCustom.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="liuxun.ssm.mapper.ItemsMapperCustom">
  <!-- 商品查詢的sql片段
  建議以單表爲單位定義查詢條件
  建議將常用的查詢條件都寫出來
   -->
   <sql id="query_items_where">
     <if test="itemsCustom!=null">
     	<if test="itemsCustom.name!=null and itemsCustom.name.trim().length >0">
     	 and name like '%${itemsCustom.name.trim()}%'
     	</if>
     	<if test="itemsCustom.id!=null">
     		and id = #{itemsCustom.id}
     	</if>
     </if>
   </sql>
   
   <!-- 商品查詢
   parameterType:輸入查詢條件
    -->
  <select id="findItemsList" parameterType="liuxun.ssm.po.ItemsQueryVo" resultType="liuxun.ssm.po.ItemsCustom">
	  SELECT * FROM items
	  <where>
	  	<include refid="query_items_where"/>
	  </where>
  </select>  
</mapper>
ItemsCustom.java
package liuxun.ssm.po;
/**
 * 商品信息的擴展類
 * @author liuxun
 *
 */
public class ItemsCustom extends Items {

}
ItemsQueryVo.java
/**
 * 商品的包裝類
 * @author liuxun
 *
 */
public class ItemsQueryVo {

	// 商品信息
	private ItemsCustom itemsCustom;

	// 定義一個List
	private List<ItemsCustom> itemsList;
	
	public ItemsCustom getItemsCustom() {
		return itemsCustom;
	}

	public void setItemsCustom(ItemsCustom itemsCustom) {
		this.itemsCustom = itemsCustom;
	}

	public List<ItemsCustom> getItemsList() {
		return itemsList;
	}

	public void setItemsList(List<ItemsCustom> itemsList) {
		this.itemsList = itemsList;
	}
	
}
修改自動生成的Items 添加校驗
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;

    //請輸入商品生產日期
    //通過groups指定此校驗屬於哪個分組,可以指定多個分組 之間用逗號隔開groups={ValidGroup1.class,ValidGroup2.class}
    @NotNull(message="{items.createtime.is.notnull}",groups={ValidGroup1.class})
    private Date createtime;
    ....
    ....
}
ItemsService
package liuxun.ssm.service;

import java.util.List;

import liuxun.ssm.po.ItemsCustom;
import liuxun.ssm.po.ItemsQueryVo;

/**
 * 商品Service接口
 * 
 * @author liuxun
 *
 */
public interface ItemsService {
	// 商品查詢列表
	public List<ItemsCustom> findItemsList(ItemsQueryVo itemsQueryVo) throws Exception;

	// 根據商品id查詢商品信息
	public ItemsCustom findItemsById(int id) throws Exception;
	
	// 更新商品信息
	/**
	 * 定義Service接口,遵循單一職責,將業務參數細化(不要使用包裝類型,比如Map)
	 * @param id  修改商品的id
	 * @param itemsCustom  修改商品的信息
	 * @throws Exception
	 */
	public void updateItems(Integer id,ItemsCustom itemsCustom) throws Exception;

}
ItemsServiceImpl.java
package liuxun.ssm.service.impl;

import java.util.List;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import liuxun.ssm.exception.CustomException;
import liuxun.ssm.mapper.ItemsMapper;
import liuxun.ssm.mapper.ItemsMapperCustom;
import liuxun.ssm.po.Items;
import liuxun.ssm.po.ItemsCustom;
import liuxun.ssm.po.ItemsQueryVo;
import liuxun.ssm.service.ItemsService;

public class ItemsServiceImpl implements ItemsService {

	// 注入mapper
	@Autowired
	private ItemsMapperCustom itemsMapperCustom;

	@Autowired
	private ItemsMapper itemsMapper;

	// 商品查詢列表
	public List<ItemsCustom> findItemsList(ItemsQueryVo itemsQueryVo) throws Exception {
		return itemsMapperCustom.findItemsList(itemsQueryVo);
	}

	public ItemsCustom findItemsById(int id) throws Exception {
		Items items = itemsMapper.selectByPrimaryKey(id);
		//如果查詢的商品信息爲空,拋出系統自定義異常
		if (items == null) {
			throw new CustomException("修改商品信息不存在");
		}
		// 在這裏隨着需求的變量,需要查詢商品的其他相關信息,返回到controller

		ItemsCustom itemsCustom = new ItemsCustom();
		// 將items的屬性拷貝到itemsCustom
		BeanUtils.copyProperties(items, itemsCustom);

		return itemsCustom;
	}

	public void updateItems(Integer id, ItemsCustom itemsCustom) throws Exception {
		// 寫業務代碼
		
		// 對於關鍵業務數據的非空校驗
		if (id == null) {
			// 拋出異常,提示調用接口的用戶,id不能爲空
			// ...
		}
		
		itemsMapper.updateByPrimaryKeySelective(itemsCustom);
	}

}
itemsList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>查詢商品列表</title>
<script type="text/javascript">
	function deleteItems(){
	  // 將form的action指向刪除商品的地址
	  document.itemsForm.action = "${pageContext.request.contextPath }/items/deleteItems.action";
	  // 進行form提交
	  document.itemsForm.submit();
	}
</script>
</head>
<body> 
當前用戶:${usercode }
<c:if test="${usercode != null}">
	<a href="${pageContext.request.contextPath}/logout.action">退出</a>
</c:if>

<form name="itemsForm" action="${pageContext.request.contextPath }/queryItems.action" method="post">
查詢條件:
<table width="100%" border=1>
	<tr>
		<td>
		商品類別:
		<select> 
			<c:forEach items="${itemsType }" var="item">
				<option value="${item.key }">${item.value }</option>
			</c:forEach>
		</select>
		</td>
	</tr>  
	<tr>
		<td><input type="submit" value="查詢"/>
		<input type="button" value="批量刪除" οnclick="deleteItems()"/>
		</td>
	</tr>
</table>
商品列表:
<table width="100%" border=1>
<tr>
	<td>選擇</td>
	<td>商品名稱</td>
	<td>商品價格</td>
	<td>生產日期</td>
	<td>商品描述</td>
	<td>操作</td>
</tr>
<c:forEach items="${itemsList }" var="item">
<tr>
    <td><input type="checkbox" name="delete_id" value="${item.id }"/></td>
	<td>${item.name }</td>
	<td>${item.price }</td>
	<td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
	<td>${item.detail }</td>
	
	<td><a href="${pageContext.request.contextPath }/items/editItems.action?id=${item.id}">修改</a></td>

</tr>
</c:forEach>

</table>
</form>
</body>

</html>
editItem.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>修改商品信息</title>

</head>
<body> 
<!-- 錯誤信息 -->
<div style="color: red;">
<c:forEach items="${errors }" var="error">
	${error.defaultMessage }<br>
</c:forEach>
</div>

<form id="itemForm" action="${pageContext.request.contextPath }/items/editItemSubmit.action" method="post" enctype="multipart/form-data">
<input type="hidden" name="id" value="${id }"/>
修改商品信息:
<table width="100%" border=1>
<tr>
	<td>商品名稱</td>
	<td><input type="text" name="name" value="${item.name }"/></td>
</tr>
<tr>
	<td>商品價格</td>
	<td><input type="text" name="price" value="${item.price }"/></td>
</tr>
<tr>
	<td>商品生產日期</td>
	<td><input type="text" name="createtime" value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>"/></td>
</tr>
<tr>
	<td>商品圖片</td>
	<td>
		<c:if test="${item.pic != null }">
			<img src="/pic/${item.pic}" width="100px" height="100px"/>
			<br/>
		</c:if>
		<input type="file" name="pictureFile"/>
	</td>
</tr>
<tr>
	<td>商品簡介</td>
	<td>
	<textarea rows="3" cols="30" name="detail">${item.detail }</textarea>
	</td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="提交"/>
</td>
</tr>
</table>

</form>
</body>

</html>
editItemsList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>查詢商品列表</title>
<script type="text/javascript">
	function updateItems(){
	  // 將form的action指向修改商品的地址
	  document.itemsForm.action = "${pageContext.request.contextPath }/items/editItemsListSubmit.action";
	  // 進行form提交
	  document.itemsForm.submit();
	}
</script>
</head>
<body> 
<form name="itemsForm" action="${pageContext.request.contextPath }/queryItems.action" method="post" >
查詢條件:
<table width="100%" border=1>
	<tr>
		<td>
		商品類別:
		<select> 
			<c:forEach items="${itemsType }" var="item">
				<option value="${item.key }">${item.value }</option>
			</c:forEach>
		</select>
		</td>
	</tr>  
	<tr>
		<td><input type="submit" value="查詢"/>
		<input type="button" value="批量修改提交" οnclick="updateItems()"/>
		</td>
	</tr>
</table>
商品列表:
<table width="100%" border=1>
<tr>
	<td>商品名稱</td>
	<td>商品價格</td>
	<td>生產日期</td>
	<td>商品描述</td>
	<td>操作</td>
</tr>
<c:forEach items="${itemsList }" var="item" varStatus="s">
<tr>
	<td><input type="text" name="itemsList[${s.index }].name" value="${item.name }"/></td>
	<td><input type="text" name="itemsList[${s.index }].price" value="${item.price }"/></td>
	<td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
	<td>${item.detail }</td>
	
	<td><a href="${pageContext.request.contextPath }/items/editItems.action?id=${item.id}">修改</a></td>

</tr>
</c:forEach>

</table>
</form>
</body>

</html>
jsontest.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>json測試</title>
<script type="text/javascript"
	src="${pageContext.request.contextPath}/js/jquery-1.4.4.min.js"></script>
<script type="text/javascript">
	// 請求json響應json
	function requestJson(){
		$.ajax({
			url:"${pageContext.request.contextPath}/requestJson.action",
			type:"post",
			contentType:"application/json;charset=utf-8",
			//請求json數據 使用json表示商品信息
			data:'{"name":"手機","price":1999}',
			success:function(data){
				alert(data.name);
			}
		});
	}
	// 請求key/value響應json
	function responseJson(){
		$.ajax({
			url:"${pageContext.request.contextPath}/responseJson.action",
			type:"post",
			//contentType:"application/json;charset=utf-8",
			//請求key/value數據 使用地址拼接表示商品信息
			data:"name=手機&price=1999",
			success:function(data){
				alert(data.name);
			}
		});
	}
</script>
</head>
<body>
	<input type="button" value="請求json響應json" οnclick="requestJson()"/>
	<input type="button" value="請求key/value響應json" οnclick="responseJson()"/>
</body>
</html>


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章