Spring MVC 4.2.4 文件實踐(一)--- 菜鳥從零開始學系列

biubiubiubiubiubiubiu發表於2018-11-21

1 前言

本文後續將開啟一個系列,順著作者學習 Spring MVC 文件的腳步,從零開始搭建一個基於 Spring MVC 的 web 應用,並且根據 Spring MVC 的文件內容,選擇現有的,用的比較多的,實現性比較好的特性,基於其程式碼實現,來講解其原始碼和背後的原理,這既是對自己在 Spring 全家桶的學習的檢驗,也可以讓我講一講自己對於 Spring MVC 的一些特性的理解。本人也是菜鳥程式設計師一枚,開始寫這個系列的時候入行也不到半年。因此,儘量可以站在初學者的角度,來解決我們大家在學習的路上一些老師想當然,而我們卻一直沒有辦法自己解決的問題。希望可以在不影響工作的情況下,儘量做到每週一更甚至每週兩更,希望這個系列可以幫助到各位。相關程式碼可以移步 我的 github 倉庫,如果有什麼問題,也煩請大佬們在這個系列中,多多指正。

2 前期準備

初始化 maven 工程

使用 Intellij 新建工程選項,在 maven 選項卡里面選擇 maven-archetype-webapp,輸入對應的工程名,點選建立,可以得到一個已經初始化了 webapp 檔案。

工程目錄初始化設定

在 main 資料夾下,分別建立 java 和 resources 資料夾,點選 File - Project Structure,將 main 和 resources 設定為 Source Folders (藍色),和 Resource Folder (紫色)。

之後,在 java 的資料夾中設定好 package 的路徑,本例的 package 名字為 com.test.myapp

maven 依賴

為了讓工程支援 Spring 的特性,需要在 pom.xml 中引入 springframework 的依賴

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-webmvc</artifactId>
	<version>5.1.0.RELEASE</version>
</dependency>
複製程式碼

下面,就開始用程式碼例項解釋 Spring MVC 的文件。

3 Spring MVC 用 DispatcherServlet 處理請求

這一部分主要告訴我們:DispatcherServlet 是 Spring MVC 的中央處理器,它負責把請求分發到控制器中,這被稱為“前端控制器”的設計模式,模式框架如下圖所示:

Front-end controller

因此,DispatcherServlet 前端的 Incoming Request 發到(Delegate)對應的 Controller 下面,在 Controller 處理了 Request,並且建立 model 之後,將響應和 model 封裝到對應的 view template 中,之後 view template 將控制權交還到 DispatcherServlet,並由其返回響應。

3.1 DispatcherServlet 建立

