一 概述
1.什么是MVC?
Model-View-Controller,一種軟件設計思想,將軟件分為三層:模型層、視圖層、控制層。
- 模型層:負責處理具體的業務。
- 視圖層:與用戶交互的界面。
- 控制層:將請求分發給指定的業務邏輯。
2.什么是Spring MVC?
Spring MVC是Spring對MVC思想的一種實現,建立在Spring核心功能之上,功能強大,使用方便。
二 Spring MVC執行流程
1.DispatcherServlet
DispathcherServlet獲得請求以后,調用SpringMVC的各個組件來處理請求。
<servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
⑴contextConfigLocation
DispatcherServlet默認從WEB-INF目錄下加載SpringMVC的配置文件,可以通過屬性contextConfigLocation更改配置文件的位置,這一點同Spring與Web整合時遇到的問題及其處理方式相同。
⑵load-on-startup
默認情況下,Servlet在被請求時才實例化初始化,如果希望在服務器啟動時創建Servlet對象,可以通過<load-on-startup>標簽設定。
⑶<url-pattern>
請求:一切以從服務器端獲取資源為動機的行為都是請求,分為兩類:
- 顯式請求:用戶在瀏覽器端通過手動點擊或者輸入的方式直接發出的請求。
- 隱式請求:包含在顯示請求內部,不是由用戶直接觸發的請求,比如用戶手動請求一個頁面,這個請求就是顯式,而頁面從服務器加載圖片的行為不是用戶直接手動觸發的,屬於隱式請求。
一般寫成*加后綴的形式,如“*.do”,不能寫成這種形式“/*”,這種形式會把Web服務器接收到的請求全部轉發給DispatcherServlet,而SpringMVC容器中不一定存在對應的Handler,會因找不到對應的資源而報錯,比如無法加載HTML、JSP頁面。
配置“/”形式,無法處理頁面中的靜態請求,即HTML頁面以及JSP頁面中的.jsp、.css等靜態信息無法加載。兩種處理方法:
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping>
- 通過匹配靜態資源名稱的方式將頁面中所有靜態資源包含進來。
- 實現原理:Web服務啟動時加載兩個web.xml文件,一個在tomcat安裝目錄下的config文件夾中,稱作默認容器,一個是項目中的,稱作自定義容器。當兩個容器出現沖突信息時,以項目中的為准。在默認容器中,定義了一個DefaultServlet,servlet-name為default。DefaultServlet用於為服務器中的其他程序提供靜態資源。
第二種方式:在Spring配置文件中加入,底層也是調用了DefaultServlet:
<mvc:default-servlet-handler/>
2.執行流程
瀏覽器發出請求,Web服務器解析該請求,如果匹配DispatcherServlet的映射路徑,就將請求轉發給DispatcherServlet。DispatcherServlet獲得請求以后,首先判斷該請求是否是文件上傳請求,然后將請求轉發給HandlerMapping,HandlerMapping根據映射關系找到對應的Handler,並將Handler與HandlerInterceptor封裝成一個HandlerExecutionChain對象返回給中央調度器,中央調度器根據Handler獲取HandlerAdaptor,HandlerAdaptor調用對應的Handler執行業務邏輯,處理完畢返回一個ModelAndView對象給中央處理器,中央處理器將ModelAndView對象轉發給ViewResolver視圖解析器,處理完畢返回一個View對象,中央處理器再將View對象轉發給View視圖渲染器,渲染完畢,中央處理器將視圖響應給瀏覽器。
3.ModelAndView
一個溝通業務邏輯與視圖的類,因為包含最終視圖的全部信息,又不是最終視圖,被稱作邏輯視圖,其中既包含早已建好的視圖模板,也包含需要傳入視圖的動態參數Model。
三 配置式開發
1.配置式開發
由程序員定義SpringMVC各部分具體執行類的開發模式,叫做配置式開發,而不是完全采用系統默認的類。
2.處理器
繼承抽象類或實現接口來自定義處理器,可用的抽象類與接口:
- Controller:接口。
- HttpRequestHandler:接口。
- AbstractController:繼承類,通過屬性supportedMethods可限定請求方式,GET或者POST。
3.HandlerMapping
訪問資源時使用的不是資源的全限定性類名或者其他可以直接確定資源的方式,而是采用url,這樣就需要在訪問方式與與資源之間建立起一對一的關系,這種關系就是映射關系,HandlerMapping就負責創建與解析這種關系,根據訪問方式確定處理器。
- BeanNameUrlHandlerMapping:默認的處理器映射器,url與beanName相同。
- DefaultAnnotationHandlerMapping:注解開發時默認的處理器映射器。
- SimpleUrlHandlerMapping:自定義url,在url與beanName之間建立映射關系:
-
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/simpleUrl.do"value="myController" /> <entry key="/simpleUrl01.do"value="myController" /> </map> </property> </bean>
如果顯式地配置了處理器映射器,那么默認的映射器不再起作用。
HandlerMapping返回一個封裝了Handler與HandlerInterceptor的HandlerExecutionChain給中央調度器。
4.解析方法
從url中解析出方法:
- InternalPathMethodNameResolver:默認解析方法,請求url語法格式:/xxxx/methodName.do。
- ParamterMethodNameResolver:url語法格式/xxx?action=methodName,action為paramName,默認action,可以自定義。
- PropertiesMethodNameResolver:請求可以采用/*.do的格式,在PropertiesMethodNameResolver中通過屬性mappings建立請求url與方法的映射關系。
5.HandlerAdapter
⑴背景
Handler有多種類型,中央調度器不能同時處理,而是通過調用對應的適配器來處理,這樣分工明確,中央調度器結構清晰。
⑵底層實現
采用適配器模式:根據處理器實現的接口獲取對應的適配器,由適配器調用處理器執行其中的方法。
6.ModelAndView
⑴model與view
- model:數據對象,包含將要傳入頁面中的動態參數。Model對象最好不要作為異常處理方法的形參,因為系統在向異常處理方法傳遞Model對象時存在很大的不確定性。
- view:視圖對象,封裝了顯示的視圖。
⑵3種創建方式:
-
ModelAndViewmv=new ModelAndView(); mv.setViewName("內部視圖相對於項目的路徑");
- 使用RedirectView類創建外部視圖:
-
<bean id="taobao"class="org.springframework.web.servlet.view.RedirectView"> <property name="url" value="http://www.taobao.com" /> </bean>
- 使用JstlView創建內部視圖:
-
<bean id="innerResource"class="org.springframework.web.servlet.view.JstlView"> <property name="url" value="/WEB-INF/firstDemo/welcome.html" /> </bean>
7.ViewResolver
不同的定義視圖的方式對應不同的視圖解析器:
- InternalResourceViewResolve:默認的視圖解析器,根據路徑解析視圖,不僅可以解析站內視圖,還可以解析站外視圖。
- BeanNameViewResolver:在配置文件中利用RedirectView或者JstlView類封裝視圖,在java代碼中使用beanName返回視圖,BeanNameViewResolver根據beanName解析視圖。
如果視圖較多,可以單獨放在一個文件中,SpringMVC提供了兩種定義視圖對象的文件:xml、properties。
⑴XmlViewResolver
將視圖對象放在XML文件中:
<bean class="org.springframework.web.servlet.view.XmlViewResolver"> <property name="location"value="classpath:com/springmvc/viewResolver/myViews.xml"/> </bean>
⑵ResourceBundleViewResolver
將視圖對象放在屬性文件中,屬性文件只能放在類路徑下:
viewName.(class)=RedirectView/JstlView
viewName.url=http://xxxxxx
將屬性文件加載到配置文件中並解析視圖:
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basenames" value="myViews" /> </bean>
- baseNames:類路徑下放置視圖的屬性文件文件名,不包括擴展名。
⑶優先級
- 允許配置多個視圖解析器,默認情況下,在配置文件中上方的優先級高於下方的,默認的視圖解析器InternalResourceViewResolver級別最低。
- 可以使視圖解析器的優先級不受配置順序的約束:在配置視圖解析器時,通過order屬性設定優先級,int類型,大於0,值越小,優先級越高。
四 處理器的注解式開發
1.加載
Spring注解采用被動加載,必須指明采用注解的組件位置:
<context:component-scan base-package=""/>
2.常用注解
⑴@Controller
標在類名上,表明該類是一個處理器。
⑵@RequestMapping("")
標在類名上,用作請求url的公共開頭部分,可稱作命名空間,與方法上的url連接時沒有“/”系統自動增加“/”,所以用以連接的“/”可以省略。
- /*:代表一級目錄。
- /**:代表任意級目錄。
⑶@RequestMapping(value="")
標在方法上,用來定義訪問該處理方法的請求url,其他屬性:
- method:限定請求方式,如method=RequestMethod.POST。
- params:限定請求必須攜帶的參數。
-
params={"paramName01","paramName02"}//請求中必須出現參數paramName01與paramName02 params={"!paramName01","paramName02"}//請求中不能出現參數paramName01,必須出現參數paramName02 params={"paramName01=value02"}//請求中必須出現參數paramName01,並且其值必須為value01 params={"paramName01!=value02"}//請求中必須出現參數paramName01,並且其值不能為value01
3.自定義方法
自定義處理方法沒有任何限制,可以有返回值,也可以沒有返回值,可以有形參,也可以沒有形參。可以有的形參:
- HttpServletRequest
- HttpServletResponse
- HttpSession
- Model
- 請求參數或者請求參數的封裝類
- BindingResult
五 表單輸入注入處理方法
1.分散注入
表單輸入名與方法形參名相同時,自動注入;不相同時,需要在表單輸入名與形參名之間建立映射關系:
public ModelAndViewdoFirst(@RequestParam("name") String pname, int age) {}
2.封裝注入
將表單輸入注入到封裝對象中,只需要將處理方法的形參設定為封裝類型對象即可。
3.域屬性注入
如果封裝對象中包含域屬性,表單在設定輸入名稱時域屬性的屬性通過域屬性引用的形式給定。
<form action="rph03.do" method="post"> 姓名:<input type="text" name="name"><br> 年齡:<input type="text" name="age"> <br> 學校:<input type="text"name="school.name"><br> 地址:<input type="text" name="school.addr"><br> <input type="submit" value="提交"> </form>
- school是域屬性,name/addr是域屬性的屬性。
4.pathVariable
在請求路徑中加入變量,處理方法從路徑中獲取參數值:
@RequestMapping("/{pname}/rph04.do") public ModelAndView doFourth(@PathVariable(name = "pname") String name, int age){}
- name不僅作為路徑變量,而且為方法的形參name賦值。實質上,就是將請求參數作為路徑的一部分,包含在路徑當中。
- @PathVariable:用來建立路徑變量與方法形參之間的映射關系。
5.其他
- 不是所有的表單輸入都必須被接收,也不是處理方法中的每一個參數都必須被注入。
- SpringMVC將接收表單輸入的封裝對象保存到request作用域中,分散接收變量沒有保存到request作用域中。
六 處理器返回值
1.String
- 視圖路徑。
- 視圖beanName。
2.void
可以向頁面響應JSON字符串。
3.Object
將響應內容通過響應體發送給瀏覽器。
⑴環境搭建
- 在配置加入<mvc:annotation-driven/>。
- 在方法上注解@ResponseBody,表示將返回值放入響應體中。
⑵基本數據類型與字符串
解決中文亂碼問題,在@RequestMapping中加入屬性produces="text/html;charset=UTF-8",限定響應內容類型與編碼。
⑶自定義類、Map集合、List集合
通過響應體以JSON字符串的格式返回,需要導入jackson架包,目的是在容器初始化時創建HttpMessageConverter轉化器,將返回值轉化為json字符串。
4.默認處理
默認情況下,返回的字符串類型的數據被當作視圖名處理,如果希望保存到響應體中,必須在處理方法上加注解@ResponseBody。
七 轉發與重定向
- 請求轉發時,Model中的數據自動保存在request作用域中。
- 重定向時,Model中的數據自動保存在param環境信息中。
- return "forward:/xxx":轉發到頁面或者另一個處理方法,forward是默認值,可以省略。
- return "redirect:/xxxx":重定向到頁面或者另一處理方法。
八 異常處理
1.跳轉到頁面
使用內置類SimpleMappingExceptionResolver,在異常類型與跳轉頁面間建立映射關系,發生異常時,跳轉到指定頁面:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="defaultErrorView" value="/Exception/error.jsp" /> <property name="exceptionMappings"> <props> <prop key="com.springmvc.exception.AgeException">/Exception/ageException.jsp</prop> <prop key="com.springmvc.exception.NameException">/Exception/nameException.jsp</prop> </props> </property> </bean>
- defaultErrorView:為沒有指定跳轉頁面的異常類型設定一個默認的跳轉頁面。
- exceptionMappings:為異常定制跳轉頁面。
2.接口式異常處理器
實現HandlerExceptionResolver接口,在配置文件中配置后即可使用,不僅可以處理方法內部的異常,還可以處理方法調用時的異常,如類型轉化異常。
3.注解式異常處理器
⑴創建
@ExceptionHandler(MyException.class) public ModelAndView handleException(HttpServletRequest request,Exception ex)
自定義異常處理器方法中不能有Model類型參數, 因為Model只有在作為處理器方法的形參時才被實例化。
⑵可處理的異常范圍
只對所在處理器中的方法有效,不僅可以處理方法內部發生的異常,也可以處理調用處理方法時發生的異常,比如類型轉化異常。
4.優先級
如果同時創建了接口式異常處理器與注解式異常處理器,采用注解式異常處理器,接口式不起作用。
5.選擇依據
根據需要對異常進行處理的深度,選擇異常處理方式:
- 如果當異常發生時,僅僅需要輸出異常信息,跳轉到指定頁面,可以采用配置內置的異常處理器的方式。
- 如果需要進一步處理異常,可以采用接口式或者注解式異常處理器。
6.異常處理過程
異常處理器被調用的過程是一個請求轉發的過程。
7.異常
異常處理器只能捕獲拋出的異常,在方法內部處理的異常無法獲取,因為異常已經在方法內部處理了,不需要再處理。
九 數據類型轉換
1.類型轉換器選擇
根據表單輸入與處理器方法形參之間的映射關系,將表單輸入按照對應的形參類型進行轉換。
2.內置轉換器的作用
SpringMVC為常見的java類型提供了類型轉換器,轉化是由框架默認完成的,不需要程序員參與,主要是將字符串轉換為基本數據類型,如將字符串轉化為int、double等。
3.接口式類型轉換器器
實現Converter接口自定義類型轉化器,在SpringMVC中類型轉換器統一由ConversionServiceFactoryBean管理,將自定義的類型轉換器添加到管理器中:
<bean id="myDateConverter"class="com.springmvc.conversion.MyDateConverter" /> ------------生成的不是工廠對象,而是由工廠類生成轉換服務對象----------- <bean id="conversionService"class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="myDateConverter"/> </set> </property> </bean>
將轉換服務對象添加到注解驅動中, 一些不常用的功能統一由注解驅動管理:
<mvc:annotation-driven conversion-service="conversionService" />
接口式類型轉換器中發生的異常不能被接口式異常處理器處理,運行時異常可以被注解式異常處理器處理,受查異常無法被注解式異常處理器處理。
4.注解式類型轉換器
返回值void,方法形參WebDataBinder類型:
@InitBinder//表明該方法用作類型轉化方法
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class,new MyPropertiesEditor());//自定義屬性編輯器
}
- 自定義屬性編輯器MyPropertiesEditor,繼承PropertiesEditor,提交格式不在可轉換格式之列時,必須拋出異常TypeMismatchException,不然無法啟動異常處理器。主要實現同接口式相同,調用setValue方法保存轉換結果。
- 注解式類型轉化器必須在不可轉換時顯式拋出異常,才能啟動異常處理器。
5.優先級
如果同時創建了接口式與注解式類型轉換器,注解式優先起作用。
6.異常
當處理器方法分散接收表單輸入時,無論形參時基本數據類型,還是對象類型,轉化失敗,都將拋出TypeMismatchException。
當處理器方法以封裝形式接收表單輸入時,基本數據類型轉化失敗,拋出BindException,對象類型轉換失敗,拋出IllegalArgumentException。
7.數據回顯與顯示提示信息
⑴什么是數據回顯?
當表單輸入發生類型轉化錯誤或者其他原因導致驗證未通過時,視覺上頁面不跳轉,用戶輸入數據不丟失,原樣顯示。
⑵原理
數據回顯與顯示提示信息都是利用異常處理機制,當發生類型轉化異常或者其他驗證異常時,啟動異常處理器,經異常處理器跳轉到輸入頁面,同時將輸入數據與提示信息顯示在輸入頁面中。
⑶具體實現
- 在表單設計時,使用EL表達式插入用於回顯的內容。
- 用於數據回顯的異常處理器必須限定異常類型,類型轉換過程可能拋出三種異常:TypeMismatchException/BindException/IllegalArgumentException,只能接收這三種異常,不然發生其他異常時也觸發異常處理器,跳轉到輸入頁面:
-
@ExceptionHandler({ TypeMismatchException.class, BindException.class, IllegalArgumentException.class }) public void handleException(HttpServletRequest request, Exception ex) { }
十 數據驗證
1.前提
數據驗證是在類型轉換成功的基礎上對表單輸入的進一步驗證,如果類型轉換未通過,不會進行數據驗證。
2.Hibernate Validator
SpringMVC支持多種數據驗證實現,目前項目中主要使用Hibernate Validator,使用時需導入Hibernate Validator架包。
3.域模型
Hibernate Validator是基於domain model的,必須在域模型屬性上添加約束,使用時只有注解@Validated開啟約束后才起作用:
- @NotNull(message=""):非空約束,用於約束字符串。
- @NotEmpty():既不可以為空字符串,也不可以為null。
- @Size(max="",min="",message="取值范圍{min}-{max}"):約束字符串長度。
- @Max(value=""):最大值約束。
- @Min(value=""):最小值約束。
- @Post:約束日期,表示只能輸入相對於當前過去的日期。
- @Future:約束日期,表示只能輸入相對於當前未來的日期。
- @Range(max="",min=""):約束取值范圍。
- @Pattern(regexp=""):要求輸入內容必須匹配給定的正則表達式。
- @DateTimeFormat(pattern = "yyyy-MM-dd"):使用指定的格式將字符串轉化為日期。
4.配置文件編寫
注冊驗證器:
<bean id="validator"class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass"value="org.hibernate.validator.HibernateValidator" /> </bean> <mvc:annotation-driven validator="validator" />
Hibernate Validator抑制接口式類型轉換器,即一旦創建了Hibernate Validator,接口式類型轉換器不再起作用。
5.處理器方法編寫
⑴形參
- @Validated:處理器采用域模型接收表單輸入,形參前添加注解@Validated,表明此處的域模型對象需要驗證。
- BindingResult:在形參中加入BindingResult對象,獲取表單輸入注入狀況,既包含類型轉換狀況,也包含數據驗證狀況。
⑵一般步驟
boolean b=result.hasFieldErrors();//判斷注入過程中是否發生異常 FieldError nameFieldError=result.getFieldError("name");//獲取向某個屬性注入表單輸入時發生的異常 String message=nameFieldError.getDefaultMessage();//獲取發生異常時的提示信息
- 發生驗證異常時的提示信息是程序員在域模型中定義的message,可以直接輸出。
- 發生類型轉換異常時默認采用系統自定義的信息,英文,較長,對用戶不友好,因此發生轉換異常時需要自定義提示信息。由於發生類型轉換異常的提示信息,大多以“Failed to convert property value of type”開頭,據此可以判斷異常類型。
⑶對應
一個域模型對象對應一個BindingResult,為了保證對應關系,在處理器方法中域模型形參與BindingResult兩個形參緊鄰,中間不能有其他參數,並且域模型在前。
十一 文件上傳
1.具體過程
DispatcherServlet獲取請求以后,首先判斷該請求是否是文件上傳請求,如果是則采用文件上傳時特有的操作。基本過程與處理非文件上傳請求時相同,加入了特有操作。
2.CommonsMultipartResolver
該類定義了很多實際開發時需要調整的參數,需要在配置文件中配置,由DispatcherServlet調用,引用變量名multipartResolver不能更改。
- 設定編碼方式,解決文件名為中文時的亂碼問題。
- 定義單次允許上傳的最大文件值。
3.MultipartFile
處理方法形參中必須包含一個MultipartFile參數,接收上傳的文件,變量名為表單中文件名,多個文件上傳時,參數為數組。
4.一般步驟
文件上傳前判斷是否選擇了文件,不然導致虛假上傳。即使在沒有文件上傳時MultipartFile對象依然被實例化,因此不能根據對象是否為null判斷是否選擇了文件,可以根據文件大小getSize判斷。
String filename=mf.getOrginalFilename();//獲取文件名getName獲取的表單中的文件名 ............................................ mf.transferTo(file);
十二 攔截器
1.創建
SpringMVC只提供了一種創建攔截器的方法:接口式,即實現HandlerInterceptor接口。接口中共有三個方法:
⑴preHandler
攔截器前置通知,在處理器方法執行前執行,用於決定是否繼續執行請求。返回true,繼續執行后面的攔截器或者處理方法,不論后面的執行狀況如何,afterCompletion都會被執行。返回false終止執行,請求結束,不會執行afteCompletion。
⑵postHandler
攔截器后置通知,在處理器方法執行完畢后執行,主要用於向ModelAndView中添加數據。
⑶afterCompletion
攔截器渲染后通知,主要用於關閉資源:
- 執行條件:前置通知返回true。
- 執行時機:在視圖渲染完畢之后執行。
2.注冊
<mvc:interceptors> <mvc:interceptor> <mvc:mappingpath="/*" /> <ref bean="oneInterceptor" /> </mvc:interceptor> <mvc:interceptor> <mvc:mappingpath="/*" /> <bean class="com.springmvc.interceptor.TwoInterceptor" /> </mvc:interceptor> </mvc:interceptors>