1.2 初始化Spring应用

1.2 初始化Spring应用

在本书中,我们将会创建一个名为Taco Cloud的在线应用,它能够订购人类所发明的一种美味,也就是墨西哥煎玉米卷(taco)^1。当然,在这个过程中,为了达成我们的目标,我们将会用到Spring、Spring Boot以及各种相关的库和框架。

我们有多种初始化Spring应用的可选方案。尽管我可以教你手动创建项目目录结构和定义构建规范的各个步骤,但这无疑是浪费时间,我们最好将时间花在编写应用代码上。因此,我们将会学习如何使用Spring Initializr初始化应用。

Spring Initializr是一个基于浏览器的Web应用,同时也是一个REST API,能够生成一个Spring项目结构的骨架,我们还可以使用各种想要的功能来填充它。使用Spring Initializr的几种方式如下:

  • 通过地址为 https://start.spring.io/ 的Web应用;
  • 在命令行中使用curl命令;
  • 在命令行中使用Spring Boot命令行接口;
  • 在Spring Tool Suite中创建新项目;
  • 在IntelliJ IDEA中创建新项目;
  • 在NetBeans中创建新项目。

我将这些细节放到了附录中,这样就不用在这里花费很多页的篇幅介绍每种方案了。在本章和本书中,我都会向你展示如何使用我最钟爱的方式创建新项目:在Spring Tool Suite中使用Spring Initializr。

顾名思义,Spring Tool Suite是一个非常棒的Spring开发环境。它同时还提供了便利的Spring Boot Dashboard特性,这个特性是其他IDE都不具备的(至少在我编写本书的时候如此)。

如果你不是Spring Tool Suite用户,那也没有关系,我们依然可以做朋友。你可以跳转到附录中,查看最适合你的Initializr方案,以此来替换后面小节中的内容。但是,在本书中,我偶尔会提到Spring Tool Suite特有的特性,比如Spring BootDashboard。如果你不使用Spring Tool Suite,那么需要调整这些指令以适配你的IDE。

1.2.1 使用Spring Tool Suite初始化Spring项目

要在Spring Tool Suite中初始化一个新的Spring项目,我们首先要点击File菜单,选择New,接下来选择Spring StarterProject。图1.2展现了要查找的菜单结构。

epub_29101559_9

图1.2 在Spring Tool Suite中使用Initializr初始化一个新项目

在选择Spring Starter Project之后,将会出现一个新的向导对话框(见图1.3)。向导的第一页会询问一些项目的通用信息,比如项目名称、描述和其他必要的信息。如果你熟悉Mavenpom.xml文件的内容,就可以识别出大多数的输入域条目最终都会成为Maven的构建规范。对于Taco Cloud应用来说,我们可以按照图1.3的样子来填充对话框。

epub_29101559_10

图1.3 为Taco Cloud应用指定通用的项目信息

向导的下一页会让我们选择要添加到项目中的依赖(见图1.4)。注意,在对话框的顶部,我们可以选择项目要基于哪个Spring Boot版本。它的默认值是最新的可用版本。一般情况下,最好使用这个默认的值,除非你需要使用不同的版本。

至于依赖项本身,你可以打开各个区域并查找所需的依赖项,也可以在Available顶部的搜索框中对依赖进行搜索。对于TacoCloud应用来说,我们最初的依赖项如图1.4所示。

epub_29101559_11

图1.4 选择Starter依赖

现在,你可以点击Finish来生成项目并将其添加到工作空间中。但是,如果你还想多体验一些,那么可以再次点击Next,看一下新Starter项目向导的最后一页,如图1.5所示。

epub_29101559_12

图1.5 指定备用的Initializr地址

默认情况下,新项目的向导会调用Spring Initializr来生成项目。通常情况下,没有必要覆盖默认值,这也是我们可以在向导的第二页直接点击Finish的原因。但是,如果你基于某种原因托管了自己的Initializr克隆版本(可能是本地机器上的副本或者公司防火墙内部运行的自定义克隆版本),那么你可能需要在点击Finish之前修改Base Url输入域,使其指向自己的Initializr实例。

在点击Finish之后,项目会从Initializr下载并加载到工作空间中。此时,要等待它加载和构建,然后你就可以开始开发应用功能了。下面我们看一下Initializr都为我们提供了什么。

1.2.2 检查Spring项目的结构

项目加载到IDE中之后,我们将其展开,看一下其中都包含什么内容。图1.6展现了Spring Tool Suite中已展开的Taco Cloud项目。

epub_29101559_13

图1.6 Spring Tool Suite中所展现的初始Spring项目结构

