1. 多線程程式在一個核的CPU運行 ![image.png](https://cdn.nlark.com/yuque/0/2023/png/35902537/1685453577663-714d9c16-e8a3-4828-bb86-86dfa10c8e52.png#averageHue=%23f ...
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 = "cn.example.springmvc.boke",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class),
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class)
})
public class RootConfig {
}
//子容器配置類,使用includeFilters指定只掃描由@Controller註解標註的類
@Configuration
@ComponentScan(value = "cn.example.springmvc.boke",
includeFilters = @ComponentScan.Filter(value = 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));
}
}