而實際操作中,DispatcherServlet 由於繼承了 HttpServlet 類,也需要在 web.xml 檔案下進行宣告。因此,可以照著文件中的例子進行 DispatchServlet 的配置:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Dispatched Servlet Demo</display-name>

    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
        <!--load on startup 大於 0 代表這個容器在應用啟動時就載入,而等於 0 則表示在這個容器被選擇時載入-->
        <!--load-on-startup 的值越小,代表載入的優先順序越大-->
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>/example/*</url-pattern>
    </servlet-mapping>

</web-app>
複製程式碼

如上述程式碼所示,我們配置了一個名為 example 的 dispatcherServlet,而當 url 的字尾為 example 時,預設將使用該 DispatcherServlet 進行請求(request)的轉發和檢視(view )的返回。

值得一提的是,我們在 web.xml 中定義了名為 example 的 DispatcherServlet,Spring 將為在 WEB-INF 中尋找名為 example-servlet.xml 的檔案以獲取對應的 bean 配置資訊。因此,我們還需要建立 example-servlet.xml 的檔案以設定對應的 bean 資訊。

3.2 example-servlet 的配置

example-servlet 中需要我們指定 bean 的位置,即 Spring MVC 系統需要去哪個地方尋找 bean 的配置並自動裝配。另一方面,需要配置 viewResolver 以便返回檢視。因此,我們在 example-servlet.xml 中首先需要包含這些資訊,下述程式碼配置了 example-servlet 中的 bean 的掃描路徑,以及檢視解析器 viewResolver。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <context:component-scan base-package="com.test.myapp.example">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/views/" p:suffix=".jsp" p:order="1">
    </bean>

</beans>
複製程式碼

首先解釋一下標頭檔案

  • beans:檔案的根節點
  • Xmlns:xmlns 是 xml name space 的縮寫,用於區分這個 xml 檔案下的定義和其他 xml 檔案下的定義。
  • Xmlns:<元件>:表示一個元件配置的 namespace,比如上文用到了 context 的元件來掃描 component 註解,則需要配置 xmlns:context
  • xmlns:xsi:是指xml檔案遵守xml規範,xsi 全名 xml schema instance,表示具體用到的 schema 資原始檔裡定義的元素所準守的規範。
  • xsi:schemaLocation:表示 xml 的某個元件需要遵循的規範,比如 xmls:context 需要遵循的規範就是

標頭檔案的配置更多的是理解一個類似於 package 的概念,在定義了 namespace(xmlns) 之後,你需要哪些元素或者元件,則定義他的規則以及 instance 的規則,以便後續的 xml 配置使用這些配置。

context 部分表示了這個工程要去哪裡去尋找這個應用的 component,並實現例項的自動裝配,這裡制定了一個針對 component 的過濾器(filter),使它只會在遇到 controller 的類時候進行自動裝配。

3.3 配置 Controller 和 jsp

在配置好 servlet 的轉發後,我們需要配置其掃描路徑下的 Controller 和對應的 jsp 檢視。在整理完成對應用途的檔案和資料夾之後,工程結構如下圖所示:

![工程檔案結構](/Users/shenruofan/Desktop/螢幕快照 2018-10-22 下午5.51.03.png)

首先在 com.test.my.example.controller 下建立對應的 controller 類:

package com.test.myapp.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping
public class ExampleController {
    @RequestMapping(value="/hello", method = RequestMethod.GET)
    public String helloWorld() {
        return "hello";
    }
}
複製程式碼

值得注意的是,由於在 example-servlet.xml 中已經配置了 url 的格式,它表示這個 servlet 只在 /example/* 的 url 格式下面才會生效。因此當我們在 helloWorld 這個方法上指定 RequestMapping 為 "hello" 時,則令其實際生效的 url 應當為 /example/hello。由於 helloWorld() 方法返回了 "hello",則 viewResolver 在 views 下面去尋找名為 hello.jsp 的檔案。因此,我們還需要一個 hello.jsp 的檢視檔案

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>hello world</title>
</head>
<body>
    <h1>example::::::hello world!</h1>
</body>
</html>
複製程式碼

之後就是執行 tomcat 時所用到的相關配置,並且 maven 打包這個工程,本機使用了 localhost:8080 埠,則輸入了 http://localhost:8080/example/hello 之後,網頁出現了

![效果圖](/Users/shenruofan/Desktop/螢幕快照 2018-10-23 下午3.09.32.png)

的樣子,則說明這個 controller 成功返回了 view 檢視。類比上述“前端控制器”的設計模式,我們可以看到:

當前端有請求進來時,比如 /example/hello,Spring MVC 將會識別這個 url 下生效的 servlet,然後該 servlet 會把它分發到 url 對映到的方法上,而該方法會返回一個檢視的名稱到 servlet;之後,servlet 會去找對應名字的 template,組裝完成後,再返回到使用者的前端。因此,我們才可以看到 hello.jsp 的前端樣式展現在我們眼前。

4 配置根 context 物件代替 Servlet.xml

這篇文件的最後提到了這樣一句話:

當你的應用中只需要一個DispatcherServlet時,只配置一個根 context 物件也是可行的。

因此,我們可以試一下只配置一個 ContextConfig 的條件下,能不能導航到這個 hello.jsp 檔案。

4.1 配置 Root Context

首先,更新 web.xml 的配置,使其只使用一個 ContextConfig 來管理 servlet 下面的 beans,配置內容如下:

<?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_3_1.xsd"
         version="3.1">


    <display-name>Dispatched Servlet Demo</display-name>

    <!--使用根 context 來管理 servlet 的配置-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/root-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>
複製程式碼

這裡注意一下 servlet-mapping 的 url 攔截問題,其實源文件的這種配置會有問題,如果不加 .do 的話,會把 jsp 解析的請求也攔截下來,造成 *noHandlerFound for .jsp 的問題,因此需要一個特殊的 url 字尾進行區分,防止 servlet 攔截不該攔截的請求。其次,我們需要配置 WEB-INF 路徑下的 root-context.xml 檔案,配置方法與上面的 example-servlet.xml 的相同。controller 亦然,需要主要的是,現在是當你輸入 /hello.do 時,可以返回 hello.jsp 的檢視。

之後也是執行 tomcat 時所用到的相關配置,並且 maven 打包這個工程,本機使用了 localhost:8080 埠,則輸入了 http://localhost:8080/hello.do 之後,網頁出現了

![效果圖](/Users/shenruofan/Desktop/螢幕快照 2018-10-23 下午3.09.32.png)

證明我們的 root context 的配置是有效的。

4.2 RootContext.xml 和 Servlet.xml 的差別

通過上面的配置,看起來 RootContext 和 servlet 的配置檔案對 Spring MVC 工程的作用是一樣的,其實不然,根據官方文件:

Spring lets you define multiple contexts in a parent-child hierarchy.

The applicationContext.xml defines the beans for the "root webapp context", i.e. the context associated with the webapp.

The spring-servlet.xml (or whatever else you call it) defines the beans for one servlet's app context. There can be many of these in a webapp, one per Spring servlet (e.g. spring1-servlet.xml for servlet spring1, spring2-servlet.xml for servlet spring2).

Beans in spring-servlet.xml can reference beans in applicationContext.xml, but not vice versa.

All Spring MVC controllers must go in the spring-servlet.xml context.

In most simple cases, the applicationContext.xml context is unnecessary. It is generally used to contain beans that are shared between all servlets in a webapp. If you only have one servlet, then there's not really much point, unless you have a specific use for it.

大概的意思是,ApplicationContext.xml 和 Servlet.xml 是父子之間的關係,其中一個 Spring mvc 工程裡應當只有一個 ApplicationContext,然而可以有多個 Servlet 的配置;並且,Servlet.xml 可以去引用 ApplicationContext 中的 bean,但是反過來卻不行。

5 總結

本章討論了 Spring MVC 中 web.xml,以及下面的 servlet.xml 或者 contextConfig.xml 的配置方法及注意事項,並用實際程式碼的方式,理解 Spring MVC 的前端控制器設計思想。

相關文章