领先一步
VMware 提供培训和认证,助您加速进步。
了解更多Spring MVC 作为核心 Spring Framework 的一部分,是一个成熟且功能强大的动作-响应式 Web 框架,具有广泛的能力和选项,旨在处理各种面向 UI 和非面向 UI 的 Web 层用例。所有这些对于 Spring MVC 新手来说可能令人望而生畏。我认为向这类读者展示启动和运行一个最简单的 Spring MVC 应用程序所需的工作量非常少是有益的(也就是说,把我的例子视为类似世界上最简单的 Spring MVC 应用程序),这就是本文其余部分将要演示的内容。
我假设你熟悉 Java、Spring(基本的依赖注入概念)以及基本的 Servlet 编程模型,但不了解 Spring MVC。阅读这篇博客文章后,读者可以继续学习 Spring MVC,方法是查看 Keith Donald 的Spring MVC 3 Showcase,或参考涵盖 Spring 和 Spring MVC 的各种其他在线和印刷资源。
关于依赖和构建系统:本文不假设您正在使用特定的构建系统,例如 Maven、Gradle 或 Ant。文章末尾包含了一个相当简洁的 Maven POM 示例文件,可作为参考。
Spring MVC 包含了大多数其他所谓的 Web MVC 框架的基本概念。传入的请求通过一个前端控制器 (Front Controller) 进入框架。在 Spring MVC 中,这是一个实际的 Java Servlet,名为 DispatcherServlet
。可以将 DispatcherServlet
视为守门员。它不执行任何实际的 Web 或业务逻辑,而是将工作委托给称为控制器 (Controllers) 的 POJO,实际工作在那里完成(无论是全部完成还是通过后端完成)。工作完成后,由视图 (Views) 负责以适当的格式生成输出(无论是 JSP 页面、Velocity 模板还是 JSON 响应)。使用策略 (Strategies) 来决定哪个控制器(以及该控制器内的哪个方法)处理请求,以及哪个视图渲染响应。Spring 容器用于将所有这些部分连接在一起。它看起来像这样
DispatcherServlet
。就像 Java EE 应用程序中的任何其他 Servlet 一样,我们通过 Web 应用的 WEB-INF/web.xml
中的一个配置告诉 Java EE 容器在 Web 应用启动时加载此 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
初始化参数来自定义 DispatcherServlet 加载的 Spring Application Context 的基本配置 XML 文件的位置,而不是依赖于默认位置 <servletname>-context.xml)。DispatcherServlet 加载的 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
注解进行标注,表明这是一个能够处理 Web 请求的 Spring MVC 控制器。由于 @Controller
是 Spring @Component
Stereotype 注解的特化,该类将作为容器组件扫描过程的一部分被 Spring 容器自动检测到,从而创建一个 Bean 定义,并允许像任何其他 Spring 管理的组件一样进行依赖注入。home
方法已使用 @RequestMapping
注解进行标注,指定该方法应处理到路径 "/" 的 Web 请求,即应用程序的主页 (home) 路径。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
)注解的代码。您会注意到,为了提高效率,我们限制了 Spring 在类路径中扫描的包空间范围(在本例中限制为 xyz.sample.baremvc
)http://localhost:8080/baremvc
,结果将显示预期的问候语:尽管它很简单,但运行此应用程序涉及到工作中的 Spring MVC 应用程序的所有主要部分。让我们逐步了解主要的序列和组件交互
web.xml
中的配置,DispatcherServlet 被加载并初始化。http://localhost:8080/baremvc
的 HTTP 请求到达 Servlet 引擎并被路由到我们的 (baremvc) Web 应用。HandlerAdapter
的策略 (strategy) 来决定将请求路由到何处。可以定制要使用的特定 HandlerAdapter 类型(或多种类型,因为它们可以链式使用),但默认情况下使用基于注解的策略,它根据这些类中 @RequestMapping
注解中的匹配条件,将请求适当地路由到标注为 @Controller
的类中的特定方法。在本例中,匹配到 home 方法上的正则表达式,并调用它来处理请求。WEB-INF/views/home.jsp
),以帮助选择要渲染响应的视图。ViewResolver
的策略来决定哪个视图负责渲染响应。可以根据应用程序需要配置此策略(以简单或链式方式),但默认情况下使用 InternalResourceViewResolver
。这是一个非常简单的视图解析器,它生成一个 JstlView
,该视图简单地委托给 Servlet 引擎内部的 RequestDispatcher
进行渲染,因此适用于 JSP 页面或 HTML 页面。如前所述,将视图模板的路径硬编码到控制器中是不合适的,就像我们当前的控制器那样。控制器和视图之间更松散、更具逻辑性的耦合,控制器专注于执行某些 Web 或业务逻辑,并且通常不关心视图路径、JSP 与其他模板技术等具体细节,这是关注点分离 (separation of concerns) 的一个例子。这使得控制器和视图都能得到更大的重用,并且更容易独立地进行演变,可能由不同的人负责不同类型的代码。
本质上,理想的控制器代码应该类似于这个变体,返回一个纯逻辑的视图名称(无论是简单的还是组合的)
//...
@Controller
public class HomeController {
@RequestMapping(value = "/")
public String home() {
System.out.println("HomeController: Passing through...");
return "home";
}
}
Spring MVC 的 ViewResolver
策略 (Strategy) 实际上是用于实现控制器和视图之间这种更松散耦合的机制。如前所述,在应用程序未配置特定的 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
,这个包含(解析器)bean 的新配置定义会被 Spring 容器自动检测到。然后 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 应用程序的一种方式。例如,对于更 RESTful 的方法,很容易获取嵌入在请求 URL 路径本身中的参数result
为键的 Model 对象中,以便视图可以访问它。简单地说,可以将 Model 视为一个功能更强的哈希映射 (hashmap)。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
Stereotype 注解(属于 @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);
}
}
请注意,我们也可以通过 @Configuration
类中基于 Java 的 @Bean
定义或基于 XML 的 Bean 定义轻松地在容器中声明此类的实例,当然在许多情况下,这些变体可能更受青睐,因为它们提供了更高的控制级别。
我们现在可以启动应用程序,并通过类似 http://localhost:8080/baremvc/compare?input1=Donkey&input2=dog
的 URL 访问它,以测试新代码
现在我鼓励您进一步学习和尝试 Spring MVC 的全部功能和综合能力,包括请求到控制器和方法的映射、数据绑定和验证、区域设置和主题支持,以及能够根据需要进行定制以处理几乎所有符合动作-响应模型的 Web 层用例。
在此学习过程中,一个有价值的资源是 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>