1.DispatcherServlet入門
(1) Spring MVC是以前端控制器模式(即圍繞著一個中央的Servelt, DispatcherServlet)進行設計的,這個DispatcherServlet為請求的處理提供了一個共用的演算法,即它都會將實際的請求處理作業委托給那些可配置的組件進行執行,說白了,DispatcherServlet的作用就是進行統一調度,并控制請求的處理流程,和其他的Servlet一樣,DispatcherServlet需要根據Servlet規范,使用基于Java的配置或在web.xml中進行宣告,與此同時,DispatcherServlet會使用Spring相關配置來發現它在請求映射、視圖決議、例外處理等方面所需要的組件,而實際的作業也會交由這些組件進行執行,下面列出了注冊DispatcherServlet的一些方式
@Configuration
@ComponentScan("cn.example.springmvc.boke")
public class WebConfig {
}
//使用基于Java的配置,注冊并初始化一個DispatcherServlet
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//宣告一個Spring-web容器
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(WebConfig.class);
//創建并注冊DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ctx);
//動態的添加Servlet
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", servlet);
registration.setLoadOnStartup(1);
//指定由DispatcherServlet攔截所有請求(包括靜態資源,但不攔截.jsp)
registration.addMapping("/");
}
}
上述是基于Java的配置,我們還可以基于web.xml來配置DispatcherServlet,如下
<web-app ....>
<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:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
(2) 現在DispatcherServlet有了,我們還需要@Controller與@RequestMapping注解,來標注某個訪問應該由誰進行處理,如下,而到此為止,我們就已經完全不需要撰寫HttpServlet相關的內容了,可見通過DispatcherServlet幫助我們免去了冗雜的Servlet映射配置
@Controller
public class DemoController {
@RequestMapping("/demo")
@ResponseBody
public String get(HttpServletRequest request) {
return "aaa";
}
}
啟動容器,訪問 http://localhost:8080/springmvc/demo, 頁面顯示aaa,說明訪問正常,這便是我們的第一個Spring MVC專案
2.Spring容器的層次結構
(1) 根容器與Servlet子容器
通常情況下,一個web應用中有一個唯一的WebApplicationContext容器就足夠了,但Spring還允許我們配置具有父子關系的根容器和它的Servlet子容器,來形成一個層次結構,如上圖所示,可以很清楚的看到,Spring將表示層相關的組件全部放到了子容器中,而將公共的與基礎服務有關的組件全部放到了根容器中,這樣的話,當我們需要注冊多個DispatcherServlet并共享那些基礎服務組件的時候,不必重復注冊Service和Dao了,因為每個Servlet子容器都可以從這個根容器中獲取到Service和Dao,這便是層次結構的意義
當然,Spring也支持單容器配置,如開頭中的示例那樣,此外我們可以通過繼承AbstractAnnotationConfigDispatcherServletInitializer來配置父子容器,如下
//配置父子容器,其中容器使用基于注解的配置方式
public class IocInit extends AbstractAnnotationConfigDispatcherServletInitializer {
//配置 DispatcherServlet 攔截的路徑
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
//設定根容器的配置類
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {RootConfig.class};
}
//設定子容器的配置類
//如果不想形成父子容器,那么只需將下面這個getServletConfigClasses()方法回傳null即可
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {WebConfig.class};
}
}
//由于我們采用的是父子容器,因此這就要求我們撰寫父子容器的組態檔時,根容器的組態檔(RootConfig)配置非web組件的bean,而子容器的組態檔(WebConfig)配置web組件的bean,同時,也要防止同一組件在不同容器中分別注冊初始化,從而出現兩個相同bean
//根容器配置類,使用excludeFilters排除掉@Controller注解標注的類和@Configuration注解標注的類,這里之所以要排除掉@Configuration注解標注的類,是為了防止根容器掃描到子容器的配置類WebConfig
@Configuration
@ComponentScan(value = "https://www.cnblogs.com/shame11/p/cn.example.springmvc.boke",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = https://www.cnblogs.com/shame11/p/Controller.class),
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class)
})
public class RootConfig {
}
//子容器配置類,使用includeFilters指定只掃描由@Controller注解標注的類
@Configuration
@ComponentScan(value ="https://www.cnblogs.com/shame11/p/cn.example.springmvc.boke",
includeFilters = @ComponentScan.Filter(value = https://www.cnblogs.com/shame11/p/Controller.class, type = FilterType.ANNOTATION))
public class WebConfig {
}
也可以基于web.xml來配置父子容器,如下
<web-app ....>
<!-- ContextLoaderListener用于配置根容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 設定根容器的xml組態檔路徑 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springroot.xml</param-value>
</context-param>
<!-- DispatcherServlet用于配置子容器 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 設定子容器的xml組態檔路徑 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springweb.xml</param-value>
</init-param>
</servlet>
<!-- 設定 DispatcherServlet 攔截的路徑 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3.特殊型別的bean
(1) 前面已經提過了,DispatcherServlet會將實際的請求處理程序委托給那些特殊的組件來干,而它本身起一個統一分配與調度的作用,這些特殊的組件已經由Spring提供了默認的實作,但同時Spring也允許我們自己實作替換它們,下表列出了由 DispatcherServlet檢測到的這些特殊的Bean型別
| Bean型別 | 說明 |
|---|---|
| HandlerMapping | 處理器映射器,主要就是將請求路徑(uri)映射到能處理該請求的處理器(handler),DispatcherServlet在接收到請求后,該請求會交由誰來處理?這個匹配查找的作業不是由DispatcherServlet來做的,而是交由HandlerMapping負責的,而至于Handler,我們可以把它理解為在@Controller注解所標注的類中的一個標注了@RequestMapping注解的方法,所謂的匹配本質上就是匹配@RequestMapping注解中所宣告的值罷了,注意:HandlerMapping在查找匹配到對應的Handler后,并不是直接回傳這個Handler,而是回傳這個Handler的包裝物件HandlerExecutionChain,而這個HandlerExecutionChain其實就是 Handler + 該請求所涉及到的攔截器 所組合而成的一個物件, 兩個主要的HandlerMapping實作類分別是RequestMappingHandlerMapping(標注了@RequestMapping注解的方法)和SimpleUrlHandlerMapping(若在xml中顯式的配置了請求路徑與Controller的對應關系,則會使用該處理器映射器) |
| HandlerAdapter | 幫助 DispatcherServlet 呼叫映射匹配到的HandlerExecutionChain,換句話說,DispatcherServlet獲得HandlerExecutionChain后,它不會進行呼叫執行,而是交由HandlerAdapter來呼叫執行HandlerExecutionChain |
| HandlerExceptionResolver | 解決例外的策略,用于定義在請求映射,引數系結或方法執行時若發生例外,該怎么處理 |
| ViewResolver | Handler方法執行后,將回傳的邏輯視圖名(通常為一個String字串)決議為真正的視圖(如.jsp 、.html等),并進行視圖渲染,渲染完成后,將視圖回傳給DispatcherServlet |
| LocaleResolver, LocaleContextResolver | 用于決議客戶的Locale,實作國際化功能 |
| ThemeResolver | 決議你的web應用可使用的主題(theme),主題是一系列靜態資源的集合(比如說css檔案,圖片等) |
| MultipartResolver | 專門用于處理檔案上傳 |
| FlashMapManager | 專門用于保存和管理FlashMap,而這個FlashMap是用來在重定向時傳遞引數的,因為redirect重定向是不能傳遞引數的,此時就可以借助FlashMap |
4.Web MVC配置
(1) 我們可以自定義在上一節中所列出來的那些特殊的bean,DispatcherServlet會檢查它們,如果沒有,那么它將會使用DispatcherServlet.properties中所列出的默認型別的bean,如下
5.Servlet 配置
(1) 在Servlet環境中,我們可以選擇以編程式的方式或基于web.xml的方式來配置Servlet容器,在本篇開頭的位置也提到過了,如下是一個基于編程式的例子
public class IocInit implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//創建一個基于xml配置的容器
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("classpath:springmvc.xml");
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", new DispatcherServlet(appContext));
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
WebApplicationInitializer是由Spring MVC提供的一個介面,Spring MVC會確保該介面的實作類們會被正確呼叫以初始化Servlet容器,WebApplicationInitializer有一個抽象基類為AbstractDispatcherServletInitializer,通過繼承該基類可以使得注冊DispatcherServlet更加容易
(2) 除了上面的例子之外,還可以使用基于java的Spring配置,如下,前面也提到過了
public class IocInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
//創建一個基于注解配置的容器
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {WebConfig.class};
}
}
(3) 除此之外,還可以繼承AbstractDispatcherServletInitializer
public class IocInit extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("classpath:springmvc.xml");
return cxt;
}
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
}
上面列出了常見的3種創建ioc容器的方式,我們可以根據自己的需要來進行選擇
(4) AbstractDispatcherServletInitializer還提供了一種便捷的方式來創建Filter實體,并且這些Filter實體會被自動映射到DispatcherServlet上,如下
public class IocInit extends AbstractDispatcherServletInitializer {
//...
//這些Filter會被自動映射到DispatcherServlet上,并且會根據它們的型別來為其添加一個默認名稱
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
此外,如果我們希望需要進一步的定制DispatcherServlet,我們可以重寫createDispatcherServlet方法
6.請求處理
(1) DispatcherServlet按照如下的方式來處理請求:
- 搜索WebApplicationContext并將其作為一個屬性系結到請求中,這樣在請求的后續處理程序中我們就可以直接從請求中拿到ioc容器,如下
@Controller
public class DemoController {
@RequestMapping("/demo")
@ResponseBody
public String get(HttpServletRequest httpServletRequest) {
//從請求中獲取到WebApplicationContext,這個ioc容器被系結到了請求的DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性上
WebApplicationContext ctx = (WebApplicationContext)httpServletRequest.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
Arrays.stream(ctx.getBeanDefinitionNames()).forEach(System.out::println);
return "aaa";
}
}
-
Locale決議器也被系結到了請求上,以便后續處理流程中進行使用
-
Theme決議器也被系結到了請求上,以便后續處理流程中進行使用
-
如果我們指定了multipart檔案決議器,那么Spring會檢查請求中是否含有multipart檔案,如果有,那么該請求會被包裹在一個MultipartHttpServletRequest中,以便后續處理流程中進行進一步處理
-
針對請求搜索恰當的handler,如果搜索到的話,與該handler相關的執行鏈(HandlerExecutionChain)將會被執行,以準備渲染模型(model)
-
如果有模型回傳,那么視圖(view)就會被渲染,否則,如果沒有模型回傳,那么視圖就不會被渲染
(2) 在WebApplicationContext中的HandlerExceptionResolver型別的bean將被用來解決請求處理程序中拋出的例外,這些例外決議器允許自定義處理例外的邏輯
(3) 我們可以在web.xml中的
| 引數 | 說明 |
|---|---|
| contextClass | 設定web容器,該容器必須是ConfigurableWebApplicationContext的實作類,默認為XmlWebApplicationContext |
| contextConfigLocation | 該引數值將會被傳遞至由上面contextClass指定的容器,通常用于指明組態檔(xml檔案,@Configuration注解標注的類)的路徑,容器會到指定位置加載組態檔 |
| namespace | 容器的命名空間,默認為[servlet-name]-servlet |
| throwExceptionIfNoHandlerFound | 決定當一個請求沒有找到其對應的handler時,是否會拋出NoHandlerFoundException例外,若設定為true,則表示拋出例外,然后我們就可以用HandlerExceptionResolver來捕獲該例外,并像處理其他例外一樣進行該處理,在默認情況下,該值被設定為false,因此在默認情況下,如果一個請求沒有找到其對應的handler,那么DispatcherServlet會將回應狀態碼設定為404(NOT_FOUND)而不會引發例外,因此我們會在頁面上看到一個 "404 - 未找到" 頁面,最后注意,如果defaultServletHandling也被配置了,那么這些不正常的請求會被轉發到defaultServlet進行處理,且永遠不會出現404 |
具體的配置例子如下
<web-app ....>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置容器,容器需要實作ConfigurableWebApplicationContext介面,此處我們選擇了XmlWebApplicationContext,這也是Spring的默認配置 -->
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>
</init-param>
<!-- 因為我們上面選用的是XmlWebApplicationContext,一個基于xml配置的容器,因此通過contextConfigLocation屬性來設定容器的xml組態檔路徑 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springweb.xml</param-value>
</init-param>
<!-- 如下是配置一個基于注解的容器 -->
<!-- <init-param>-->
<!-- <param-name>contextClass</param-name>-->
<!-- <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>-->
<!-- </init-param>-->
<!-- 因為是基于注解的容器,因此通過contextConfigLocation屬性來設定Configuration配置類的路徑 -->
<!-- <init-param>-->
<!-- <param-name>contextConfigLocation</param-name>-->
<!-- <param-value>cn.example.springmvc.boke.config.Config</param-value>-->
<!-- </init-param>-->
<!-- 設定WebApplicationContext的命名空間 -->
<init-param>
<param-name>namespace</param-name>
<param-value>app</param-value>
</init-param>
<!-- 注意,單單將這個值設定為true,并不會生效(即不會拋出NoHandlerFoundException例外),原因是Spring會默認加上ResourceHttpRequestHandler這個handler來進行處理,也就不會出現no handler的情況了,因此我們還需要配置spring.resources.add-mappings=false,這樣在發生no handler時才會拋出NoHandlerFoundException例外 -->
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<!-- 設定 DispatcherServlet 攔截的路徑 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
7.攔截器
(1) 攔截器必須實作HandlerInterceptor介面,該介面提供了3個方法,分別為
-
preHandle(..): 在handler執行之前執行
-
postHandle(..): 在handler執行之后執行
-
afterCompletion(..): 在整個請求完成后執行
preHandle方法回傳一個boolean值,通過該方法我們可以決定是繼續還是中斷執行鏈的執行,若回傳true,則執行鏈繼續執行,若回傳false,則DispatcherServlet會認為攔截器本身已經處理了該請求,因此會中斷執行鏈中其他的攔截器以及handler的執行
postHandle方法在有@ResponseBody和ResponseEntity的方法中用處不大,因為這種方法在postHandle方法執行前已經在寫入response了,等待postHandle方法執行時已經太遲了,而針對這種情況,我們可以使用ResponseBodyAdvice
8.例外
(1) 如果在請求映射處理程序中發生例外,DispatcherServlet會委托一個由HandlerExceptionResolver構成的例外處理鏈來處理這個例外,下面列出一些可用的HandlerExceptionResolver實作類
| HandlerExceptionResolver | 說明 |
|---|---|
| SimpleMappingExceptionResolver | 提供例外類名稱與例外視圖名稱之間的一個映射,即針對不同型別的例外回應不同的錯誤視圖 |
| DefaultHandlerExceptionResolver | 決議由Spring MVC引發的標準例外,并將它們映射成對應的HTTP狀態碼,它是作為“預設”使用的,如果其他HandlerExceptionResolver不能處理某些例外,最后會使用DefaultHandlerExceptionResolver來統一處理 |
| ResponseStatusExceptionResolver | 決議被@ResponseStatus注解標注的例外,并根據注解中的值將例外映射到對應的HTTP狀態碼 |
| ExceptionHandlerExceptionResolver | 通過呼叫@ControllerAdvice類或@Controller類中合適的@ExceptionHandler方法來決議例外 |
(2) 我們可以在容器中配置多個HandlerExceptionResolver型別的bean并根據需要設定它們的order屬性來形成一個例外處理器鏈,其中order屬性值越高,它在例外處理器鏈中的位置就越靠后,通常情況下,HandlerExceptionResolver回傳
-
一個指向錯誤視圖的ModelAndView
-
若例外在處理器中被處理,回傳一個空的ModelAndView
-
若例外未被解決,則回傳null,以便讓后續的處理器嘗試解決,如果到最后例外仍未解決,則允許將例外冒泡到Servlet容器中
使用MVC Config(通過@EnableWebMvc注解)后,會自動的向容器中添加Spring MVC例外,由@ResponseStatus注解標注的例外等例外的處理器,我們可以對它們進行替換
(3) 如果一個例外沒有被任何HandlerExceptionResolver處理或者請求的http回應被設定為錯誤狀態(即4xx,5xx),那么Servlet容器使用HTML來渲染一個默認的錯誤頁面,而為了自定義容器的默認錯誤頁面,我們可以在web.xml中配置錯誤頁面映射url,如下
<error-page>
<location>/error</location>
</error-page>
當配置好錯誤頁面映射url后,如果此時有一個例外沒有被任何HandlerExceptionResolver處理或者請求的http回應被設定為錯誤狀態,那么Servlet容器將會請求我們所配置的這個url,此時,我們就可以回傳一個自定義的錯誤視圖或像下面這樣直接回傳一個字串
@RequestMapping("/error")
@ResponseBody
public String error() {
return "error";
}
9. view,locale,theme和logging這四節的內容略過,如果有需要可查看官方檔案
10. Multipart決議器
(1) org.springframework.web.multipart包中的MultipartResolver可用于決議multipart型別(例如:檔案上傳)的請求,為了使用Multipart決議器,我們需要配置一個MultipartResolver型別的bean,并且這個bean的名稱必須是multipartResolver,配置好之后,DispatcherServlet會檢測到它并將其應用于后續的請求,當我們接收到一個型別為multipart/form-data的post請求時,Multipart決議器會決議請求內容并將當前這個HttpServletRequest包裝成MultipartHttpServletRequest以便訪問決議的內容,Spring MVC已為我們提供了兩個MultipartResolver的實作類,如下
-
CommonsMultipartResolver:基于Apache Commons FileUpload的MultipartResolver實作類,為了使用它,需要添加commons-fileupload依賴
-
StandardServletMultipartResolver:基于Servlet 3.0 multipart請求決議的MultipartResolver實作類,為了使用它,我們需要在<web.xml/>或Servlet registration中進行一些配置:
<!-- 在web.xml的<servlet/>標簽中添加<multipart-config/>標簽,配置上傳檔案的大小等資訊 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<multipart-config>
<location>/</location>
<max-file-size>2097152</max-file-size>
<max-request-size>419304</max-request-size>
</multipart-config>
</servlet>
也可在Servlet registration中進行配置
public class IocInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {WebConfig.class};
}
//使用ServletRegistration配置上傳檔案大小等資訊
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setMultipartConfig(new MultipartConfigElement("/", 2097152, 419304, 0));
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553935.html
標籤:Java
上一篇:別再滿屏找日志了!推薦一款 IDEA 日志管理插件,看日志輕松多了!
下一篇:返回列表
