搶先一步
VMware 提供培訓和認證,以加速您的進展。
了解更多Spring MVC 是核心 Spring Framework 的一部分,是一個成熟且功能強大的動作-回應式網頁框架,具有廣泛的功能和選項,旨在處理各種以 UI 為中心和非 UI 為中心的網路層使用案例。所有這些對於 Spring MVC 的新手來說都可能難以招架。我認為向這些讀者展示啟動並運行一個最基本的 Spring MVC 應用程式是很有用的(也就是說,將我的範例視為類似於世界上最簡單的 Spring MVC 應用程式),這就是我將在本文的其餘部分演示的內容。
我假設您熟悉 Java、Spring(基本依賴注入概念)和基本的 Servlet 程式設計模型,但不了解 Spring MVC。在閱讀這篇部落格文章後,讀者可以繼續透過查看 Keith Donald 的 Spring MVC 3 Showcase,或涵蓋 Spring 和 Spring MVC 的各種其他線上和印刷資源,來繼續學習 Spring MVC。
關於依賴性和建置系統的注意事項:本文不假設您正在使用特定的建置系統,例如 Maven、Gradle 或 Ant。文章末尾包含了一個相當精簡的範例 Maven POM 檔案作為範例。
Spring MVC 包含了大多數與其他所謂的網頁 MVC 框架相同的基本概念。傳入的請求透過前端控制器進入框架。在 Spring MVC 的情況下,這是一個名為 DispatcherServlet
的實際 Java Servlet。將 DispatcherServlet
視為守門員。它不執行任何真正的網頁或業務邏輯,而是委派給名為控制器的 POJO,在其中完成實際工作(全部或透過後端)。完成工作後,視圖負責以適當的格式產生輸出(無論是 JSP 頁面、Velocity 範本還是 JSON 回應)。策略用於決定哪個控制器(以及該控制器中的哪個方法)處理請求,以及哪個視圖呈現回應。Spring 容器用於將所有這些部分連接在一起。它看起來像這樣
DispatcherServlet
。與 Java EE 應用程式中的任何其他 Servlet 一樣,我們告訴 Java EE 容器在網頁應用程式啟動時透過網頁應用程式的 WEB-INF/web.xml
中的項目載入此 Servlet。DispatcherServlet
也負責載入 Spring ApplicationContext
,它用於執行受管組件的連線和依賴注入。在此基礎上,我們為 Servlet 指定一些初始化參數,這些參數配置 Application Context。讓我們看一下 web.xml
中的設定WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
這裡正在完成許多事情
appServlet
的 ServletContextConfigLocation
初始化參數來自訂 Spring Application Context 的基本配置 XML 檔案的位置,該檔案由 DispatcherServlet 載入,而不是依賴 <servletname>-context.xml 的預設位置)。DispatcheServlet 載入的預設 Application Context 類型期望至少載入一個包含 Spring bean 定義的 XML 檔案。您將看到,我們還將啟用 Spring 來載入基於 Java 的配置,以及 XML。
每個人在這個領域都會有自己的(有時非常強烈的)意見,但雖然我通常更喜歡基於 Java 的配置,但我確實相信某些領域的少量 XML 配置有時仍然更有意義,原因有很多(例如,無需重新編譯即可更改配置的能力、XML 命名空間的簡潔性、工具性等)。在此基礎上,此應用程式將使用混合方法,同時支援 Java 和 XML。
請放心,如果您更喜歡純 Java 方法,完全不使用 Spring XML,那麼只需在 web.xml
中設定一個初始化參數來覆蓋預設的 Application Context 類型,並改用名為 AnnotationConfigWebApplicationContext
的變體,就可以輕鬆實現。
package xyz.sample.baremvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "WEB-INF/views/home.jsp";
}
}
讓我們逐步了解此類別的關鍵方面
@Controller
註解進行註解,表明這是一個能夠處理網頁請求的 Spring MVC 控制器。由於 @Controller
是 Spring 的 @Component
刻板印象註解的特殊化,因此該類別將會被 Spring 容器自動偵測為容器組件掃描過程的一部分,建立 bean 定義並允許像任何其他 Spring 管理的組件一樣進行依賴注入。home
方法已使用 @RequestMapping
註解進行註解,指定此方法應處理路徑 "/" 的網頁請求,即應用程式的首頁路徑。home
方法只是將訊息記錄到系統輸出,然後傳回 WEB-INF/views/home.jsp
,指示應處理回應的視圖,在本例中為 JSP 頁面。(如果硬編碼整個視圖路徑,包括 WEB-INF 前綴,以及它是 JSP 的事實,對您來說似乎是錯誤的,那麼您是對的。我們稍後會處理這個問題)WEB-INF/views/home.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>
最後,如前所述,我們需要建立一個最小的 Spring Application Context 定義檔案。
WEB-INF/spring/appServlet/servlet-context.xml
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Scans within the base package of the application for @Components to configure as beans -->
<!-- @Controller, @Service, @Configuration, etc. -->
<context:component-scan base-package="xyz.sample.baremvc" />
<!-- Enables the Spring MVC @Controller programming model -->
<mvc:annotation-driven />
</beans>
讓我們檢查此檔案的內容
@Component
子類型(例如 @Controller
)註解的任何程式碼。您會注意到,為了提高效率,我們限制了(在本例中為 xyz.sample.baremvc
)Spring 應在類別路徑中掃描的套件空間部分http://localhost:8080/baremvc
,將顯示預期的問候語:儘管它很簡單,但執行此應用程式涉及運作中的 Spring MVC 應用程式的所有主要部分。讓我們逐步了解主要順序和組件互動
web.xml
中的項目而被載入和初始化。http://localhost:8080/baremvc
的 HTTP 請求擊中 servlet 引擎,並路由到我們的 (baremvc) 網頁應用程式。HandlerAdapter
的策略來決定將請求路由到哪裡。要使用的特定 HandlerAdapter 類型(或類型,因為它們可以鏈接)可以自訂,但預設情況下,使用基於註解的策略,該策略根據在這些類別中找到的 @RequestMapping
註解中的匹配條件,將請求適當地路由到註解為 @Controller
的類別中的特定方法。在本例中,home 方法上的正規表示式匹配,並呼叫它來處理請求。WEB-INF/views/home.jsp
),以協助選擇視圖來呈現回應。ViewResolver
的策略來決定哪個視圖負責呈現回應。這可以根據應用程式的需要進行配置(以簡單或鏈接的方式),但預設情況下,使用 InternalResourceViewResolver
。這是一個非常簡單的視圖解析器,它產生一個 JstlView
,該 JstlView
只是委派給 Servlet 引擎的內部 RequestDispatcher
進行呈現,因此適用於 JSP 頁面或 HTML 頁面。如前所述,將視圖範本的路徑硬編碼到控制器中是不合適的,就像我們的控制器目前所做的那樣。控制器和視圖之間更鬆散、更邏輯的耦合,控制器專注於執行一些網頁或業務邏輯,並且通常與視圖路徑或 JSP 與其他範本技術等特定細節無關,這是關注點分離的一個範例。這允許控制器和視圖的更大程度的重複使用,以及每個的更容易的獨立演變,並且可能不同的人員在每種類型的程式碼上工作。
本質上,控制器程式碼理想情況下需要像這樣的變體,其中傳回純粹邏輯的視圖名稱(無論是簡單的還是複合的)
//...
@Controller
public class HomeController {
@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "home";
}
}
Spring MVC 的 ViewResolver
策略實際上是用於實現控制器和視圖之間這種更鬆散耦合的機制。如前所述,在應用程式未配置特定 ViewResolver
的情況下,Spring MVC 會設定預設的最小配置 InternalResourceViewResolver
,這是一個非常簡單的視圖解析器,它產生一個 JstlView
。我們可能會使用其他視圖解析器,但為了獲得更好的解耦程度,我們實際上需要做的就是設定我們自己的 InternalResourceViewResolver
實例,並進行稍微調整的配置。InternalResourceViewResolver
採用非常簡單的策略;它只是採用控制器傳回的視圖名稱,並在前面加上一個可選的前綴(預設為空),並在後面加上一個可選的後綴(預設為空),然後將結果路徑饋送到它建立的 JstlView
。然後,JstlView
委派給 Servlet 引擎的 RequestDispatcher
來執行實際工作,即呈現範本。因此,為了允許控制器傳回邏輯視圖名稱(例如 home
),而不是特定的視圖範本路徑(例如 WEB-INF/views/home.jsp
),我們只需要使用前綴 WEB-INF/views
和後綴 .jsp
配置此視圖解析器,以便它分別將它們添加到控制器傳回的邏輯名稱的前面和後面。
配置視圖解析器實例的一種簡單方法是引入 Spring 基於 Java 的容器配置的使用,並將解析器作為 bean 定義
package xyz.sample.baremvc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
public class AppConfig {
// Resolve logical view names to .jsp resources in the /WEB-INF/views directory
@Bean
ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}
我們已經在執行組件掃描,因此由於 @Cofiguration
本身就是一個 @Component
,因此 Spring 容器會自動拾取此新的配置定義(其中包含(解析器)bean)。然後 Spring MVC 掃描所有 bean 並找到解析器。
這是一種很好的方法,但有些人可能更喜歡將解析器配置為 XML 定義檔案中的 bean,例如
<!-- Resolve logical view names to .jsp resources in the /WEB-INF/views directory -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
很難證明對於這個對象來說,一種特定的方法比另一種方法好得多,因此這實際上是個人喜好的問題(我們實際上可以看到 Spring 的優勢之一,它的靈活性)。
package xyz.sample.baremvc;
import java.util.Comparator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
@Autowired
Comparator<String> comparator;
@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "home";
}
@RequestMapping(value = "/compare", method = RequestMethod.GET)
public String compare(@RequestParam("input1") String input1,
@RequestParam("input2") String input2, Model model) {
int result = comparator.compare(input1, input2);
String inEnglish = (result < 0) ? "less than" : (result > 0 ? "greater than" : "equal to");
String output = "According to our Comparator, '" + input1 + "' is " + inEnglish + "'" + input2 + "'";
model.addAttribute("output", output);
return "compareResult";
}
}
新程式碼中的關鍵元素
@RequestMapping
註解來將以路徑 /compare
結尾的請求導向到新的 compare 方法@RequestParam
註解獲取它們。請注意,我們依賴於此註解的預設處理,該處理假設這些參數是必需的。如果缺少這些參數,用戶端將收到 HTTP 400 錯誤。另請注意,這只是將參數傳遞到 Spring MVC 應用程式的一種方法。例如,很容易獲取嵌入為請求 URL 路徑本身一部分的參數,以獲得更 REST 風格的方法result
鍵下,以便視圖可以存取它。將 Model 視為一個經過美化的雜湊表,以最簡單的術語來說。WEB-INF/views/compareResult.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Result</title>
</head>
<body>
<h1><c:out value="${output}"></c:out></h1>
</body>
</html>
最後,我們需要為控制器提供一個 Comparator
實例以供使用。我們已使用 Spring 的 @Autowired
註解(在偵測到控制器後會自動偵測到)註解了控制器中的 comparator
欄位,並指示 Spring 容器將 Comparator
注入到該欄位中。因此,我們需要確保容器有一個可用的。為此,建立了一個最小的 Comparator
實作,它只是執行不區分大小寫的比較。為了簡單起見,這個類別本身已使用 Spring 的 @Service
刻板印象註解進行註解,這是一種 @Component
類型,因此將會被 Spring 容器自動偵測為容器組件掃描過程的一部分,並注入到控制器中。
package xyz.sample.baremvc;
import java.util.Comparator;
import org.springframework.stereotype.Service;
@Service
public class CaseInsensitiveComparator implements Comparator<String> {
public int compare(String s1, String s2) {
assert s1 != null && s2 != null;
return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
}
}
請注意,我們也可以在容器中透過 Java 基於 @Configuration
類別中的 @Bean
定義或基於 XML 的 bean 定義來宣告此實例,當然在許多情況下,這些變體可能更受歡迎,因為它們提供更高層次的控制。
我們現在可以啟動應用程式並使用 URL(例如 http://localhost:8080/baremvc/compare?input1=Donkey&input2=dog
)來存取它,以練習新程式碼
現在我想鼓勵您更多地了解和試驗 Spring MVC 的完整功能集和在以下領域的全面功能:將請求對應到控制器和方法、資料綁定和驗證、地區設定和主題支援,以及針對幾乎所有符合動作回應模型的網頁層使用案例進行自訂的一般能力。
在此學習過程中,對您來說一個有價值的資源是 Keith Donald 的 Spring MVC 3 Showcase,其中包括顯示 Spring MVC 大多數功能的工作程式碼,您可以輕鬆地將其載入 SpringSource Tool Suite (STS) 或另一個 Eclipse 環境並進行實驗。順便一提,如果您不熟悉 STS,我應該提到它是一個用於試驗 Spring 技術集的絕佳工具,因為它對 Spring 的強大支援以及現成的專案範本等功能。在 這段簡短的影片錄製中,我展示了如何透過 STS 範本開始使用新的 Spring MVC 應用程式。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.sample</groupId>
<artifactId>baremvc</artifactId>
<name>Sprring MVC sample project</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>3.0.6.RELEASE</org.springframework-version>
<org.slf4j-version>1.6.1</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- CGLIB, only required and used for @Configuration usage -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>org.springframework.maven.release</id>
<name>Spring Maven Release Repository</name>
<url>http://maven.springframework.org/release</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>false</enabled></snapshots>
</repository>
<!-- For testing against latest Spring snapshots -->
<repository>
<id>org.springframework.maven.snapshot</id>
<name>Spring Maven Snapshot Repository</name>
<url>http://maven.springframework.org/snapshot</url>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
<!-- For developing against latest Spring milestones -->
<repository>
<id>org.springframework.maven.milestone</id>
<name>Spring Maven Milestone Repository</name>
<url>http://maven.springframework.org/milestone</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java-version}</source>
<target>${java-version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warName>baremvc</warName>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.1</version>
</plugin>
</plugins>
</build>
</project>