青豆:Spring MVC 入门

工程 | Colin Sampaleanu | 2011 年 1 月 4 日 | ...

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 和 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>

这里完成了一些事情

  • 我们将 DispatcherServlet 注册为一个名为 appServlet 的 Servlet
  • 我们将此 Servlet 映射为处理以 "/" 开头(相对于应用路径)的传入请求
  • 我们使用 ContextConfigLocation 初始化参数来自定义 DispatcherServlet 加载的 Spring Application Context 的基本配置 XML 文件的位置,而不是依赖于默认位置 <servletname>-context.xml)。
等等,如果有人不想通过 XML 配置 Spring 怎么办?

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 的事实硬编码到代码中,您觉得这不对,那您是对的。我们稍后会解决这个问题)
现在,我们需要创建视图。这个 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>

让我们检查一下这个文件的内容

  • 您会注意到这里使用了几种不同的 Spring XML 命名空间:contextmvc 和默认的 beans
  • <context:component-scan> 声明确保 Spring 容器进行组件扫描,以便自动发现任何使用 @Component 子类型(例如 @Controller)注解的代码。您会注意到,为了提高效率,我们限制了 Spring 在类路径中扫描的包空间范围(在本例中限制为 xyz.sample.baremvc
  • <mvc:annotation-driven> 声明设置了 Spring MVC 对请求路由到 @Controllers 的支持,以及如何处理转换、格式化和验证等方面的内容(基于类路径中存在的(库)提供了一些合理的默认设置,并可以在需要时进行覆盖)
Web 应用现在已准备好运行。假设 Servlet 容器(我的例子中使用 tc Server)设置为在 localhost:8080 监听,启动应用程序并通过浏览器访问 URL http://localhost:8080/baremvc,结果将显示预期的问候语:

尽管它很简单,但运行此应用程序涉及到工作中的 Spring MVC 应用程序的所有主要部分。让我们逐步了解主要的序列和组件交互

  • Web 应用启动时,由于 web.xml 中的配置,DispatcherServlet 被加载并初始化。
  • DispatcherServlet 加载基于注解的 Application Context,该上下文已配置为通过指定基本包的正则表达式扫描带注解的组件。
  • 带注解的组件(如 HomeController)被容器检测到。
  • http://localhost:8080/baremvc 的 HTTP 请求到达 Servlet 引擎并被路由到我们的 (baremvc) Web 应用。
  • URL 末尾隐含的 "/" 路径匹配为 DispatcherServlet 注册的正则表达式,请求被路由到它
  • DispatcherServlet 需要决定如何处理请求。它使用一个称为 HandlerAdapter策略 (strategy) 来决定将请求路由到何处。可以定制要使用的特定 HandlerAdapter 类型(或多种类型,因为它们可以链式使用),但默认情况下使用基于注解的策略,它根据这些类中 @RequestMapping 注解中的匹配条件,将请求适当地路由到标注为 @Controller 的类中的特定方法。在本例中,匹配到 home 方法上的正则表达式,并调用它来处理请求。
  • home 方法完成其工作,在本例中只是向系统输出打印一些内容。然后它返回一个字符串,这是一个提示(在本例中是一个非常明确的提示,即 WEB-INF/views/home.jsp),以帮助选择要渲染响应的视图。
  • DispatcherServlet 再次依赖于一个称为 ViewResolver 的策略来决定哪个视图负责渲染响应。可以根据应用程序需要配置此策略(以简单或链式方式),但默认情况下使用 InternalResourceViewResolver。这是一个非常简单的视图解析器,它生成一个 JstlView,该视图简单地委托给 Servlet 引擎内部的 RequestDispatcher 进行渲染,因此适用于 JSP 页面或 HTML 页面。
  • Servlet 引擎通过指定的 JSP 渲染响应

更进一步

到目前为止,我们已经构建了一个确实符合世界上最简单的 Spring MVC 应用程序的应用,但坦率地说,它并没有真正达到该描述的精神。让我们将事情发展到另一个层次。

如前所述,将视图模板的路径硬编码到控制器中是不合适的,就像我们当前的控制器那样。控制器和视图之间更松散、更具逻辑性的耦合,控制器专注于执行某些 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 采用一种非常简单的策略;它仅仅接收控制器返回的视图名称,并在其前面添加一个可选的前缀(默认为空),在其后面添加一个可选的后缀(默认为空),然后将生成的结果路径提供给它创建的 JstlViewJstlView 随后委托给 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 的一个优势,即其灵活的特性)。

处理用户输入

几乎任何 Web 应用都需要接收客户端的输入,对其进行处理,然后返回或渲染结果。将输入导入 Spring MVC 应用程序的方法有很多种,渲染结果的方法也有很多种,但至少让我们展示其中一种变体。在这个简单的例子中,我们将修改 HomeController,添加一个新的处理方法,该方法接收两个字符串输入,比较它们,并返回结果。

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 方法
  • 我们期望调用者将两个字符串输入参数作为 GET 请求的一部分传递给我们,因此我们通过 @RequestParam 注解获取它们。请注意,我们依赖此注解的默认处理方式,即这些参数是必需的。如果它们缺失,客户端将收到 HTTP 400 错误。另请注意,这只是将参数传递到 Spring MVC 应用程序的一种方式。例如,对于更 RESTful 的方法,很容易获取嵌入在请求 URL 路径本身中的参数
  • 我们使用 Comparator 实例来比较这两个字符串
  • 我们将比较结果放入以 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&amp;input2=dog 的 URL 访问它,以测试新代码

后续步骤

我只是触及了 Spring MVC 功能的冰山一角,但希望这篇博客文章能让您了解开始使用 Spring MVC 是多么容易,以及框架中的一些核心概念是如何关联在一起的。此时可能也适合提及,为了更容易理解核心概念,我的示例中有一些(希望非常明显)的处理方式与在更大或生产应用程序中的方式不同,例如,将消息硬编码在 Java 代码内部,或一些包组织方式。

现在我鼓励您进一步学习和尝试 Spring MVC 的全部功能和综合能力,包括请求到控制器和方法的映射、数据绑定和验证、区域设置和主题支持,以及能够根据需要进行定制以处理几乎所有符合动作-响应模型的 Web 层用例。

在此学习过程中,一个有价值的资源是 Keith Donald 的Spring MVC 3 Showcase,其中包含演示 Spring MVC 大多数功能的工作代码,您可以轻松将其加载到 SpringSource Tool Suite (STS) 或其他 Eclipse 环境中进行实验。顺便说一句,如果您不熟悉 STS,我应该提一下,它是用于试验 Spring 技术集的一个很棒的工具,因为它对 Spring 的出色支持以及内置的项目模板等功能。在这段简短的视频录制中,我展示了如何通过 STS 模板开始构建一个新的 Spring MVC 应用程序。

附录 - 依赖

无论您使用哪种构建系统(如今,我通常更喜欢 Gradle 或 Maven),上面的代码都应该可以工作。这里提供了一个相对简洁的 Maven POM 示例文件,用于构建上述项目,可用作基础,或帮助理解所需的依赖项。实际上,它并没有尽可能地简洁,因为我明确地将 commons-logging 替换为 SLF4J,并添加了一些可选的仓库和 Maven 插件。唯一不太明显的地方可能是对 CGLIB 的需求,这是 Spring 在使用 @Configuration 时所需的可选依赖项。

<?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>

订阅 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获取支持

Tanzu Spring 通过一项简单的订阅,提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部