A.6 使用元框架创建Spring应用

另外值得一提的是,有一些基于Spring和Spring Boot构建的框架:

  • Grails。
  • JHipster。

这些元框架(meta-framework)在更高层级上提供了快速开发Spring应用的能力,同时依然能够使用Spring和Spring Boot所提供的所有功能。

这些元框架都有自己独特的开发模型,实际上,它们本身就是框架,因此在本附录中简单地将它们作为项目初始化机制有点不公平。事实上,每个元框架都值得写一本书。

我们不会深入研究如何使用这些元框架来初始化Spring项目,在这里将它们列出来只是让读者知道还有初始化和开发Spring应用的其他方式。

A.5 使用命令行初始化项目

Spring Initializr的IDE和基于浏览器的用户界面可能是初始化项目的常见方式。它们都是Initializr应用程序提供的REST服务的客户端。在某些特殊情况下(例如,在脚本化场景中),我们可能会发现直接从命令行使用Initializr服务也很有用。

我们有两种消费该API的方式:

  • 使用curl命令(或者类似的命令行REST客户端);
  • 使用Spring Boot的命令行接口(又称为Spring Boot CLI)。

下面我们看一下这两种方案,先从curl命令开始。

A.5.1 curl和Initializr API

使用curl初始化Spring项目的最简单方式是按照如下格式消费该API:

1
% curl https://start.spring.io/starter.zip -o demo.zip

在本例中,我们请求了Initializr的“/starter.zip”端点,它将会生成一个Spring项目并下载为zip文件。生成的项目是使用Maven构建的,并且除了Spring Bootstarter依赖外并没有其他的依赖,pom.xml文件中的所有项目信息都是默认值。

如果不进行特殊指定,文件名将会是starter.zip。在本例中,-o选项将下载的文件命名为demo.zip。

对外公开的Spring Initializr服务器托管在https://start.spring.io上,如果你想要使用自定义Initializr,那么需要对应地修改URL。

除了默认值之外,我们可能还想要指定一些详情信息和依赖。表A.1列出了消费Spring Initializr REST服务所有可用的参数(及其默认值)。

表A.1 Initializr API支持的请求参数

epub_29101559_157

我们可以通过发送请求至基础Initializr URL获取参数列表和可用依赖项的列表:

1
% curl https://start.spring.io

在这些参数中,我们可能会发现dependencies是最常用的。例如,我们想要创建一个使用Spring的简单Web项目。如下使用curl的命令行将会生成一个包含webstarter依赖的项目zip包:

1
2
3
% curl https://start.spring.io/starter.zip \
-d dependencies=web \
-o demo.zip

假设有一个更复杂的样例:我们想要开发一个使用Spring Data JPA进行数据持久化的Web应用程序,使用Gradle构建,位于zip文件中名为my-dir的目录下,并且我们不仅想要下载zip文件,还希望在下载时将项目解压到文件系统中。在这种情况下,下面的命令可以解决该问题:

1
2
3
4
% curl https://start.spring.io/starter.tgz \
-d dependencies=web,data-jpa \
-d type=gradle-project
-d baseDir=my-dir | tar -xzvf -

在这里,下载的zip文件将会以管道的方式传递给tar命令进行解压。

A.5.2 Spring Boot命令行接口

Spring Boot CLI是另一种初始化Spring应用的方案。我们能够以多种方式安装Spring Boot CLI,最简单的(也是我最喜欢的)方式可能是使用SDKMAN:

1
% sdk install springboot

Spring Boot CLI安装完成之后,我们就可以使用它来生成项目了。使用方式与curl非常类似,这里我们要使用的命令是spring init。实际上,使用Spring Boot CLI生成项目的最简单方式为:

1
% spring init

这样会生成一个Spring Boot项目的骨架,并且会下载成名为demo.zip的zip文件。

有时我们可能想要指明一些详情信息和依赖,表A.2列出了spring init命令所有可用的参数。

表A.2 spring init命令支持的所有请求参数

epub_29101559_158

通过使用–list参数,我们可以列出参数的列表以及可用依赖:

1
% spring init --list

假设我们希望创建一个基于Java 1.7的Web应用,那么如下使用–dependencies和–java命令即可:

1
% spring init --dependencies=web --java-version=1.7

假设我们想要创建一个使用Spring Data JPA进行数据持久化的Web应用程序,并且还希望使用Gradle构建它,而不是使用Maven,那么我们可以使用如下的命令:

1
% spring init --dependencies=web,jpa --type=gradle-project

你可能已经发现,spring init的很多参数与curl方案的参数相同或相似。也就是说,spring init并没有支持curl方案中的所有参数(比如baseDir),而且参数是中划线分割的而不是采用驼峰命名(例如package-name与packageName)。

A.4 在start.spring.io中初始化项目

到目前为止所描述的基于IDE的初始化方案可能会满足你的需求,但是有时候你可能需要完全不同的IDE,或者使用简单的文本编辑器。在这种情况下,你依然可以借助基于Web界面的Initializr来使用Spring Initializr。

首先,在Web浏览器中访问 https://start.spring.io 。我们将会看到简单版本的Spring Initializr Web用户界面,如图A.15所示。

image-20211022214010291

图A.15 简单版本的Spring Initializr Web界面

在简单版本的Initializr Web应用中,我们只需要填写一些非常基本的信息,比如使用Maven还是Gradle进行构建、开发项目要使用什么语言、基于什么版本的Spring Boot进行构建以及项目的group和artifact ID。

我们还可以通过在Search for Dependencies文本框中输入搜索条件来指明依赖。例如,如图A.16所示,我们可以输入“web”来搜索带有“web”关键字的依赖。

image-20211022214019020

图A.16 搜索依赖

当看到自己想要的依赖时,在键盘上按Return键来选中它,它就会添加到选中依赖的列表中。在图A.17中,Selected Dependencies文本下面显示已经选中了Web、Thymeleaf、DevTools和Lombok依赖。

image-20211022214029301

图A.17 选择依赖

如果不想要某个已选中的依赖,只需要点击依赖条目右侧的×就可以将其移除。

完成之后,我们可以点击Generate Project按钮(也可以使用按钮上所显示的快捷键,不同操作系统下会有所差异),让Initializr初始化项目并下载为zip文件。然后,我们就可以解压该文件并导入你所选择的IDE或文本编辑器了。

如果你希望更细粒度地控制项目创建的过程,可以点击Generate Project下的Switch to the full version链接,这样用户界面会展现更多的输入域和所有可用依赖的复选框列表。图A.18显示了Web界面完整版本的一部分。

image-20211022214042949

图A.18 完整版本的Initializr用户界面(部分)

完整版本中的大多数输入域都衍生自Group和Artifact输入域,或者在简单版本中已经有了默认值。完整版本能够让我们重写这些衍生值/默认值。

图A.18只展现了完整版本中可用依赖复选框的一部分,所以你可能需要向下滑动很久才能找到想要的依赖。不过,在完整版本的用户界面中,搜索框依然是可用的。

A.3 使用NetBeans初始化项目

要使用NetBeans创建新项目,首先要选择File菜单的New Project菜单项,如图A.10所示。

image-20211022213836364

图A.10 使用NetBeans初始化一个新的Spring项目

此时我们会看到新项目向导的第一页,如图A.11所示。该页面会让我们选择想要创建什么类型的项目。

image-20211022213851422

图A.11 创建新的Spring Boot Initializr项目

对于Spring Boot项目来说,我们要从左侧的列表中选择Maven,然后在右侧的项目列表中选择Spring Boot Initializr Project,然后点击Next按钮进入下一页。

新项目向导的第二页(见图A.12)允许我们设置项目的基本信息,比如项目名称、版本以及Maven pom.xml文件中定义项目的其他信息。

image-20211022213902798

图A.12 声明项目的基本信息

在声明完项目的基本信息之后,点击Next按钮进入新项目向导的依赖页,参见图A.13。

image-20211022213921937

图A.13 选择项目的依赖

依赖会按照分类的形式全部列在同一个列表中。如果在查找特定依赖时遇到麻烦,可以使用顶部的Filter文本框限制列表中可选项的数量。

在这个页面中,还可以指定想要使用哪个Spring Boot版本。它默认会设置为当前Spring Boot的正式版本。

在为项目选择完依赖之后,点击Next按钮会显示新项目向导的最后一页,如图A.14所示。这个页面允许我们声明项目的一些详情信息,包括项目的名称以及文件系统中的位置(Project Folder文本域是只读的,它的值会根据其他两个文本域的值衍生而来)。在这里,还允许我们通过Maven Spring Boot插件运行和调试项目,而不是使用NetBeans。我们还可以让NetBeans移除生成项目中的Maven包装器。

image-20211022213940930