你可能已经看出来了,这就是一个典型的Maven或Gradle项目结构,其中应用的源码放到了“src/main/java”中,测试代码放到了“src/test/java”中,而非Java的资源放到了“src/main/resources”。在这个项目结构中,我们需要注意以下几点。

  • mvnw和mvnw.cmd:这是Maven包装器(wrapper)脚本。借助这些脚本,即便你的机器上没有安装Maven,也可以构建项目。
  • pom.xml:这是Maven构建规范,随后我们将会深入介绍该文件。
  • TacoCloudApplication.java:这是Spring Boot主类,它会启动该项目。随后,我们会详细介绍这个类。
  • application.properties:这个文件起初是空的,但是它为我们提供了指定配置属性的地方。在本章中,我们会稍微修改一下这个文件,但是我会将配置属性的详细阐述放到第5章。
  • static:在这个文件夹下,你可以存放任意为浏览器提供服务的静态内容(图片、样式表、JavaScript等),该文件夹初始为空。
  • templates:这个文件夹中存放用来渲染内容到浏览器的模板文件。这个文件夹初始是空的,不过我们很快就会往里面添加Thymeleaf模板。
  • TacoCloudApplicationTests.java:这是一个简单的测试类,它能确保Spring应用上下文可以成功加载。在开发应用的过程中,我们会将更多的测试添加进来。

随着Taco Cloud应用功能的增长,我们会不断使用Java代码、图片、样式表、测试以及其他附属内容来充实这个项目结构。不过,在此之前,我们先看一下Spring Initializr提供的几个条目。

探索构建规范

在填充Initializr表单的时候,我们声明项目要使用Maven来进行构建。因此,Spring Initializr所生成的pom.xml文件已经包含了我们所选择的依赖。程序清单1.1展示了Initializr为我们提供的完整pom.xml。

程序清单1.1 初始的Maven构建规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sia</groupId>
<artifactId>taco-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> ⇽--- 打包为JAR
<name>taco-cloud</name>
<description>Taco Cloud Example</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version> ⇽--- Spring Boot的版本
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>
UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency> ⇽--- Starter依赖
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>htmlunit-driver</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin> ⇽--- Spring Boot插件
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

在pom.xml文件中,我们第一个需要注意的地方就是<packaging>。我们选择了将应用构建成一个可执行的JAR文件,而不是WAR文件。这可能是你所做出的最奇怪的选择之一,对Web应用来说尤为如此。毕竟,传统的Java Web应用都是打包成WAR文件,JAR只是用来打包库和较为少见的桌面UI应用的。

打包为JAR文件是基于云思维做出的选择。尽管WAR文件非常适合部署到传统的Java应用服务器上,但对于大多数云平台来说它们并不是理想的选择。有些云平台(比如CloudFoundry)也能够部署和运行WAR文件,但是所有的Java云平台都能够运行可执行的JAR文件。因此,Spring Initializr默认会使用基于JAR的打包方式,除非我们明确告诉它采用其他的方式。

如果你想要将应用部署到传统的Java应用服务器上,那么需要选择使用基于WAR的打包方式并要包含一个Web初始化类。在第2章中,我们将会更详细地了解如何构建WAR文件。

接下来,请留意<parent>元素,更具体来说是它的<version>子元素。这表明我们的项目要以spring-boot-starter-parent作为其父POM。除了其他的一些功能之外,这个父POM为Spring项目常用的一些库提供了依赖管理,现在你不需要指定它们的版本,因为这是通过父POM来管理的。这里的2.0.4.RELEASE表明要使用Spring Boot 2.0.4,所以会根据这个版本的Spring Boot定义来继承依赖管理。

既然我们谈到了依赖的话题,那么需要注意在<dependencies>元素下声明了3个依赖。在某种程度上,你可能会对前两个更熟悉一些。它们直接对应我们在Spring ToolSuite新项目向导中点击Finish之前所选择的Web和Thymeleaf依赖。第三个依赖提供了很多有用的测试功能。我们没有必要在专门的复选框中选择它,因为Spring Initializr假定你将会编写测试(希望你会正确地开展这项工作)。

你可能也会注意到这3个依赖的artifact ID上都有starter这个单词。Spring Boot starter依赖的特别之处在于它们本身并不包含库代码,而是传递性地拉取其他的库。这种starter依赖主要有3个好处。

  • 构建文件会显著减小并且更易于管理,因为这样不必为每个所需的依赖库都声明依赖。
  • 我们能够根据它们所提供的功能来思考依赖,而不是根据库的名称。如果是开发Web应用,那么你只需要添加webstarter就可以了,而不必添加一堆单独的库再编写Web应用。
  • 我们不必再担心库版本的问题。你可以直接相信给定版本的Spring Boot,传递性引入的库的版本是兼容的。现在,你只需要关心使用的是哪个版本的Spring Boot就可以了。

最后,构建规范还包含一个Spring Boot插件。这个插件提供了一些重要的功能。

  • 它提供了一个Maven goal,允许我们使用Maven来运行应用。在1.3.4小节,我们将会尝试这个goal。
  • 它会确保依赖的所有库都会包含在可执行JAR文件中,并且能够保证它们在运行时类路径下是可用的。
  • 它会在JAR中生成一个manifest文件,将引导类(在我们的场景中,也就是TacoCloudApplication)声明为可执行JAR的主类。

谈到了主类,我们打开它看一下。

引导应用

因为我们将会通过可执行JAR文件的形式来运行应用,所以很重要的一点就是要有一个主类,它将会在JAR运行的时候被执行。我们同时还需要一个最小化的Spring配置,以引导该应用。这就是TacoCloudApplication类所做的事情,如程序清单1.2所示。

