一、SpringMVC原理解析
1、我們首先分析一下整個請求處理的流程:
①在B/S架構的系統中,使用者首先從瀏覽器中發出Http請求(請求中會包含使用者的請求內容資訊或者表單資訊),然後首先經過前端控制器(DispatcherServlet)進行處理,
②然後前端控制器需要接觸處理器對映器知道自己使用哪一個處理器處理請求資訊,
③然後處理器對映器會返回給前端控制器一個處理器執行鏈,
④前端控制器通過處理器介面卡去執行處理器,然後讓處理器介面卡返回給自己模型和檢視,
⑤處理器介面卡去呼叫相應的處理器
⑥執行後的處理器返回給處理器介面卡資訊,具體就是返回處理結果(ModelAndView)
⑦處理器介面卡得到模型檢視(ModelAndView)之後,將之返回給前端控制器
⑧前端控制器自己本身不對ModelAndView進行解析,而是交給檢視解析器進行檢視解析
⑨檢視解析器完成檢視解析後,將檢視(View)返回給前端控制器
⑩前端控制器得到view後,會交給檢視進行渲染,具體就是jsp、freemaker等等,最後響應給使用者
2、通過上面的解釋和線面圖例的理解,我們可以對SpringMVC這個框架的處理流程有一個大致的瞭解。上面只是介紹了一部分元件,我們下面可以簡單的介紹各個元件的功能
①前端控制器DispatcherServlet:作為整個SpringMVC的中央處理器,可以發現整個流程中大部分都經過它。使用者請求首先到達前端處理器(MVC模式中C層),作為整個流程的中心,由他呼叫其他元件,其存在大大降低了各個元件的耦合性
②處理器對映器HandlerMapping:顧名思義,處理器對映器就是根據使用者請求找到相應的處理器Handler進行處理請求,SpringMVC中提供配置檔案方式、介面方式、註解方式等等實現不同的對映方式
③處理器介面卡HandlerAdapter:不同於處理器對映器,處理器介面卡的作用是對處理器進行執行(處理器對映器是找到處理器),HandlerAdapter可以通過擴充套件介面卡對多種型別的介面卡進行執行
④處理器Handler:處理器也可以說是後端控制器,需要由自己根據處理器介面卡的規範來進行編寫,在DispatcherServlet的控制下對具體的使用者請求進行處理
⑤檢視解析器ViewResolver:負責將結果生成View檢視,ViewResolver首先根據邏輯檢視名解析成物理檢視名即具體的頁面地址,再生成View檢視物件,最後對View進行渲染將處理結果通過頁面展示給使用者。 springmvc框架提供了很多的View檢視型別,包括:jsp、freemarker、pdf等。
二、搭建SpringMVC程式
1、首先我們需要知道SpringMVC是Spring的一個部分,所以在編寫程式開始的時候需要匯入SpringMVC的jar包
2、在匯入springmvc的相關jar包之後,需要在web.xml中配置前端控制器DispatcherServlet,在配置前端控制器的時候,我們自己需要手動修改配置springmvc的配置檔案以及他的路徑
下面就是在web.xml中配置到的前端控制器
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" 5 version="4.0"> 6 <!--配置前端控制器--> 7 <servlet> 8 <servlet-name>SpringMvc</servlet-name> 9 <servlet-class> 10 org.springframework.web.servlet.DispatcherServlet 11 </servlet-class> 12 <!-- 13 配飾SpringMVC的配置檔案(處理器對映器、介面卡等) 14 註明需要這樣配置的原因:自己配置contextConfigLocation,就不會自己預設載入/WEB-INF/下面的dispatch-servlet.xml 15 --> 16 <init-param> 17 <param-name>contextConfigLocation</param-name> 18 <param-value>classpath:springmvc.xml</param-value> 19 </init-param> 20 </servlet> 21 <servlet-mapping> 22 <servlet-name>SpringMvc</servlet-name> 23 <url-pattern>*.do</url-pattern> 24 </servlet-mapping> 25 </web-app>
3、下面說明一下上面配置的url-pattern的問題,在SpringMVC中,DispatcherServlet的攔截方式有下面兩種
①*.do、*.action:表示攔截指定字尾的url,這種方式對於伺服器端的靜態資源(jsp、css、image等)不會攔截
②/ :表示攔截所有資源,這種攔截方式的設計可以實現RESTful風格,後面會介紹到
需要注意的是:不能使用/*(這種方式下,當請求到action後,action處理完返回ModelAndView的jsp資訊時,又會被攔截,導致不能根據jsp對映成功)
4、我們上面修改了SpringMVC的配置檔案,所以我們下來需要自己修改SpringMVC的配置檔案。而在springmvc.xml中,我們需要配置處理器對映器,處理器(Handler),處理器介面卡、檢視解析器
①配置處理器對映器,處理器對映器會根據配置的beanname找到相應的URL,找到的就是制定的Handler,然後交給Handler進行處理
1 <!-- 2 處理器對映器 3 將bean的NAME作為url進行查詢,需要配置handler的時候指定bean的NAME(即url) 4 --> 5 <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
②配置處理器介面卡,我們配置的是SimpleControllerHandlerAdapter,檢視該類的原始碼,會發現下面這個方法,這個方法就是判斷我們自己編寫的處理器(Handler)是否滿足條件(實現Controller介面),所以在接下來編寫Handler時需要實現Controller介面以及其中的方法
下面這是處理器介面卡的配置
1 <!-- 2 處理器介面卡 3 所有處理器介面卡都實現了Handler Adapter這個介面,通過觀察SimpleControllerHandlerAdapter中的support方法可以發現, 4 我們自己需要實現Controller介面 才能執行 5 --> 6 <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
③根據上面講的,我們就需要自己編寫Handler,我們先檢視一下Controller介面的程式碼,發現Controller只有一個方法,即請求處理然後返回ModelAndView物件
所以下來我們就自己實現這個方法,然後返回ModelAndView(包括其中的資料,和路徑資訊):
1 package cn.test.springmvc.controller; 2 3 import cn.test.springmvc.po.Product; 4 import org.springframework.web.servlet.ModelAndView; 5 import org.springframework.web.servlet.mvc.Controller; 6 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 import java.util.ArrayList; 10 import java.util.List; 11 12 /** 13 * 實現Controller的Handler 14 */ 15 public class ItemController implements Controller { 16 17 @Override 18 public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { 19 20 //模擬資料 21 List<Product> products = new ArrayList<>(); 22 23 Product product1 = new Product(); 24 product1.setPname("筆記本"); 25 product1.setShop_price(123); 26 products.add(product1); 27 28 Product product2 = new Product(); 29 product2.setPname("手機"); 30 product2.setShop_price(123); 31 products.add(product2); 32 33 //返回ModelAndView 34 ModelAndView modelAndView = new ModelAndView(); 35 //想模型檢視中新增資料 36 modelAndView.addObject("products", products); 37 38 //指定檢視 39 modelAndView.setViewName("/WEB-INF/items/itemsList.jsp"); 40 return modelAndView; 41 } 42 }
我們自己編寫好Handler之後,就需要根據處理器對映器的要求,在springmvc.xml中配置Handler
1 <!--配置Handler--> 2 <bean name="/queryItemList.do" class="cn.test.springmvc.controller.ItemController"></bean>
配置的beanname就會處理器對映器進行對映,然後執行相應的處理器
④上面我們返回的是jsp頁面資訊,所以下倆就需要配置檢視解析器,解析jsp。
1 <!--檢視解析器--> 2 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"></bean>
我們來看一下InternalResourceViewResolver這個類中的程式碼,該類預設支援jstl解析,所以我們就匯入jstl的jar包,正好解釋了前面第一步中導包(其中含有jstl包)
private static final boolean jstlPresent = ClassUtils.isPresent("javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
⑤在上面的Handler中,我們返回了一個jsp頁面,下面就簡單的根據返回資訊編寫一張簡單的jsp頁面
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> 5 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 6 <html> 7 <head> 8 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 9 <title>查詢商品列表</title> 10 </head> 11 <body> 12 <form action="${pageContext.request.contextPath }/item/queryItem.action" method="post"> 13 查詢條件: 14 <table width="100%" border=1> 15 <tr> 16 <td><input type="submit" value="查詢"/></td> 17 </tr> 18 </table> 19 商品列表: 20 <table width="100%" border=1> 21 <tr> 22 <td>商品名稱</td> 23 <td>商品價格</td> 24 <td>操作</td> 25 </tr> 26 <c:forEach items="${products }" var="item"> 27 <tr> 28 <td>${item.pname }</td> 29 <td>${item.shop_price }</td> 30 31 <td><a href="${pageContext.request.contextPath }/item/editItem.do?id=${item.pid}">修改</a></td> 32 33 </tr> 34 </c:forEach> 35 36 </table> 37 </form> 38 </body> 39 40 </html>
jsp測試頁面
⑥然後在IDEA中部署Tomcat,進行測試
三、基於非註解方式進行編寫
1、處理器對映器的非註解配置
上面使用的是BeanNameURLHandlerMapping來進行對映。其簡單的執行過程就是:BeanNameUrlHandlerMapping根據請求的url與spring容器中定義的bean的name進行匹配,從而從spring容器中找到bean例項。下面我們使用簡單對映器(SimpleSimpleUrlHandlerMapping)進行處理器對映器的配置
其中prop中的key就是配置好url,而value就是我們配置的Handler的id值:<bean id=”test” name=”/queryItemList.do” class=”cn.test.springmvc.controller.ItemController”></bean>
<!--非註解的處理器對映器--> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!--對指定的Controller進行對映--> <prop key="test1.do">test</prop> </props> </property> </bean>
然後我們重啟啟動專案,在瀏覽器中輸入http://localhost:8080/springmvc1/test1.do,就可以同樣實現上面BeanNameUrlHandlerMapping實現的這個功能。
總結一下就是:simpleUrlHandlerMapping是BeanNameUrlHandlerMapping的增強版本,它可以將url和處理器bean的id進行統一對映配置。
2、處理器介面卡的非註解配置,我們使用HttpRequestHandlerAdapter來實現,並且通過觀察該類可以發現,該介面卡所要求的處理器是需要實現HttpRequestHandler介面的
下面就是具體的實現方法
1 package cn.test.springmvc.controller; 2 3 import cn.test.springmvc.po.Product; 4 import org.springframework.web.HttpRequestHandler; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 import java.io.IOException; 10 import java.util.ArrayList; 11 import java.util.List; 12 13 public class ItemHandler implements HttpRequestHandler { 14 @Override 15 public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { 16 //模擬資料 17 List<Product> products = new ArrayList<>(); 18 19 Product p1 = new Product(); 20 p1.setPid(1); 21 p1.setPname("test1"); 22 p1.setShop_price(12); 23 products.add(p1); 24 25 Product p2 = new Product(); 26 p1.setPid(2); 27 p2.setPname("test2"); 28 p2.setShop_price(13); 29 products.add(p2); 30 31 httpServletRequest.setAttribute("products", products); 32 33 httpServletRequest.getRequestDispatcher("/WEB-INF/items/itemsList.jsp").forward(httpServletRequest,httpServletResponse); 34 } 35 }
2、然後同樣可以再瀏覽器中輸入http://localhost:8080/springmvc1/test2.do得到測試結果
四、基於註解方式進行開發
1、註解方式和非註解方式開發的不同就是處理器對映器和處理器介面卡的配置不同(而且需要注意,如果使用註解的方式進行,那麼需要同時使用註解方式的處理器介面卡和處理器對映器,不能一個使用非註解的一個使用註解的),我們首先配置基於註解的HandlerMapping
1 <!--基於註解的處理器對映器--> 2 <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>
2、然後配置基於註解的處理器介面卡
1 <!--基於註解的處理器介面卡--> 2 <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>
3、既然是基於註解的方式進行,那麼可以不繼承或者實現某個特定的介面來進行編寫Handler。只需要注意使用Controller註解來表示編寫的類是一個處理器,同時編寫的方法的返回型別應該是前端控制器所需要的ModelAndView,下面就是簡單的一個Handler的編寫
1 package cn.test.springmvc.annotation; 2 3 import cn.test.springmvc.po.Product; 4 import org.springframework.stereotype.Controller; 5 import org.springframework.web.bind.annotation.RequestMapping; 6 import org.springframework.web.servlet.ModelAndView; 7 8 import java.util.ArrayList; 9 import java.util.List; 10 11 @Controller 12 public class QueryListHandler { 13 14 @RequestMapping("/queryList.do") 15 public ModelAndView queryList() throws Exception { 16 17 //模擬資料 18 List<Product> productList = new ArrayList<>(); 19 20 Product p1 = new Product(); 21 p1.setPname("AnnotationTest1"); 22 p1.setShop_price(12); 23 24 Product p2 = new Product(); 25 p2.setPname("AnnotationTest2"); 26 p2.setShop_price(12); 27 productList.add(p1); 28 productList.add(p2); 29 30 ModelAndView modelAndView = new ModelAndView(); 31 //想模型檢視中新增資料 32 modelAndView.addObject("productList", productList); 33 34 //指定檢視 35 modelAndView.setViewName("/WEB-INF/items/itemsList.jsp"); 36 return modelAndView; 37 } 38 }
4、然後在瀏覽器中進行測試如下:
五、SpringMVC執行過程
1、按照開始介紹的SpringMVC的執行原理框架圖來進行分析,首先請求到達前端控制器,這裡會呼叫一個叫做doDispatcher的方法
2、前端控制器中需要找到處理器對映器,即前端控制器將請求url交給處理器對映器,從而根據url得到handler
3、處理器對映器包裝請求資訊,返回給前端控制器一個處理器執行鏈
4、前端控制器呼叫HandlerAdapter(處理器介面卡)對HandlerMapping找到Handler進行包裝、執行。HandlerAdapter執行Handler完成後,返回了一個ModleAndView(springmvc封裝物件)。這個過程就分為兩步,第一步就是找到處理器介面卡
第二步就是找到處理器介面卡呼叫響應的處理器Handler。然後返回給前端控制器一個ModelAndView
5、這樣現在前端控制器就得到了ModelAndView,下面就是交給檢視解析器進行解析,解析得到檢視View
6、前端控制器將模型資料進行渲染,然後放在reques中