图A.14 指明项目的名称和位置

在设置完项目的所有信息后,点击Finish按钮,项目将会创建并添加到NetBeans的工作空间中。

A.2 使用IntelliJ IDEA初始化项目

如果要使用IntelliJ IDEA初始化Spring项目,就在File > New菜单下选择Project菜单项,如图A.5所示。

image-20211022213640922

图A.5 在IntelliJ IDEA中初始化一个新的Spring项目

此时,将会打开新Spring Initializr项目向导的第一页(参见图A.6)。在本页中,通常我们会直接点击Next按钮进入下一页。如果你想要使用与 https://start.spring.io 不同的Spring Initializr,就可以选中Custom单选框,并输入想要使用的Spring Initializr的基础URL。

image-20211022213653302

图A.6 选择Spring Initializr的位置

点击Next按钮之后,我们将会看到一个输入项目基本信息的页面,如图A.7所示。你会发现,这个页面上有很多输入域与Maven pom.xml中的信息是一致的。实际上,在Type输入域中选择Maven Project,就能发现这些输入域的用途所在。如果你更喜欢Gradle,也可以选择Gradle Project。

image-20211022213733109

图A.7 在IntelliJ IDEA中指明必要的项目信息

在填写完必要的项目信息后,点击Next按钮将会展现项目依赖页(见图A.8)。

image-20211022213747989

图A.8 选择项目依赖

在最左侧,依赖是按照分类来组织的。选中某个分类时,这个分类对应的可选项会显示在中间区域。已经选中的依赖将会(按照分类)列在最右侧。

在选择完依赖之后,点击Next按钮,我们将会看到项目向导的最后一页,如图A.9所示。在这个页面中,我们要输入项目的名称并指定项目要放到磁盘的什么位置。

image-20211022213758124

图A.9 设置项目的名称和位置

点击Finish按钮,项目将会创建并加载到IntelliJ IDEA的工作空间中。

要使用Spring Tool Suite来初始化新的Spring项目,我们需要在File > New菜单中选择Spring Starter Project菜单项,如图A.1所示。

image-20211022213446109

图A.1 在Spring Tool Suite中初始化一个新项目

注意:这是一个使用Spring Tool Suite初始化Spring项目的简单描述。更详细的阐述,可以参考1.2.1小节。

接下来,我们将会看到项目创建对话框的第一页(见图A.2)。在这个页面中,我们将会定义项目的基本信息,比如项目名称、坐标(group ID和artifact ID)、版本和基础包名。我们也可以确定项目使用Maven还是Gradle来构建,还可以声明构建生成JAR文件还是WAR文件,以及使用哪个版本的Java,甚至还能使用其他的JVM语言,比如Groovy或Kotlin。

image-20211022213458149

图A.2 定义基本的项目信息

这个页面的第一个输入域要求我们指定Spring Initializr服务的位置。如果你运行或使用自定义的Initializr实例,那么可以在这里指定Initializr服务的基础URL;否则,使用默认的http://start.spring.io就可以了。

在定义完项目的基本信息之后,点击Next按钮,会看到项目的依赖页(见图A.3)。

image-20211022213509034

图A.3 指定项目的依赖

在项目依赖页中,我们可以指定项目需要的所有依赖。其中,有些依赖是SpringBoot Starter依赖,有些依赖是Spring项目常用的依赖。

可用的依赖都列在左侧,以分组的形式进行组织,可以展开和收缩。如果在查找依赖时遇到麻烦,还可以对依赖进行搜索,以便于缩小可选范围。

要将某个依赖添加到所生成的项目中,我们只需要选中依赖名称前面的复选框即可。已经选中的依赖会显示在右侧Selected标题下面。如果想要移除依赖,可以点击已选中依赖前面的×,也可以点击Clear Selection按钮清除所有已选的依赖。

为了增加便利性,如果你发现在项目中特定的一组依赖始终(或者经常)会用到,那么你可以在选择完这些依赖后点击Make Default按钮,这样在下一次创建项目的时候它们会预先选中。

在选择完之后,点击Finish按钮就可以生成项目并添加到工作空间中了。

如果你想要使用http://start.spring.io之外的其他Initializr,可以点击Next按钮来设置Initializr的基础URL,如图A.4所示。

image-20211022213533643

图A.4 指定Initializr的基础URL

Base Url输入域指定了Initializr API监听的URL。在这个页面中,这是唯一可以修改的输入域。Full Url输入域展现了通过Initializr请求新项目的完整URL地址。

