绿豆: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。阅读完这篇博文后,读者可以通过查看 Keith Donald 的Spring MVC 3 Showcase或其他各种涵盖 Spring 和 Spring MVC 的在线和印刷资源来继续学习 Spring MVC。

关于依赖项和构建系统的说明:本文不假设您正在使用特定的构建系统,例如 Maven、Gradle 或 Ant。本文末尾包含一个相当简单的示例 Maven POM 文件。

Spring MVC 包含与其他所谓的 Web MVC 框架大多数相同的基本概念。传入的请求通过前端控制器进入框架。在 Spring MVC 的情况下,这是一个名为DispatcherServlet的实际 Java Servlet。将DispatcherServlet视为门卫。它不执行任何真正的 Web 或业务逻辑,而是委托给称为控制器的 POJO,在那里执行实际工作(全部或通过后端)。完成工作后,由视图负责以正确的格式生成输出(无论是 JSP 页面、Velocity 模板还是 JSON 响应)。策略用于确定哪个控制器(以及该控制器内的哪个方法)处理请求,以及哪个视图呈现响应。Spring 容器用于将所有这些部分连接在一起。它看起来有点像这样

引导 DispatcherServlet 和 Spring 容器

如前所述,所有传入的请求都流经DispatcherServlet。与 Java EE 应用程序中的任何其他 Servlet 一样,我们通过 Web 应用程序的WEB-INF/web.xml中的一个来告诉 Java EE 容器在 Web 应用程序启动时加载此 Servlet。DispatcherServlet还负责加载一个 SpringApplicationContext,该上下文用于执行托管组件的连接和依赖注入。在此基础上,我们向 Servlet 指定一些初始化参数来配置应用程序上下文。让我们看一下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初始化参数来自定义 Spring Application Context 的基本配置 XML 文件的位置,该上下文由 DispatcherServlet 加载,而不是依赖于<servletname>-context.xml的默认位置)。
等等,如果有人不想通过 XML 配置 Spring 怎么办?

DispatcheServlet 加载的默认类型的应用程序上下文期望加载至少一个包含 Spring bean 定义的 XML 文件。正如您将看到的,我们还将启用 Spring 加载基于 Java 的配置,以及 XML。

每个人在这个领域都会有自己的(有时非常强烈)的观点,但是虽然我通常更喜欢基于 Java 的配置,但我确实认为在某些情况下使用少量 XML 配置仍然更有意义,原因有很多(例如,能够在不重新编译的情况下更改配置、XML 命名空间的简洁性、可工具性等)。在此基础上,此应用程序将使用混合方法,同时支持 Java 和 XML。

请放心,如果您更喜欢纯 Java 方法,完全不使用 Spring XML,则可以通过在web.xml中设置一个初始化参数来覆盖默认的应用程序上下文类型并使用名为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原型注解的专门化,所以 Spring 容器将在容器的组件扫描过程中自动检测该类,创建 bean 定义并允许像任何其他 Spring 托管组件一样依赖注入实例。
  • home方法已使用@RequestMapping注解进行注释,指定此方法应处理对路径“/”的 Web 请求,即应用程序的主页路径。
  • 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)进行注释的代码。您会注意到,为了提高效率,我们限制了(在本例中为xyz.sample.baremvc)Spring 应该在类路径中扫描的包空间的哪一部分
  • <mvc:annotation-driven>声明设置了 Spring MVC 对将请求路由到 @Controller 的支持,以及如何处理一些诸如转换、格式化和验证之类的事情(根据类路径中存在哪些(库)使用一些合理的默认值,以及在需要时覆盖的能力)
Web 应用程序现在已准备好运行。假设 Servlet 容器(在我的情况下为 tc Server)设置为侦听 localhost:8080,启动应用程序,然后通过浏览器访问 URLhttps://127.0.0.1:8080/baremvc,将显示预期的问候语:

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

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

提升到下一个级别

在这一点上,我们有一个应用程序,它当然有资格被称为世界上最简单的 Spring MVC 应用程序,但坦率地说,它并没有真正满足该描述的精神。让我们将事情提升到另一个层次。

如前所述,将视图模板的路径硬编码到控制器中是不合适的,就像我们当前的控制器所做的那样。控制器和视图之间更松散、更逻辑的耦合,控制器专注于执行一些 Web 或业务逻辑,并且通常与特定细节(如视图路径或 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,所以这个包含(解析器)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 方法。
  • 我们期望调用者将两个 String 输入参数作为 GET 请求的一部分传递给我们,因此我们通过 @RequestParam 注解获取它们。请注意,我们依赖于此注解的默认处理,该处理假定这些参数是必需的。如果缺少这些参数,客户端将收到 HTTP 400 错误。另请注意,这只是将参数传递到 Spring MVC 应用程序的一种方式。例如,很容易获取作为请求 URL 路径本身一部分嵌入的参数,以实现更 REST 风格的方法。
  • 我们使用 Comparator 实例来比较这两个字符串。
  • 我们将比较结果放入 Model 对象的键 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 实例。为此,创建了一个最小的 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 定义来声明此实例,并且在许多情况下,这些变体可能是首选,因为它们提供了更高的控制级别。

现在我们可以启动应用程序并使用诸如 https://127.0.0.1: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 社区中所有即将举行的活动。

查看全部