你正在阅读本书,说明你是一位读书人。也许你是一个书虫,博览群书;也许你只读自己需要的东西,拿起本书只是为了知道怎么用Spring开发应用程序。
无论何种情况,你都是一位读书人,是读书人便有心维护一个阅读列表,里面是自己想读或者需要读的书。就算没有白纸黑字的列表,至少在你心里会有这么一个列表。1
1如果你不是一个读书人,就把书换成想看的电影、想去的餐厅,只要合适自己就好。
在本书中,我们会构建一个简单的阅读列表应用程序。在这个程序里,用户可以输入想读的图书信息,查看列表,删除已经读过的书。我们将使用Spring Boot来辅助快速开发,各种繁文缛节越少越好。
开始前,我们需要先初始化一个项目。在第1章里,我们看到了好几种从Spring Initializr开始Spring Boot开发的方法。因为选择哪种方法都行,所以要选个最合适的,着手用Spring Boot开发就好了。
从技术角度来看,我们要用Spring MVC来处理Web请求,用Thymeleaf来定义Web视图,用Spring Data JPA来把阅读列表持久化到数据库里,姑且先用嵌入式的H2数据库。虽然也可以用Groovy,但是我们还是先用Java来开发这个应用程序吧。此外,我们使用Gradle作为构建工具。
无论是用Web界面、Spring Tool Suite还是IntelliJ IDEA,只要用了Initializr,你就要确保勾选了Web、Thymeleaf和JPA这几个复选框。还要记得勾上H2复选框,这样才能在开发应用程序时使用这个内嵌式数据库。
至于项目元信息,就随便你写了。以阅读列表为例,我创建项目时使用了图2-1中的信息。
图 2-1 通过Initializr的Web界面初始化阅读列表应用程序
如果你创建项目时用的是Spring Tool Suite或者IntelliJ IDEA,那么把图2-1的内容适配成IDE需要的东西就好了。
另一方面,如果用Spring Boot CLI来初始化应用程序,可以在命令行里键入以下内容:
$ spring init -dweb,data-jpa,h2,thymeleaf --build gradle readinglist
请记住,CLI的init
命令是不能指定项目根包名和项目名的。包名默认是demo,项目名默认是Demo。在项目创建完毕之后,你可以打开项目,把包名demo改为readinglist,把DemoApplication.java改名为ReadingListApplication.java。
项目创建完毕后,你应该能看到一个类似图2-2的项目结构。
图 2-2 初始化后的readinglist项目结构
这个项目结构基本上和第1章里Initializr生成的结构是一样的,只不过你现在真的要去开发应用程序了,所以让我们先放慢脚步,仔细看看初始化的项目里都有什么东西。
2.1.1 查看初始化的Spring Boot新项目
图2-2中值得注意的第一件事是,整个项目结构遵循传统Maven或Gradle项目的布局,即主要应用程序代码位于src/main/java目录里,资源都在src/main/resources目录里,测试代码则在src/test/java目录里。此刻还没有测试资源,但如果有的话,要放在src/test/resources里。
再进一步,你会看到项目里还有不少文件。
build.gradle:Gradle构建说明文件。
ReadingListApplication.java
:应用程序的启动引导类(bootstrap class),也是主要的Spring配置类。application.properties
:用于配置应用程序和Spring Boot的属性。ReadingListApplicationTests.java
:一个基本的集成测试类。
因为构建说明文件里有很多Spring Boot的优点尚未揭秘,所以我打算把最好的留到最后,先让我们来看看ReadingListApplication.java
。
1. 启动引导Spring
ReadingListApplication
在Spring Boot应用程序里有两个作用:配置和启动引导。首先,这是主要的Spring配置类。虽然Spring Boot的自动配置免除了很多Spring配置,但你还需要进行少量配置来启用自动配置。正如代码清单2-1所示,这里只有一行配置代码。
代码清单2-1
ReadingListApplication.java
不仅是启动引导类,还是配置类
package readinglist;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication ←---开启组件扫描和自动配置public class ReadingListApplication { public static void main(String args) { SpringApplication.run(ReadingListApplication.class, args); ←---负责启动引导应用程序 }}
@SpringBootApplication
开启了Spring的组件扫描和Spring Boot的自动配置功能。实际上,@SpringBootApplication
将三个有用的注解组合在了一起。
Spring的
@Configuration
:标明该类使用Spring基于Java的配置。虽然本书不会写太多配置,但我们会更倾向于使用基于Java而不是XML的配置。Spring的
@ComponentScan
:启用组件扫描,这样你写的Web控制器类和其他组件才能被自动发现并注册为Spring应用程序上下文里的Bean。本章稍后会写一个简单的Spring MVC控制器,使用@Controller
进行注解,这样组件扫描才能找到它。Spring Boot的
@EnableAutoConfiguration
:这个不起眼的小注解也可以称为@Abracadabra
2,就是这一行配置开启了Spring Boot自动配置的魔力,让你不用再写成篇的配置了。
2abracadabra的意思是咒语。——译者注
在Spring Boot的早期版本中,你需要在ReadingListApplication
类上同时标上这三个注解,但从Spring Boot 1.2.0开始,有@SpringBootApplication
就行了。
如我所说,ReadingListApplication
还是一个启动引导类。要运行Spring Boot应用程序有几种方式,其中包含传统的WAR文件部署。但这里的main
方法让你可以在命令行里把该应用程序当作一个可执行JAR文件来运行。这里向SpringApplication.run
传递了一个ReadingListApplication
类的引用,还有命令行参数,通过这些东西启动应用程序。
实际上,就算一行代码也没写,此时你仍然可以构建应用程序尝尝鲜。要构建并运行应用程序,最简单的方法就是用Gradle的bootRun
任务:
$ gradle bootRun
bootRun
任务来自Spring Boot的Gradle插件,我们会在2.1.2节里详细讨论。此外,你也可以用Gradle构建项目,然后在命令行里用java
来运行它:
$ gradle build...$ java -jar build/libs/readinglist-0.0.1-SNAPSHOT.jar
应用程序应该能正常运行,启动一个监听8080端口的Tomcat服务器。要是愿意,你可以用浏览器访问http://localhost:8080,但由于还没写控制器类,你只会收到一个HTTP 404(NOT FOUND)错误,看到错误页面。在本章结束前,这个URL将会提供一个阅读列表应用程序。
你几乎不需要修改ReadingListApplication.java
。如果你的应用程序需要Spring Boot自动配置以外的其他Spring配置,一般来说,最好把它写到一个单独的@Configuration
标注的类里。(组件扫描会发现并使用这些类的。)极度简单的情况下,可以把自定义配置加入ReadingListApplication.java
。
2. 测试Spring Boot应用程序
Initializr还提供了一个测试类的骨架,可以基于它为你的应用程序编写测试。但ReadingListApplicationTests
(代码清单2-2)不止是个用于测试的占位符,它还是一个例子,告诉你如何为Spring Boot应用程序编写测试。
代码清单2-2
@SpringApplicationConfiguration
加载Spring应用程序上下文
package readinglist;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.SpringApplicationConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import org.springframework.test.context.web.WebAppConfiguration;import readinglist.ReadingListApplication;@RunWith(SpringJUnit4ClassRunner.class)@SpringApplicationConfiguration( classes = ReadingListApplication.class) ←---通过Spring Boot加载上下文@WebAppConfigurationpublic class ReadingListApplicationTests { @Test public void contextLoads { ←---测试加载的上下文 }}
一个典型的Spring集成测试会用@ContextConfiguration
注解标识如何加载Spring的应用程序上下文。但是,为了充分发挥Spring Boot的魔力,这里应该用@SpringApplicationConfiguration
注解。正如你在代码清单2-2里看到的那样,ReadingListApplicationTests
使用@SpringApplicationConfiguration
注解从ReadingListApplication
配置类里加载Spring应用程序上下文。
ReadingListApplicationTests
里还有一个简单的测试方法,即contextLoads
。实际上它就是个空方法。但这个空方法足以证明应用程序上下文的加载没有问题。如果ReadingListApplication
里定义的配置是好的,就能通过测试。如果有问题,测试就会失败。
当然,现在这只是一个新的应用程序,你还会添加自己的测试。但contextLoads
方法是个良好的开端,此刻可以验证应用程序提供的各种功能。第4章会更详细地讨论如何测试Spring Boot应用程序。
3. 配置应用程序属性
Initializr为你生成的application.properties文件是一个空文件。实际上,这个文件完全是可选的,你大可以删掉它,这不会对应用程序有任何影响,但留着也没什么问题。
稍后,我们肯定有机会向application.properties里添加几个条目。但现在,如果你想小试牛刀,可以加一行看看:
server.port=8000
加上这一行,嵌入式Tomcat的监听端口就变成了8000,而不是默认的8080。你可以重新运行应用程序,看看是不是这样。
这说明application.properties文件可以很方便地帮你细粒度地调整Spring Boot的自动配置。你还可以用它来指定应用程序代码所需的配置项。在第3章里我们会看到好几个例子,演示application.properties
的这两种用法。
要注意的是,你完全不用告诉Spring Boot为你加载application.properties
,只要它存在就会被加载,Spring和应用程序代码都能获取其中的属性。
我们差不多已经把初始化的项目介绍完了,还剩最后一样东西,让我们来看看Spring Boot应用程序是如何构建的。
2.1.2 Spring Boot项目构建过程解析
Spring Boot应用程序的大部分内容都与其他Spring应用程序没有什么区别,与其他Java应用程序也没什么两样,因此构建一个Spring Boot应用程序和构建其他Java应用程序的过程类似。你可以选择Gradle或Maven作为构建工具,描述构建说明文件的方法和描述非Spring Boot应用程序的方法相似。但是,Spring Boot在构建过程中耍了些小把戏,在此需要做个小小的说明。
Spring Boot为Gradle和Maven提供了构建插件,以便辅助构建Spring Boot项目。代码清单2-3是Initializr创建的build.gradle文件,其中应用了Spring Boot的Gradle插件。
代码清单2-3 使用Spring Boot的Gradle插件
buildscript { ext { springBootVersion = `1.3.0.RELEASE` } repositories { mavenCentral } dependencies { classpath(/"org.springframework.boot:spring-boot-gradle-plugin: ${springBootVersion}/") ←---依赖Spring Boot插件 }}apply plugin: /'java/'apply plugin: /'eclipse/'apply plugin: /'idea/'apply plugin: /'spring-boot/' ←---运用Spring Boot插件jar { baseName = /'readinglist/' version = /'0.0.1-SNAPSHOT/'}sourceCompatibility = 1.7targetCompatibility = 1.7repositories { mavenCentral}dependencies { compile(/"org.springframework.boot:spring-boot-starter-web/") ←---起步依赖 compile(/"org.springframework.boot:spring-boot-starter-data-jpa/") compile(/"org.springframework.boot:spring-boot-starter-thymeleaf/") runtime(/"com.h2database:h2/") testCompile(/"org.springframework.boot:spring-boot-starter-test/")}eclipse { classpath { containers.remove(/'org.eclipse.jdt.launching.JRE_CONTAINER/') containers /'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7/' }}task wrapper(type: Wrapper) { gradleVersion = /'1.12/'}
另一方面,要是选择用Maven来构建应用程序,Initializr会替你生成一个pom.xml文件,其中使用了Spring Boot的Maven插件,如代码清单2-4所示。
代码清单2-4 使用Spring Boot的Maven插件及父起步依赖
<?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>com.manning</groupId> <artifactId>readinglist</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>ReadingList</name> <description>Reading List Demo</description> <parent> ←---从spring-boot-starterparent继承版本号 <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>{springBootVersion}</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> ←---起步依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <properties> <project.build.sourceEncoding> UTF-8 </project.build.sourceEncoding> <start-class>readinglist.Application</start-class> <java.version>1.7</java.version> </properties> <build> <plugins> <plugin> ←---运用Spring Boot插件 <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
无论你选择Gradle还是Maven,Spring Boot的构建插件都对构建过程有所帮助。你已经看到过如何用Gradle的bootRun
任务来运行应用程序了。Spring Boot的Maven插件与之类似,提供了一个spring-boot:run
目标,如果你使用Maven,它能实现相同的功能。
构建插件的主要功能是把项目打包成一个可执行的超级JAR(uber-JAR),包括把应用程序的所有依赖打入JAR文件内,并为JAR添加一个描述文件,其中的内容能让你用java -jar
来运行应用程序。
除了构建插件,代码清单2-4里的Maven构建说明中还将spring-boot-starter-parent作为上一级,这样一来就能利用Maven的依赖管理功能,继承很多常用库的依赖版本,在你声明依赖时就不用再去指定版本号了。请注意,这个pom.xml里的<dependency>
都没有指定版本。
遗憾的是,Gradle并没有Maven这样的依赖管理功能,为此Spring Boot Gradle插件提供了第三个特性,它为很多常用的Spring及其相关依赖模拟了依赖管理功能。其结果就是,代码清单2-3的build.gradle里也没有为各项依赖指定版本。
说起依赖,无论哪个构建说明文件,都只有五个依赖,除了你手工添加的H2之外,其他的Artifact ID都有spring-boot-starter-
前缀。这些都是Spring Boot起步依赖,它们都有助于Spring Boot应用程序的构建。让我们来看看它们究竟都有哪些好处。