附录 初始化Spring应用

有很多种方式都可以初始化Spring项目,至于选择哪一种完全取决于个人喜好。其中,很多方案是由我们喜欢哪款IDE决定的。

我们在这里所讨论的可选方案除了一种之外,其他的都是基于Spring Initializr的,Spring Initializr是一个能够为我们生成Spring Boot项目的REST API。各种IDE只不过是REST API的客户端。另外,还有几种方式可以在IDE之外使用SpringInitializr API。

在本附录中,我们将会快速看一下所有的可选方案。

19.6 小结

  • Spring应用可以部署到多种不同的环境中,包括传统的应用服务器、像Cloud Foundry这样的平台即服务环境,或者Docker容器。
  • 在构建WAR文件的时候,我们应当包含一个SpringBootServletInitializr的子类,确保Spring的DispatcherServlet恰当地进行了配置。
  • 构建可运行的JAR文件允许将Spring Boot应用部署到多个云平台上,而且能够避免WAR文件带来的开销。
  • 借助Spotify的Maven Dockerfile插件,能够非常容易地容器化Spring应用。它会在Docker容器中包装一个可执行的JAR文件,容器可以部署到任何支持Docker的环境中,其中包括像Amazon Web Services、MicrosoftAzure、Google Cloud Platform、Pivotal Web Services (PWS)、Pivotal Container Service (PKS)这样的云供应商。

19.5 以终为始

在过去的几百页中,我们从一个简单的起点开始(更具体来讲,也就是start.spring.io),最终将应用部署到了云中。我希望在这几百页中,你所能获取到的乐趣与我在编写本书的过程中是一样的。

虽然本书必须要结束了,但是你的Spring征程才刚刚开始。利用本书所学的知识,用Spring构建令人赞叹的应用吧!我迫不及待地想知道你们的成就。

19.4 在Docker容器中运行Spring Boot

在分发云中部署的各种应用时,Docker已经成为事实标准。很多云环境都接受以Docker容器的形式部署应用,包括AWS、Microsoft Azure、Google CloudPlatform和Pivotal Web Services(简单举例)。

容器化应用程序(比如使用Docker创建的应用程序)的概念借鉴了现实世界中的联运集装箱。在运输过程中,不管里面的东西是什么,所有的联运集装箱都有一个标准的尺寸和格式。正因为如此,联运集装箱才能够很容易地堆放在船上、火车上或卡车上。按照类似的方式,容器化的应用程序遵循通用的容器格式,可以在任何地方部署和运行,而不必关心里面的应用是什么。

尽管创建Docker镜像并不困难,但是Spotify提供了一个Maven插件,借助它我们可以轻而易举地将Spring Boot的构建结果创建为Docker容器。要使用该Docker插件,需要将其添加到Spring Boot项目pom.xml文件的<build>/<plugins>代码块下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<build>
<plugins>
...
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.3</version>
<configuration>
<repository>
${docker.image.prefix}/${project.artifactId}
</repository>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>

<configuration>代码块下,我们设置了一些属性,用于指导如何创建Docker镜像。<repository>描述了在Docker仓库中该Docker镜像的名称。按照这里的设置,其名称是Maven项目的artifact ID,加上Maven属性docker.image.prefix的值作为前缀。项目的artifact ID是Maven已知的,而前缀属性则需要我们进行设置:

1
2
3
4
<properties>
...
<docker.image.prefix>tacocloud</docker.image.prefix>
</properties>

以Taco Cloud的配料服务来讲,所形成的Docker镜像在Docker仓库的名称为tacocloud/ingredient-service。

<buildArgs>元素下面,我们声明镜像要包含Maven构建所生成的JAR文件,在这里使用Maven属性project.build.finalName来确定target目录下JAR文件的名称。

除了提供给Maven构建文件的信息之外,Docker镜像的所有定义都位于一个名叫Dockerfile的文件中。这个文件指明了新镜像要基于哪个基础镜像、要设置的环境变量、要mount的卷以及最重要的入口点(entry point)。入口点也就是基于该镜像的容器在启动时要执行的命令。对于大多数Spring Boot应用来讲,如下的Dockerfile就是一个很好的起点:

1
2
3
4
5
6
7
8
9
FROM openjdk:8-jdk-alpine
ENV SPRING_PROFILES_ACTIVE docker
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java",\
"-Djava.security.egd=file:/dev/./urandom",\
"-jar",\
"/app.jar"]