程序清单1.2 Taco Cloud的引导类

1
2
3
4
5
6
7
8
9
package tacos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication //⇽--- Spring Boot应用
public class TacoCloudApplication {
public static void main(String[] args) {
SpringApplication.run(TacoCloudApplication.class, args); //⇽--- 运行应用
}
}

尽管在TacoCloudApplication中只有很少的代码,但是它包含了很多的内容。其中,最强大的一行代码也是最短的。@SpringBootApplication注解明确表明这是一个Spring Boot应用。但是,@SpringBootApplication远比看上去更强大。

@SpringBootApplication是一个组合注解,它组合了3个其他的注解。

  • @SpringBootConfiguration:将该类声明为配置类。尽管这个类目前还没有太多的配置,但是后续我们可以按需添加基于Java的Spring框架配置。这个注解实际上是@Configuration注解的特殊形式。
  • @EnableAutoConfiguration:启用Spring Boot的自动配置。我们随后会介绍自动配置的更多功能。就现在来说,我们只需要知道这个注解会告诉Spring Boot自动配置它认为我们会用到的组件。
  • @ComponentScan:启用组件扫描。这样我们能够通过像@Component、@Controller、@Service这样的注解声明其他类,Spring会自动发现它们并将它们注册为Spring应用上下文中的组件。

TacoCloudApplication另外一个很重要的地方是它的main()方法。这是JAR文件执行的时候要运行的方法。在大多数情况下,这个方法都是样板代码,我们编写的每个Spring Boot应用都会有一个类似或完全相同的方法(类名不同则另当别论)。

这个main()方法会调用SpringApplication中静态的run()方法,后者会真正执行应用的引导过程,也就是创建Spring的应用上下文。在传递给run()的两个参数中,一个是配置类,另一个是命令行参数。尽管传递给run()的配置类不一定要和引导类相同,但这是最便利和最典型的做法。

你可能并不需要修改引导类中的任何内容。对于简单的应用程序来说,你可能会发现在引导类中配置一两个组件是非常方便的,但是对于大多数应用来说,最好还是要为没有实现自动配置的功能创建一个单独的配置类。在本书的整个过程中,我们将会创建多个配置类,所以请继续关注后续的细节。

测试应用

测试是软件开发的重要组成部分。鉴于此,Spring Initializr为我们提供了一个测试类作为起步。程序清单1.3展现了这个测试类的概况。

程序清单1.3 应用测试类的概况

1
2
3
4
5
6
7
8
9
10
11
12
package tacos;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class) //⇽--- 使用Spring的运行器
@SpringBootTest //⇽--- Spring Boot测试
public class TacoCloudApplicationTests {
@Test //⇽--- 测试方法
public void contextLoads() {
}
}

TacoCloudApplicationTests类中的内容并不多:这个类中只有一个空的测试方法。即便如此,这个测试类还是会执行必要的检查,确保Spring应用上下文能够成功加载。如果你所做的变更导致Spring应用上下文无法创建,那么这个测试将会失败,你就可以做出反应来解决相关的问题了。

TacoCloudApplicationTests类中的内容并不多:这个类中只有一个空的测试方法。即便如此,这个测试类还是会执行必要的检查,确保Spring应用上下文能够成功加载。如果你所做的变更导致Spring应用上下文无法创建,那么这个测试将会失败,你就可以做出反应来解决相关的问题了。

另外,注意这个类带有@RunWith(SpringRunner.class)注解。@RunWith是JUnit的注解,它会提供一个测试运行器(runner)来指导JUnit如何运行测试。可以将其想象为给JUnit应用一个插件,以提供自定义的测试行为。在本例中,为JUnit提供的是SpringRunner,这是一个Spring提供的测试运行器,它会创建测试运行所需的Spring应用上下文。

测试运行器的其他名称

如果你已经熟悉如何编写Spring测试或者见过其他一些基于Spring的测试类,那么你可能见过名为SpringJUnit4ClassRunner的测试运行器。SpringRunner是SpringJUnit4ClassRunner的别名,是在Spring 4.3中引入的,以便于移除对特定JUnit版本的关联(比如,JUnit 4)。毫无疑问,这个别名更易于阅读和输入。

@SpringBootTest会告诉JUnit在启动测试的时候要添加上Spring Boot的功能。从现在开始,我们可以将这个测试类视同为在main()方法中调用SpringApplication.run()。在这本书中,我们将会多次看到@SpringBootTest,而且会不断见识它的威力。

最后,就是测试方法本身了。尽管@RunWith(SpringRunner.class)和@SpringBootTest会为测试加载Spring应用上下文,但是如果没有任何测试方法,那么它们其实什么事情都没有做。即便没有任何断言或代码,这个空的测试方法也会提示这两个注解完成了它们的工作并成功加载Spring应用上下文。如果这个过程中有任何问题,那么测试都会失败。

此时,我们已经看完了Spring Initializr为我们提供的代码。我们看到了一些用来开发Spring应用程序的基础样板,但是还没有编写任何代码。现在是时候启动IDE、准备好键盘并向TacoCloud应用程序添加一些自定义的代码了。