一個簡單的SpringMVC程序:
我們先通過創建一個簡單的動態的JavaWeb項目“springmvc01”來認識一下SpringMVC,這樣方便我們對其進行分析。
1、拷貝jar包:
對於所有框架而言,這一步都是必不可少的,我們需要在web工程的“WEB-INF”的目錄下的lib文件夾中拷貝下列jar包,由於SpringMVC是spring家族的,使用它就必不可少的要拷貝spring框架的jar包:
- a.Spring框架的jar包:
- b.Spring框架的依賴包:
- c.SpringMVC框架的jar包:
- d.SpringMVC框架的依賴包:
2、創建一個JSP視圖:
在“WEB-INF”目錄下新建一個文件夾“jsp”用於存放JSP視圖,在此目錄下的視圖非常安全,只能通過請求轉發的方式訪問。我們在此創建一個視圖“hello.jsp”,該視圖用於在瀏覽器客戶端響應給用戶。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
Hello SpringMVC
</body>
</html>
3、定義一個控制器Controller:
這一步是SpringMVC框架的集中體現,之前衆多Servlet現在都可以寫在一個類(控制器)中,大大的簡化了代碼。我們在src目錄下新建一個包“cn.jingpengchong.hello.controller”,在該包中新建一個類HelloController:
package cn.jingpengchong.hello.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class HelloController {
@RequestMapping("hello")
public String hello() {
return "hello";
}
}
- @Controller:添加該註解後該類可以被Spring掃描器到,並且告訴Spring該類是一個控制器;
- @RequestMapping():該註解用於使處理器映射器將其value屬性值與請求地址進行匹配,以確定應該執行的方法;
4、配置spring的xml核心配置文件:
在spring的xml核心配置文件中配置一個掃描器,用於將自定義的處理器HelloController交給Spring來管理;除此之外還需要配置一個視圖解析器InternalResourceViewResolver,用來將請求匹配到的處理器返回的字符串解析成真正的請求路徑:
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- 配置controller包掃描 -->
<context:component-scan base-package="cn.jingpengchong.hello"></context:component-scan>
<!-- 配置視圖解析器:這個配置可以簡化控制器中返回的請求路徑 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 將接受的url路徑片段字符串拼接上前綴 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!-- 將接受的url路徑片段字符串拼接上後綴 -->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
5、配置web.xml核心配置文件:
要發揮控制器的作用,在這裏需要配置核心控制器(或稱前端控制器),並且設置哪些請求路徑會被匹配到並進行處理:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>springmvc01</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 核心控制器的配置 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- DispatcherServlet繼承了父類FrameworkServlet的contextConfigLocation屬性,因此這裏可以給個初始值 -->
<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>springmvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
6、測試:
將此項目添加進tomcat服務器並啓動tomcat服務器,打開瀏覽器,並在地址欄輸入“http://127.0.0.1/springmvc01/hello.do”後回車,結果如下:
請求的執行過程:
1、從在地址欄輸入“http://127.0.0.1/springmvc01/hello.do”並回車,發送請求;
2、首先找到web.xml文件:
用於該請求以“.do”結尾,所以被url-pattern標籤匹配到,然後根據servlet-name標籤找到核心處理器“DispatcherServlet”。
3、然後找到springmvc.xml文件:
爲核心處理器“DispatcherServlet”初始化contextConfigLocation屬性時找到springmvc.xml文件。
4、然後找到HelloController.java文件:
由於springmvc.xml文件中配置了Spring掃描器,掃描器會去“cn.jingpengchong.hello”包下逐一查找添加了特定註解的類,由於類HelloController添加了@Controller註解,所以便被掃描到,並且根據該註解斷定該類是一個處理器。
5、DispatcherServlet內部代碼流程:
我們知道一個請求被servlet捕獲後必定會調用service方法,由service方法根據請求類別再調用doGet或doPost方法,對於DispatcherServlet也是一樣。而由於DispatcherServlet沒有重寫其父類FrameworkServlet的service方法,因此請求必定會執行FrameworkServlet中的service():
由於“HttpMethod.PATCH == httpMethod || httpMethod == null”的返回值時false,所以調用了父類的service()方法,在其父類的service()方法中,發現其又執行了doGet()方法:
由於FrameworkService類中重寫了doGet()方法,所以執行到了FrameworkService類中的doGet()方法:
在該方法中又執行了processRequest()方法,在processRequest()方法中又執行了doService()方法:
我們發現doService()方法發現其是一個抽象方法,既然是一個抽象方法,那麼該類的子類必定有實現該方法的,恰巧DispatcherServlet類就是FrameworkServlet類的子類,我們點開該方法的實現,發現DispatcherServlet確實實現了該方法,那麼當執行doService()方法時必定是執行DispatcherServlet類中的doService()方法了,向下執行,發現確實執行到了DispatcherServlet類中的doService()方法,並且在該方法中執行了doDispatch()方法:
在doDispatch()方法中,發現裏面通過getHandler()方法獲得了一個處理器映射器,該映射器獲得了將要被執行的HelloController類中的hello()方法:
接着向下走,發現其又獲得了一個處理器適配器,並且將處理器映射器獲得的hello()方法交給該處理器適配器執行:
接着向下執行,到了processDispatchResult()方法:
在processDispatchResult()方法中又執行了render()方法:
在render()方法中又獲得了一個View類的實例化對象,並調用了該對象的render()方法:
接着向下執行,發現View的render()方法其實是View的子類AbstractView中實現的方法:
在該方法中又執行了renderMergedOutputModel()方法:
接着向下執行,發現renderMergedOutputModel()方法其實是AbstractView的子類InternalResourceView中的方法:
在renderMergedOutputModel()方法中獲得了一個RequestDispatcher類對象:
接着向下執行,發現在方法的最後調用了該對象的forward()方法,此時RequestDispatcher的實例化對象rd中已經有了響應的完整路徑:
我們在學習Servlet時就知道了“request.getRequestDispatcher(“路徑”).forward(request, response);”是一個請求轉發,可見在SpringMVC中,控制器處理請求後默認是用請求轉發來響應頁面給用戶的!爲了進一步驗證我們的觀點,我們不妨對HelloController類中的hello()方法做一些改造,直接讓它以請求轉發的方式響應給用戶:
@RequestMapping("hello")
public String hello() {
return "forward:/WEB-INF/jsp/hello.jsp";
}
再次發送同樣的請求,我們發現,效果真實一樣的!
那麼用重定向的方式可以響應嗎?我們試一下:
@RequestMapping("hello")
public String hello() {
return "redirect:/WEB-INF/jsp/hello.jsp";
}
從上面的圖中可以發現,重定向是不可以的,這也是爲什麼在WEB-INF目錄下的jsp頁面是安全的了,因爲在該目錄下的資源只能通過請求轉發的方式訪問!