我们将Docker文件逐行拆分,将会看到它包含如下内容。

  • FROM指令声明了新镜像要基于哪个基础镜像。新的镜像会扩展基础镜像。在本例中,基础镜像为openjdk:8-jdk- alpine,这是一个基于OpenJDK 8的容器镜像。
  • ENV指令设置了环境变量。我们可以基于激活状态的profile重写一些SpringBoot应用的配置属性,所以在本镜像中,我们将SPRING_PROFILES_ACTIVE环境变量设置为docker,从而确保Spring Boot应用启动时docker是处于激活状态的profile。
  • VOLUME指令在容器中创建了一个mount点。在本例中,它在“/tmp”创建了一个mount点,所以需要的话可以将数据写入“/tmp”目录下。
  • ARG指令声明了一个要在构建期传入的参数。在本例中,它声明了名为JAR_FILE的参数,与Maven插件<buildArgs>代码块中的参数是相同的。
  • COPY指令会将给定路径下的某个文件复制到另外一个路径下。在本例中,它会将Maven插件中声明的JAR文件复制为容器中名为app.jar的文件。
  • ENTRYPOINT描述了容器启动的时候要执行什么操作。它以数组的形式指定了要执行的命令行。在本例中,它使用java命令来运行可执行的app.jar。

我们着重介绍一下ENV指令。在任何包含Spring Boot应用程序的容器镜像中设置SPRING_PROFILES_ACTIVE环境变量通常都是一个好办法。这样的话,我们可以配置一些仅在Docker下运行应用时有效的bean和配置属性。

对于配料服务,我们需要将应用程序链接到运行在单独容器中的Mongo数据库。默认情况下,Spring Data会尝试连接localhost上监听端口27017的Mongo数据库。但是,这种做法只有在本地运行的时候才有效,并不适合容器。因此,我们需要配置spring.data.mongodb.host属性,告诉Spring Data要访问哪个主机上的Mongo。

虽然我们可能还不知道Mongo数据库运行在何处,但是我们可以通过application.yml文件配置Docker特定的配置,让它在docker profile处于激活状态时配置Spring Data连接名为mongo的主机上的Mongo:

---
spring:
  profiles: docker
  data:
    mongodb:
      host: mongo

随后,当我们启动Docker容器的时候,会将mongo主机映射到一个在不同容器中运行的Mongo数据库上。现在,我们先来构建容器镜像。借助Maven包装器,执行package和dockerfile:build goal来构建JAR文件,然后构建Docker镜像:

1
$ mvnw package dockerfile:build

此时,我们可以通过docker images来校验本地镜像仓库中的镜像(为了可读性和适应本书的宽度,这里将CREATED和SIZE列删减掉了):

1
2
3
$ docker images
REPOSITORY TAG IMAGE ID
tacocloud/ingredient-service latest 7e8ed20e768e

在启动容器之前,我们需要启动Mongo数据库的容器。如下的命令显示了运行一个名为tacocloud-mongo的新容器,其中包含Mongo 3.7.9数据库:

1
$ docker run --name tacocloud-mongo -d mongo:3.7.9-xenial

现在,我们终于可以运行配料服务容器了,并链接它到刚刚启动的Mongo容器上:

1
2
3
$ docker run -p 8080:8081 \
--link tacocloud-mongo:mongo \
tacocloud/ingredient-service

这里的docker run命令有多个值得介绍的重要组件。

  • 因为我们配置了容器中的Spring Boot应用运行在8081端口上,所以-p参数可以将内部端口映射到主机的8080端口上。
  • –link参数能够将我们的容器链接到名为tacocloud-mongo的容器上,并为其分配mongo主机名,这样Spring Data就可以使用该主机名连接它了。
  • 最后,我们指定了容器要运行的镜像名称(也就是tacocloud/ingredient-service)。

现在,Docker镜像构建完成并且已经证明可以作为本地容器运行。我们可以更进一步,将镜像推送至Dockerhub或其他Docker镜像仓库。如果你有Dockerhub账号并且已经登录,那么可以使用如下的Maven命令推送镜像:

1
$ mvnw dockerfile:push

这样的话,我们可以将镜像部署到几乎所有支持Docker容器的环境中,包括AWS、Microsoft Azure和Google Cloud Platform。你可以选择任意的环境并按照平台相关的指令部署Docker镜像。