前言
Spring強大的自動配置功能幫我們簡化了很多開發的步驟,這是我們能快速上手一個複雜的項目,但是我們對於背後的原理確是一知半解,今天我們對MVC的執行流程進行說明。
1. SpringMVC的執行原理
說真的,着手寫這篇文章的時候,我很糾結,因爲感覺無論以何種方式入手都很難說明白這個流程;這裏我嘗試先使用圖解,文字說明後,通過一段代碼加深圖形和文字的理解。
這是官方對於MVC流程的圖解說明:
- 用戶通過輸入網址發送請求到前端控制器 DispatcherServlet
- 前端控制器把用戶的請求委託給頁面的控制器Controller層(這裏的底層通過處理器映射HandlerMapping和處理器適配器HandlerAdapter 找到合適的控制層進行匹配)
3,4. 頁面控制器 Controller 通過調用service,dao層的相關業務後,把數據和視圖封裝成爲一個模型視圖對象(ModelVIew)並返回 - 頁面控制器 (Controller層) 把 模型視圖對象 (ModelAndView) 返回給前端控制器 (DispatcherServlet)
- 前端控制器 (DispatcherServlet) 進行相關的視圖渲染後,把頁面返回給用戶併產生相應的響應。
這裏我們再以我最喜歡的一個up 主狂神的圖解再講解一次它底層的實現
在開發中,上圖的實線是Spring底層幫我們實現的,虛線纔是我們需要完成的配置。
1 DispatcherServlet 表示的是前端控制器,是整個SpringMVC的控制中心,用戶發送請求,DispatcherServlet 接收請求並攔截請求
假設我們發送的url請求爲 :http://localhost:8080/SpringMVC/hello。
這個url 請求可以拆分爲三部分:
- http://localhost:8080 服務器域名
- SpringMVC部署在服務器上的web站點
- hello 表示控制器
- 通過分析,如上url表示爲:請求位於服務器localhost:8080上的SpringMVC站點的hello控制器
2 HandlerMapping爲處理器映射,被DispatcherServlet所調用,主要是爲了根據url 找到相應的處理器 (Handler)
3 HandlerExcution表示處理的結果,這裏指的是具體的Handler,根據1我們知找到的控制器名爲 hello
4 HandlerExcution將解析後的信息傳遞給 DispatcherServlet (如解析控制的映射等)
5. HandlerAdapter表示處理適配器,這就是按特定的名字去找Controller層的相關class類
6. Controller 層去調用底層service,dao層的方法。
7. Controller層把數據和頁面封裝成一個模型視圖對象(ModelAndView)返回給處理適配器(HandlerAdapter)
8. HandlerAdapter將視圖的邏輯名或模型傳遞給DispatcherServlet
9. DispatcherServlet調用視圖解析器 (ViewResolver)來解析邏輯視圖名(即加上的前綴和後綴)
10. 視圖解析器將解析的邏輯視圖名傳給DispatcherServlet
11. DispatcherServlet根據視圖解析器解析視圖結果,調用具體的視圖
12. 最終把相關的前端頁面呈現給用戶
話說了一大堆,可大多數人還是很懵的,接下來通過一段代碼對上面的圖片和文字加深理解
2. SpringMVC的代碼實現
說在前面,這段代碼在我們開發中基本上不會使用,完全是爲了契合上面的原理所寫。
[1] 創建一個Maven項目(可以是導入web模塊的也可以是普通項目 ps:普通項目後面有一個bug需要改)===>這裏以普通項目進行講解
[2] 在pom.xml導入依賴
<!--依賴-->
<dependencies>
<!--junit的測試包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--springMVC的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--servlet層的包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!--jsp的包-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<!--jstl表達式的包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
[3] 添加web.xml文件
由於是普通Maven項目,需要添加web框架的支持
這是我們後期編寫完成後的目錄結構
[4] 編寫web.xml文件
1.這裏的核心主要是配置我們的前端控制器DispatcherServlet(Spring底層已經幫我們比編寫好了),它需要一個springMVC配置文件(springmvc-servlet.xml)的注入
2.在給這個控制器加映射的時候,我們一般使用的路徑是 / 而不是 /* ,因爲後者會處理 .jsp的請求,這樣我們在調用視圖解析器的時候。也會給jsp文件加上相應的前後綴,這樣會產生映射錯誤。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--1.註冊DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--關聯一個springmvc的配置文件 [servlet-name] -servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--啓動級別:1-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- / 匹配所有的請求,不包括 .jsp -->
<!-- /* 匹配所有的請求,包括 .jsp -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
[3] 編寫SpringMVC的配置文件
springmvc-servlet.xml
主要功能有三個:
- 配置處理器映射器HandlerMapping (這裏就是處理我們用戶發送的url請求,去獲取相應的控制器的名字)
- 配置處理器適配器HandlerAdapter (這裏既是把請求交給相應名字的控制器處理)
- 配置視圖解析器 ViewResolver
作用:
1.獲取了ModelAndView的數據
2.解析ModelAndView視圖的名字
3.拼接名字,找到對應的視圖 /WEB-INF/jsp/hello.jsp
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--處理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--處理器適配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--視圖解析器: DispatcherServlet給它的ModelAndView
1.獲取了ModelAndView的數據
2.解析ModelAndView視圖的名字
3.拼接名字,找到對應的視圖 /WEB-INF/jsp/hello.jsp
-->
<!--視圖解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!--前綴-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--後綴-->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
[4] 編寫處理器(處理用戶的url請求)
HelloController (這裏是通過實現Controller接口來完成的,但我們開發中一般不這麼使用,這裏是爲例把邏輯解釋清楚)
作用:把視圖和數據封裝成ModelAndView對象(模型和視圖),並返回給我們前端控制器DispatcherServlet,然後它在通過視圖解析器完成相關頁面的渲染,並返回給用戶
package com.gs.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//注意:這裏我們導入Controller接口
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
//ModelAndView 模型和視圖
ModelAndView mv = new ModelAndView();
//封裝對象,就在ModelAndView中,Model
mv.addObject("msg","HelloSpringMVC!");
//封裝要轉發的視圖,放在ModelAndView中
mv.setViewName("hello"); // /WEB-INF/jsp/hello.jsp
return mv;
}
}
[5] 配置處理器
在springmvc-servlet.xml加上
<!--Handler-->
<bean id="/hello" class="com.gs.controller.HelloController"/>
這樣我們編寫完後,運行時是會報錯的,主要是因爲這個普通項目並沒有導入我們的相關jar包,我們可以手動添加該目錄(web項目就不會有這種錯誤)
這時訪問:http://localhost:8080/hello
小結
SpringMVC的執行原理,說難不難,說易不易,我們還是要多通過圖解和文字,並結合相關代碼反覆對應記憶,這樣我們才能真正做到知其然不知其所以然。