文 by / 林本托
Tip 做一个终身学习的人。
Spring Boot 包含了很多 start(Spring boot 中 的叫法,就是一个模块,后文统一称模块,便于理解),这些模块其实早就是 Spring Boot 大家庭的成员。这章主要介绍 http://start.spring.io/,Spring Boot 提供的可用的组建,通过这个链接我们可以快速搭建一个项目。
这章主要包括以下内容:
在当今软件开发快节奏的世界中,应用程序创建的速度和快速原型的需求正在变得越来越重要。 如果您正在使用 JVM 语言开发软件,那么 Spring Boot 就是一种能够为您提供灵活性的框架,从而使您能够以快速的速度生产高质量的软件。 所以,让我们来看看 Spring Boot 如何帮助你实现你的应用程序。
Spring Boot 提供了超过 40 种不同的模块,它们为许多不同的框架提供即用型集成库,例如关系型和 NoSQL 的数据库连接,Web 服务,社交网络集成,监控库,日志记录,模板渲染, 而且这个名单一直在扩大。 虽然覆盖这些组件中的每一个功能不是实际可行的,但是我们将会重点介绍一些重要和受欢迎的组件,以便了解 Spring Boot 为我们提供的可能性和易用性。
我们将从创建一个基本的简单项目框架开始,Spring Boot 将帮助我们:
网站的截图如下:
在上面截图中,你会看到 "Project Dependencies" 部分,如果你的项目需要连接数据库,要有 Web 接口,计划要和其他的社交网络进行整合,需要提供运行时运营支持的能力,等等。在这里你可以根据你的项目需要选择不同的功能。通过选择所需的技术,相应的模块将自动添加到我们预先生成的项目模板的依赖列表中。
在我们继续开发项目之前,让我们来看一下 Spring Boot 的模块的功能以及它为我们提供的好处。
Spring Boot 旨在简化应用程序创建入门的过程。 Spring Boot 模块是引导库,其中包含启动特定功能所需的所有相关传递依赖关系的集合。 每个启动器都有一个特定文件,其中包含所有提供的依赖关系的列表—— spring.provides。 我们来看一下 spring-boot-starter-test 定义的链接:spring.provides。
我们看到此文件的内容为:
- provides: spring - test,
- spring - boot,
- junit,
- mockito,
- hamcrest - library
这告诉我们,通过在我们的构建中包含 spring-boot-starter-test 作为依赖,我们将自动获得 spring-test,spring-boot,junit,mockito 和 hamcrest-library。 这些库将为我们提供所有必要的事情,以便开始为我们开发的软件编写应用程序测试,而无需手动将这些依赖关系手动添加到构建文件中。
随着 40 多个模块的提供以及社区的不断增加,我们很可能发现自己需要与一个相当普遍或流行的框架进行整合,所以我们可以使用其中的模块。
下表列举了比较有名的模块,以便了解每个模块的使用:
模块 | 描述 |
---|---|
spring-boot-starter | Spring Boot 核心模块,提供所有的基础功能。 其他模块都要依赖它,所以没有必要明确声明。 |
spring-boot-starter-actuator | 提供了监视,管理应用程序和审核的功能。 |
spring-boot-starter-jdbc | 提供了连接和使用 JDBC 数据库,连接池等的支持。 |
spring-boot-starter-data-jpa | 为使用 Java Persistence API(如 Hibernate 等)提供了必要的类库。 |
spring-boot-starter-data-* | 带有 "data-*" 的集合组件为诸如 MongoDB,Data-Rest 或 Solr 之类的数据存储提供支持。 |
spring-boot-starter-security | 为 Spring-security 提供所有必需的依赖。 |
spring-boot-starter-social-* | 提供了与 Facebook, Twitter, 和 LinkedIn 整合的功能。 |
spring-boot-starter-test | 包含 Spring-test 和各种测试框架(如 JUnit 和 Mockito 等)的依赖。 |
spring-boot-starter-web | 提供了 Web 应用程序开发所需的所有依赖。作为 spring-boot-starter-hateoas, spring-boot-starter-websocket, spring-boot-starter-mobile, 和 spring-boot-starter-ws 的补充。以及各种模板渲染模块 sping-boot-starter-thymeleaf 和 spring-boot-starter-mustache。 |
现在我们去 http://start.spring.io 去创建一个基本的应用。这里需要注意的是,我们需要展开更多选项。如下图:
我们要创建的应用程序是一个图书目录管理系统。 它将保存出版的书籍,作者,评论者,出版社等的记录。 我们将项目命名为 BookPub,具体步骤如下:
我们下载 bookpub.zip 后并解压,会生成 bookpub 目录,在此目录下你会看到 build.gradle 文件来定义项目的构建,它已经预先配置了正确版本的 Spring Boot 插件和库,甚至包括我们选择的额外的模块。
build.gradle 文件里的部分内容如下:
- dependencies {
- compile("org.springframework.boot:spring-boot-starter-data-jpa")
- compile("org.springframework.boot:spring-boot-starter-jdbc")
- runtime("com.h2database:h2")
- testCompile("org.springframework.boot:spring-boot-starter-test")
- }
我们已经选择了如下模块:
在上面的文件中,你会发现,只有一个运行时依赖:runtime("com.h2database:h2")。这是因为我们不需要,甚至不想要知道在编译时我们将连接的数据库的确切类型。 一旦它在启动应用程序时检测到类路径中的 org.h2.Driver 类的存在,Spring Boot 将自动配置所需的设置并创建适当的 bean。
data-jpa 和 jdbc 是 Spring Boot 模块的 artifact。 如果我们在 Gradle 本地下载,或使用 Maven Central 在线文件存储库的时候查看这些依赖项,我们会发现它们不包含任何实际的类,只包含各种元数据。 我们特别感兴趣的两个文件是 Manven 的 pom.xml 和 Gradle 的 spring.provides。 我们先来看一下 spring-boot-starter-jdbc.jar 中的 spring.provides 文件,其中包含以下内容:
- provides: spring - jdbc,
- spring - tx,
- tomcat - jdbc
这告诉我们,通过将这个模块做为我们的依赖关系,我们将在构建中传递地获取 spring-jdbc,spring-tx 和 tomcat-jdbc 依赖库。 pom.xml 文件包含正确的依赖关系声明,将由 Gradle 或 Maven 用来在构建期间解析所需的依赖关系。 这也适用于我们的第二个模块:spring-boot-starter-data-jpa。 这个模块将会向我们提供 spring-orm,hibernate-entity-manager 和 spring-data-jpa 类库。
在这一点上,我们在应用程序类路径中有足够的库 / 类,以便给 Spring Boot 一个想要运行的应用程序的想法,以及 Spring Boot 需要自动配置的工具和框架类型把这些模块拼装在一起。
早些时候,我们提到类路径中的 org.h2.Driver 类,在触发 Spring Boot 时为我们的应用程序自动配置 H2 数据库连接。 要了解其原理,我们首先来看看我们新创建的应用程序模板,位于项目根目录下的 src/main/java/org/test/ bookpub 目录中的 BookPubApplication.java,如下所示:
- package org.test.bookpub;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- @SpringBootApplication
- public class BookPubApplication {
- public static void main(String[] args) {
- SpringApplication.run(BookPubApplication.class, args);
- }
- }
这实际上是我们整个以及完全可运行的应用程序。 这里没有很多的代码,也没有提及任何地方的配置或数据库。 但关键是 @SpringBootApplication 注解。 为了了解实际发生的情况,我们可以看看这个注解代码,这里找到其注解的注解,它们会使 Spring Boot 自动设置一些事情:
- @Configuration
- @EnableAutoConfiguration
- @ComponentScan
- public @interface SpringBootApplication {…}
接下来,让我们看一下上面几个注解的作用:
: 告诉 Spring(不只是 Spring Boot,因为它是一个 Spring 框架核心注释),注解类包含 Spring 配置定义声明,例如
- @Configuration
,
- @Bean
和
- @Component
等。
- @Service
:告诉 Spring,我们要扫描我们的应用程序包 —— 从我们的注解类的包作为默认的根路径开始扫描 - 可以使用
- @ComponentScan
,
- @Configuration
和其他适合的注解,Spring 将自动引入,作为上下文配置的一部分。
- @Controller
:是 Spring Boot 注解的一部分,它是自己的元注解。 它导入
- @EnableAutoConfiguration
和
- EnableAutoConfigurationImportSelector
类,它们有效地指示 Spring 根据类路径中可用的类自动配置条件 bean。
- AutoConfigurationPackages.Registrar
上面代码中的
, 在
- SpringApplication.run(BookPubApplication.class,args);
方法中创建了一个 Spring 应用程序上下文,它读取 BookPubApplication.class 中的注解,并实例化,这与前面已经完成的方法类似,而不是使用 Spring Boot,我们无法摆脱 Spring 框架。
- main
通常情况下,创建任何应用程序的第一步是创建一个基本的骨架,然后可以立即启动。 由于 Spring Boot 模块已经为我们创建了应用程序模板,所以我们所要做的就是提取代码,构建和执行它。 现在让我们去命令行控制台,并用 Gradle 启动应用程序。因为我的操作系统是 macOS,所以我使用 Terminal 控制台来做。
首先,我们在命令行控制台中进入我们已经解压好的 bookpub.zip 的目录下,然后执行下面的命令:
- ./gradlew clean bootRun
下载完的状态是这样的,中间要等上一会儿。
正如我们所看到的,应用程序启动正常,但由于我们没有添加任何功能或配置任何服务,它便立即终止了。无论如何,从启动日志总可以看到,自动配置确实发生了。让我们来看看下面的内容:
- Building JPA container EntityManagerFactory
- for persistence unit 'default'HHH000412: Hibernate Core {
- 4.3.8.Final
- }
- HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
以上信息说明,因为我们增加了 jdbc 和 data-jpa 的模块,JPA 容器被创建并使用 h2dialect 方式管理持久层 Hibernate 4.3.8.final 版本。这也是因为我们在 classpath 中配置了正确的类。
随着我们的基本应用骨架准备好了,让我们添加功能,使我们的应用程序做一些事情。
首先我们创建一个类,类名为
,它实现
- StartupRunner
接口,这个接口中只提供了一个方法
- CommandLineRunner
,这个方法将在应用程序启动以后被 Spring Boot 调用一次。
- public void run(String… args)
我们在 bookpub 目录的 src/main/java/org/test/bookpub/ 路径下,创建
,具体代码为:
- StartupRunner.java
- package org.test.bookpub;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.springframework.boot.CommandLineRunner;
- public class StartupRunner implements CommandLineRunner {
- protected final Log logger = LogFactory.getLog(getClass());
- @Override
- public void run(String... args) throws Exception {
- logger.info("Hello");
- }
- }
接下来在
文件中,把上面的类标记 @Bean 注解用来注入,具体如下:
- BookPubApplication.java
- package org.test.bookpub;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.context.annotation.Bean;
- @SpringBootApplication
- public class BookPubApplication {
- public static void main(String[] args) {
- SpringApplication.run(BookPubApplication.class, args);
- }
- @Bean
- public StartupRunner schedulerRunner() {
- return new StartupRunner();
- }
- }
接着,在命令行中执行
,
- ./gradlew clean bootRun
在启动过程中的日志里输出了 "Hello" 字符串。
即使程序将被终止执行,至少我们让它做一些事!
命令行的运行是一个有用的功能,用来执行各种类型的代码,只需要运行一次后,应用程序启动。有些人也可以使用这个作为一种启动各种执行器线程的方式,但 Spring 启动提供了一个更好方式解决这个任务。
接口由 Spring Boot 启动以后扫描改接口所有的实现,调用的每个实例的带有启动参数的
- CommandLineRunner
方法。我们也可以使用 @Order 注解或实现
- run
接口,以便定义我们想要 Spring Boot 来执行它们的确切顺序。例如,Spring 批处理依赖 runner 类以便触发 job 的执行。
- Ordered
当命令行运行器在应用程序启动后实例化并执行时,我们可以使用依赖注的优势来绑定我们所需要的依赖(例如数据源、服务和其他组件)。当在实现
方法后来使用。
- run(String... args)
Tips 需要注意的是,如果在
方法内有异常抛出,这将导致上下文和应用程序的关闭。为了避免这种情况发生,建议用
- run(String… args)
包装有风险的代码块。
- try/catch
在每个应用程序中,需要访问一些数据并对其进行一些操作。最常见的,这个数据源是某种数据存储,即数据库。Spring Boot 采取了非常简单容易的方式,以便连接到数据库,并使用 JPA 来访问和操作数据。
在前面的示例中,我们创建了基本应用程序,在命令行中启动应用并在日志中打印一条消息。接下来,我们增强这个应用,给他添加数据库连接的功能。
此前,我们已经添加必要的 jdbc 和 data-jpa 模块,以及 H2 数据库的依赖构建文件。现在,我们将配置 H2 数据库的内存实例。
Tips 当使用嵌入式数据库时,如 H2,HSQL,或者 Derby,没有真正必需的配置,此外包括在构建文件中的依赖关系。当这些数据库在类路径中检测到
这个 bean 的依赖在代码里声明时,Spring Boot 会自动给你创建一个。
- DataSource
为了演示这一情况,现在只包括在类路径中的 H2 的依赖,我们将自动获得一个默认数据库,下面修改我们之前的
文件:
- StartupRunner.java
- package org.test.bookpub;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.springframework.boot.CommandLineRunner;
- public class StartupRunner implements CommandLineRunner {
- protected final Log logger = LogFactory.getLog(getClass());
- @Autowired
- private DataSource ds;
- @Override
- public void run(String... args) throws Exception {
- logger.info("DataSource: " + ds.toString());
- }
- }
现在,我们继续应用程序的运行,我们在日志里看到数据源的名称,如下:
所以,在框架引擎下,Spring 会意识到自动装配数据源的依赖并自动创建一个初始化内存 H2 数据库。这一切看起来还不错,但只是在早期原型阶段或测试目的,其他场景并不是很有用。一旦应用程序关闭,内存数据库的数据将会全部消失,不会保留。
那如何才能持久保留数据呢?可以更改默认值,以创建一个嵌入式 H2 数据库,它不会将数据存储在内存中,而是使用一个文件来在应用程序重启之间保持数据。
在 src/main/resources 目录下打开 application.properties 文件,添加以下内容:
- spring.datasource.url = jdbc:h2:~/test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
- spring.datasource.username = sa
- spring.datasource.password =
接下来执行
。执行以后,就会在你的系统主目录下生成 test.mv.db 文件。
- ./gradlew clean bootRun
Tips 如何你使用的是 Linux 系统,test.mv.db 会生成在
下。 如果是 macOS,则在
- /home/<username>
下。
- /Users/<username>
默认情况下,Spring Boot 通过检查类路径支持的数据库驱动程序的存在使得对数据库配置进行一定的假设,通过配置文件中的
属性组,从而提供了非常容易的配置选项来调整数据库的访问。
- spring.datasource.*
我们可以配置 url, username, password, driver-class-name 等选项。如果你想使用 JNDI 方式访问数据源,创建应用程序之外的数据源实例,例如通过一个容器,如 JBoss、Tomcat、和通过 JNDI 共享,可以配置 spring.datasource.jndiname。
Tips 在配置文件的属性名字中,例如,driver-class-name,和 driverClassName,两者都是支持的,Spring Boot 会把它们转换成同一种方式。
如果你想连接到一个常规(非嵌入式)数据库,除了在类路径中添加适当的驱动程序库,我们需要指定的配置中选择驱动程序。下面的片段是 MySQL 的配置信息:
- spring.datasource.driver-class-name: com.mysql.jdbc.Driver
- spring.datasource.url: jdbc:mysql://localhost:3306/springbootcookbook
- spring.datasource.username: root
- spring.datasource.password:
如果我们希望 Hibernate 基于我们的实体类,自动创建 schema,需要添加下面的配置属性:
- spring.jpa.hibernate.ddl-auto=create-drop
Tips 在上面的配置属性中,不要在生产环境中使用,否则在启动时,所有的表模式和数据都会被删除!而是根据需要,使用 update 或 validate 属性值。
你可以在应用程序的抽象层再进一步,不再自动装配 DataSource 对象,而是直接用 jdbcTemplate。这将指示 Spring Boot 自动创建一个数据源,然后创建一个 JdbcTemplate 对象包装数据源,从而为您提供更方便的方式与数据库安全的交互。JdbcTemplate 的代码如下:
- @Autowired
- private JdbcTemplate jdbcTemplate;
如果你对此处保持好奇的心态,可以查看 spring-boot-autoconfigure 模块下的
类,就会豁然开朗。
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
连接到数据库,然后执行良好的 SQL 语句,这是简单和直接的方式,而不是最方便的方式操作数据,而更好的方式是映射在一组领域对象,并操纵关系的内容。这就是为什么出现了很多框架实现了将数据从表到对象的映射,也就是大家常说的 ORM(Object Relational Mapping)。其中一个最有名的框架就是 Hibernate。
在前面的例子中,我们介绍了如何建立一个连接到数据库和配置设置的用户名,密码,使用哪个驱动程序,等等。我们将增强应用程序,根据数据库中数据结构的定义,添加对应的实体对象, 使用
接口访问数据。
- crudrepository
根据我们应用程序的应用场景,是一个图书查找分类的系统,所以会包括 Book,,Author,,Reviewers,和 Publisher 这些实体对象。
接下来,在 src/main/java/org/test/bookpub 目录下,创建 entity 包;
在 entity 包下,创建
文件,代码如下:
- Book.java
- package org.test.bookpub.entity;
- import javax.persistence.*;
- import java.util.List;
- @Entity
- public class Book {
- @Id
- @GeneratedValue
- private Long id;
- private String isbn;
- private String title;
- private String description;
- @ManyToOne
- private Author author;
- @ManyToOne
- private Publisher publisher;
- @ManyToMany
- private List<Reviewer> reviewers;
- protected Book() {}
- public Book(String isbn, String title, Author author, Publisher publisher) {
- this.isbn = isbn;
- this.title = title;
- this.author = author;
- this.publisher = publisher;
- }
- // 省略属性的 getter 和 setter 方法
- }
任何一本书都会有一个作者和出版社,还会有很多评论者,所以, 我们也要创建这些对应的实体对象,在
同目录下,创建
- Book.java
。
- Author.java
- @Entity
- public class Author {
- @Id
- @GeneratedValue
- private Long id;
- private String firstName;
- private String lastName;
- @OneToMany(mappedBy = "author")
- private List<Book> books;
- protected Author() {}
- public Author(String firstName, String lastName) {...}
- // 省略购房方法属性赋值
- }
- // 省略 属性 getter 和 setter 方法
同样,创建
和
- Publisher.java
文件。
- Reviewer.java
- @Entity
- public class Publisher {
- @Id
- @GeneratedValue
- private Long id;
- private String name;
- @OneToMany(mappedBy = "publisher")
- private List<Book> books;
- protected Publisher() {}
- public Publisher(String name) {...}
- }
- @Entity
- public class Reviewer {
- @Id
- @GeneratedValue
- private Long id;
- private String firstName;
- private String lastName;
- protected Reviewer() {}
- public Reviewer(String firstName, String lastName) {
- ...
- }
- }
下一步,我们在 src/main/java/org/test/bookpub/repository 目录下创建
,并继承 Spring 的
- BookRepository.java
父类,
- CrudRepository
- package org.test.bookpub.repository;
- import org.springframework.data.repository.CrudRepository;
- import org.springframework.stereotype.Repository;
- import org.test.bookpub.entity.Book;
- @Repository
- public interface BookRepository extends CrudRepository<Book, Long> {
- public Book findBookByIsbn(String isbn);
- }
最后,修改
文件,用来打印图书的数量,通过自动装配 BookRepository 接口的实例,并调用
- StartupRunner.java
方法。
- .count()
- package org.test.bookpub;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.CommandLineRunner;
- import org.springframework.scheduling.annotation.Scheduled;
- import org.test.bookpub.repository.BookRepository;
- public class StartupRunner implements CommandLineRunner {
- protected final Log logger = LogFactory.getLog(getClass());
- @Autowired private BookRepository bookRepository;
- @Override
- public void run(String... args) throws Exception {
- logger.info("Welcome to the Book Catalog System!");
- }
- public void run() {
- logger.info("Number of books: " + bookRepository.count());
- }
- }
您可能已经注意到,我们没有写一行 SQL,甚至没有提到任何关于数据库连接,构建查询或类似的事情。 我们处理数据库支持的数据的唯一提示是我们的代码中的类和属性注解:@Entity,@Repository,@Id,@GeneratedValue 和 @ManyToOne 以及 @ ManyToMany 和 @OneToMany。 这些注解是 Java Persistance API 的一部分,以及 CrudRepository 接口的扩展,我们与 Spring 通信的方式是将我们的对象映射到数据库中相应的表和字段,并向我们提供编程与这些数据交互的能力。
我们来看一下具体注解的使用:
,表示 Spring JPA 应该自动将对该方法的调用转换为通过其 ISBN 字段选择 Book 的 SQL 查询。 这是一个约定命名的映射,将方法名称转换为 SQL 查询。 这是一个非常强大的功能,允许构建查询,如 `findByNameIgnoringCase(String name) 等其他的方法。
- public Book findBookByIsbn(String isbn)
属性定义了反向关联映射。 它表示 Hibernate 的真实的映射源在 Book 类中,在 Author 或 Reviewer 字段中定义。Author 和 Reviewer 类中的 Book 引用仅仅是反向关联。
- mappedBy
Tips 有关 Spring Data 的所有功能的更多信息,请访问 http://docs.spring.io/spring-data/data-commons/docs/current/reference/html/。
在本章之前,我们讨论了如何使用命令行运行程序作为启动计划的执行程序线程池的方式,用来间隔运行工作线程。 虽然这是一个可能性,但 Spring 提供了更简洁的配置来实现相同的目的:@EnableScheduling 注解。
我们将加强我们的应用程序,以便它每 10 秒在我们的存储库中打印一些图书数量。 为了实现这一点,我们将对
和
- BookPubApplication
类进行必要的修改。
- StartupRunner
首先,我们需要在
类上添加
- BookPubApplication
注解,
- @EnableScheduling
- @SpringBootApplication
- @EnableScheduling
- public class BookPubApplication {…}
由于 @Scheduled 注解只能放置在没有参数的方法上,所以我们将一个新的
方法添加到 StartupRunne r 类中,并使用 @Scheduled 注解,如下所示:
- run()
- package org.test.bookpub;
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.CommandLineRunner;
- import org.springframework.scheduling.annotation.Scheduled;
- import org.test.bookpub.repository.BookRepository;
- public class StartupRunner implements CommandLineRunner {
- protected final Log logger = LogFactory.getLog(getClass());
- @Autowired private BookRepository bookRepository;
- @Override
- public void run(String... args) throws Exception {
- logger.info("Welcome to the Book Catalog System!");
- }
- @Scheduled(initialDelay = 1000, fixedRate = 10000)
- public void run() {
- logger.info("Number of books: " + bookRepository.count());
- }
- }
接下来,在命令行中执行
,在日志中就会每间隔 10 秒打印出 "Number of books: 0" 的消息。
- ./gradlew clean bootRun
像我们在本章中讨论的一些其他注解一样,@EnableScheduling 不是 Spring Boot 里的注解,而是一个 Spring Context 模块里的注解。 类似于 @SpringBootApplication 和 @EnableAutoConfiguration 注解,它们都是元注释,并通过 @Import(SchedulingConfiguration.class) 指令在内部导入 SchedulingConfiguration,如果在 @EnableScheduling 注解类的代码中查找,可以看到它。
将由导入的配置创建的
类扫描已声明的 Spring Bean 的 @Scheduled 注解。 对于每个没有参数的注释方法,将会创建适当的执行程序线程池。 它将管理已添加注解方法的计划调用。
- ScheduledAnnotationBeanPostProcessor
来源: http://www.cnblogs.com/IcanFixIt/p/6890308.html