SpringBoot2 高级教程(一)
一、Spring 框架 5
欢迎阅读本书的第一章,在这一章中,我将向您介绍 Spring 框架,一点历史,以及它自诞生以来是如何发展的。本章适用于不熟悉 Spring 框架的开发人员。如果你是一个有经验的 Spring 框架开发者,你可以跳过这一章。
也许你在想,“我想学 Spring Boot。为什么我需要了解 Spring Framework?”嗯,让我告诉你,Spring Boot 的 Spring 是。Spring Boot 有不同的机制来运行 Spring 应用;要理解 Spring Boot 真正是如何工作的,有必要了解更多关于 Spring 框架的知识。
一点历史
Spring 框架是 Rod Johnson 在 2003 年创建的,他是《没有 EJB 的 J2EE 发展》一书的作者。Spring 框架是对当时 J2EE 规范的所有复杂性的回应。今天,它已经得到改善,但你需要有一个完整的基础设施来运行 J2EE 生态系统的某些方面。
我们可以说 Spring 是 Java EE 的补充技术。Spring 框架集成了几种技术,如 Servlet API、WebSocket API、并发实用程序、JSON 绑定 API、bean 验证、JPA、JMS 和 JTA/JCA。
Spring 框架支持依赖注入和公共注释规范,使得开发更加容易。
本章说明了 Spring Framework 版本 5.x 至少需要 Java EE 7 级别(Servlet 3.1+和 JPA 2.1)。Spring 仍然支持 Tomcat 8 和 9、WebSphere 8 和 JBoss EAP 7。此外,我还向您展示了 Spring Framework 5 的新特性——反应式支持!
如今,Spring 是 Java 社区中使用最多、最受认可的框架之一,不仅因为它能够工作,还因为它继续与其他令人惊叹的项目一起创新,包括 Spring Boot、Spring Security、Spring Data、Spring Cloud、Spring Batch 和 Spring Integration 等等。
设计原则和模式
要了解 Spring Boot,你需要了解一个框架;重要的是不仅要知道它做什么,还要知道它遵循哪些原则。以下是 Spring 框架的一些原则。
-
在每个级别提供选择。Spring 允许您尽可能推迟设计决策。例如,您可以通过配置切换持久性提供者,而无需更改代码。许多其他基础设施问题以及与第三方 API 的集成也是如此。您将会看到,这甚至会在您将应用部署到云中时发生。
-
容纳不同的观点。Spring 拥抱灵活性,对事情应该如何做并不固执己见。它从不同的角度支持广泛的应用需求。
-
保持强大的向后兼容性。Spring 的发展已经被小心地管理,使得版本之间几乎没有突破性的变化。Spring 支持精心选择的一系列 JDK 版本和第三方库,以便于维护依赖于 Spring 的应用和库。
-
关心 API 设计。Spring 团队投入了大量的思想和时间来制作直观的、跨多个版本和多年的 API。
-
为代码质量设定高标准。Spring 框架非常强调有意义的、当前的和准确的 Javadocs。这是极少数可以宣称代码结构清晰,包之间没有循环依赖的项目之一。
那么,运行 Spring 应用需要什么呢?Spring 使用普通的旧 Java 对象(POJOs ),使其易于扩展。Spring 是非侵入性的,使您的应用企业就绪;但是你需要通过添加一个配置来帮助 Spring 连接所有的依赖项,并注入创建 Springbean来执行你的应用所需要的东西(见图 1-1 )。
图 1-1
Spring 语境
图 1-1 显示了创建所有 Spring beans 的 Spring 上下文——感谢引用您的类的配置,这使得您的应用运行。您将在接下来的小节中找到更多信息,在这些小节中,您将创建一个完整的 REST API 应用。
Spring 框架 5
Spring 使得创建 Java 企业应用变得容易,因为它提供了开发人员在企业环境中使用 Java 语言所需的一切。它在 JVM (Java 虚拟机)上提供了对 Groovy 和 Kotlin 作为替代语言的出色支持。
Spring Framework 5 需要 JDK 8+版本,并为 Java 开发工具包(JDK) 9、10 和 11 提供现成的支持。Spring 团队对 11 和 17 版本有相同的长期维护支持,这与 JDK 团队相关。这个新版本于 2017 年推出,采用了一种新的方法来使用反应式流进行函数式编程。
Spring Web MVC 是为 Servlet API 和 Servlet 容器服务的。这是可以的,直到有更多的服务需求,这发现了一个特殊的问题:每个请求都有一些阻塞;随着高需求,有必要做些别的事情。结果是:反应器,一个网络框架。在版本 5 中引入了 Spring WebFlux 模块,具有完全非阻塞的堆栈,支持反应流背压,并运行在 Netty、Undertow 和 Servlet 3.1+容器等服务器上。这是用少量线程处理并发性的非阻塞堆栈的部分答案,这些线程可以用更少的硬件进行扩展。
WebFlux 模块依赖于另一个 Spring 项目:项目反应器。Reactor 是 Spring WebFlux 的反应库选择。它提供了 Mono 和 Flux API 类型,通过与操作符的react vex词汇表一致的一组丰富的操作符来处理0..1和0..N的数据序列。Reactor 是一个反应流库,因此,它的所有操作符都支持非阻塞背压。Reactor 非常关注服务器端 Java。它是与 Spring 密切合作开发的。
我不想深入讨论 Spring 的很多特性,因为我可以用一个简单的 web 应用来展示它们。你怎么想呢?所有这些很酷的 WebFlux 特性都在它自己的章节中进行了回顾。
简单的 Spring Web 应用
让我们首先创建一个 Spring web 应用——一个 ToDo 应用,它提供了一个可以执行 CRUD(创建、读取、更新和删除)的 REST API。要创建一个新的 Spring 应用,您需要安装 Maven。在接下来的章节中,你可以选择 Maven 或者 Gradle。
使用 Maven 创建项目
让我们从使用 Maven 中的以下命令创建 ToDo Spring 项目开始。
$ mvn archetype:generate -DgroupId=com.apress.todo -DartifactId=todo -Dversion=0.0.1-SNAPSHOT -DinteractiveMode=false -DarchetypeArtifactId=maven-archetype-webapp
该命令为 web 应用生成基本模板和结构。通常,它会生成webapp和resources文件夹,但不会生成java文件夹,后者需要手动创建。
todo
├── pom.xml
└── src
└── main
├── resources
└── webapp
├── WEB-INF
│ └── web.xml
└── index.jsp
您可以在您喜欢的 IDE 中导入代码;这将使识别任何问题变得更加容易。
添加依赖关系
打开pom.xml,用清单 1-1 替换所有内容。
<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>com.apress.todo</groupId>
<artifactId>todo</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>todo Webapp</name>
<properties>
<!-- Generic properties -->
<java.version>1.8</java.version>
<!-- Web -->
<jsp.version>2.2</jsp.version>
<jstl.version>1.2</jstl.version>
<servlet.version>3.1.0</servlet.version>
<bootstrap.version>3.3.7</bootstrap.version>
<jackson.version>2.9.2</jackson.version>
<webjars.version>0.32</webjars.version>
<!-- Spring -->
<spring-framework.version>5.0.3.RELEASE</spring-framework.version>
<!-- JPA -->
<spring-data-jpa>1.11.4.RELEASE</spring-data-jpa>
<hibernate-jpa.version>1.0.0.Final</hibernate-jpa.version>
<hibernate.version>4.3.11.Final</hibernate.version>
<!-- Drivers -->
<h2.version>1.4.197</h2.version>
<!-- Logs -->
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version>
</properties>
<dependencies>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data-jpa}</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>${hibernate-jpa.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- Logs -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- Drivers -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Java EE Web dependencies -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp.version}</version>
<scope>provided</scope>
</dependency>
<!-- Web UI -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>${webjars.version}</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>${bootstrap.version}</version>
</dependency>
<!-- Web - JSON/XML Response -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
<build>
<finalName>todo</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Listing 1-1todo/pom.xml
清单 1-1 显示了pom.xml文件以及创建一个简单的 Spring web 应用所需的所有依赖项。
Spring 腹板配置
接下来,我们从 Spring 配置开始。Spring 需要开发人员决定类在哪里,它们如何相互交互,以及 web 应用的一些额外配置。
让我们从修改web.xml文件开始,如清单 1-2 所示。
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
version="2.5">
<display-name>ToDo Web Application</display-name>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Listing 1-2todo/src/main/webapp/WEB-INF/web.xml
需要设置DispatcherServlet,它是任何 Spring web app 的主要入口。这个类根据上下文配置连接所有东西。如您所见,这是一个非常简单的配置。
接下来,让我们通过创建一个dispatcherServlet-servlet.xml文件来配置 Spring 上下文。有一个命名约定;如果 servlet 在web.xml文件中被命名为todo,那么 Spring 上下文文件应该被命名为todo-servlet.xml。在本例中,servlet 被命名为dispatcherServlet,因此它寻找一个dispatcherServlet-servlet.xml文件(参见清单 1-3 )。
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:component-scan base-package="com.apress.todo" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jsonMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="jsonMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="simpleDateFormat" value="yyyy-MM-dd HH:mm:ss" />
</bean>
<bean id="xmlMapper" parent="jsonMapper">
<property name="createXmlMapper" value="true"/>
</bean>
<mvc:resources mapping="/webjars/**" location="classpath:META-INF/resources/webjars/" />
<jpa:repositories base-package="com.apress.todo.repository" />
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:META-INF/sql/schema.sql" />
<jdbc:script location="classpath:META-INF/sql/data.sql" />
</jdbc:embedded-database>
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="h2WebServer" class="org.h2.tools.Server" factory-method="createWebServer"
init-method="start" destroy-method="stop">
<constructor-arg value="-web,-webAllowOthers,-webDaemon,-webPort,8082" />
</bean>
</beans>
Listing 1-3todo/src/main/webapp/WEB-INF/dispatcherServlet-servlet.xml
清单 1-3 显示了 Spring web 配置。看看它使用的所有 XML 名称空间。这很有帮助,因为如果您使用带有代码完成功能的 IDE,它会为您提供每个条目的组件及其属性。我们来分析一下。
-
<context:component-scan/>。这个标签告诉 Spring 容器它需要扫描所有的类;它寻找注释,包括@Service和@Configuration。这有助于 Spring 连接所有的 Spring beans,以便您的应用可以运行。在这种情况下,它在com.apress.todo.*包级别扫描标记的类和所有的子包。 -
<mvc:annotation-driven/>。这个标签告诉 Spring 容器这是一个 web 应用,它需要寻找每一个@Controller和@RestController类及其具有@RequestMapping或其他 Spring MVC 注释的方法,因此它可以创建必要的 MVC beans 来接受来自用户的请求。 -
<mvc:message-converters/>。这个标签通知 MVC beans 在有请求时使用什么来进行消息转换。例如,如果有一个带有 HTTP 头Accept: application/xml的请求,它会以 XML 的形式响应,就像带有application/json一样。 -
jsonMapper和xmlMapper豆。这些类是 Spring beans,帮助格式化数据和创建正确的映射器。 -
<mvc:resources/>。这个标签告诉 Spring MVC 使用哪些资源,以及在哪里可以找到它们。在这种情况下,这个应用正在使用 WebJars (在pom.xml文件中声明)。 -
<jpa:repositories/>。这个标签告诉 Spring 容器和 Spring Data 模块扩展CrudRepository接口的接口在哪里。在这种情况下,它会在com.apress.todo.repository中寻找它们。*包装级别。 -
<jdbc:embedded-database/>。因为这个应用使用 JPA 和内存数据库的 H2 驱动程序,所以这个标记只是一个声明,使用一个可以在启动时执行 SQL 脚本的实用程序;在本例中,它创建了todo表并插入了一些记录。 -
jpaVendorAdapter比恩。使用 JPA 实现需要这个 bean 声明;在本例中,它是 Hibernate(在pom.xml文件中使用的一个依赖项)。换句话说,Hibernate 框架被用作 Java 持久性 API (JPA)的实现。 -
EntityManagerFactory比恩。对于每个 JPA 实现,有必要创建一个实体管理器来保存所有会话,并代表应用执行所有 SQL 语句。 -
TransactionManager比恩。应用需要有一个交易,因为我们不想有重复或坏数据,对不对?我们需要应用并符合 ACID(原子性、一致性、隔离性、持久性),所以我们需要事务。 -
<tx:annotation-driven/>。这个注释基于前面的声明设置所有的事务。 -
viewResolver比恩。有必要说明 web 应用将使用哪种视图引擎,因为有很多选项,比如 Java Server Faces、JSP 等等。 -
h2WebServer比恩。这个 bean 设置 H2 引擎,以便可以在应用中访问它。
正如你所看到的,这部分需要一点关于如何装配 Spring 的知识。如果你想了解更多,我推荐几本出版社的书,包括 I. Cosmina 等人的《Pro Spring 5 》。
我想向您展示运行一个更简单的 REST API 需要做些什么;相信我,如果你认为这太多了,试着用 Java EE 做同样的事情,用这个应用的所有特性(MVC,JPA,SQL 初始化,JSP,事务)。
让我们回顾一下启动时执行的 SQL 脚本。在resources/META-INF/sql文件夹中创建两个文件(参见清单 1-4 和清单 1-5 )。
create table todo (
id varchar(36) not null,
description varchar(255) not null,
created timestamp,
modified timestamp,
completed boolean,
primary key (id)
);
Listing 1-4todo/src/main/resources/META-INF/sql/schema.sql
如您所见,创建 SQL 表非常简单。
insert into todo values ('7fd921cfd2b64dc7b995633e8209f385','Buy Milk','2018-09-23 15:00:01','2018-09-23 15:00:01',false);
insert into todo values ('5820a4c2abe74f409da89217bf905a0c','Read a Book','2018-09-02 16:00:01','2018-09-02 16:00:01',false);
insert into todo values ('a44b6db26aef49e39d1b68622f55c347','Go to Spring One 2018','2018-09-18 12:00:00','2018-09-18 12:00:00',false);
Listing 1-5todo/src/main/resources/META-INF/sql/data.sql
当然,还有一些 SQL 语句和 ToDo。
重要的是要知道 JPA 需要一个持久性单元,您可以在其中配置一些东西,比如哪些托管类是持久性单元的一部分、类如何映射到数据库表、数据源连接等等。因此,有必要创建一个。您可以在resources/META-INF/文件夹中创建persistence.xml文件(参见清单 1-6 )。
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="toDo">
<description>My Persistence Unit</description>
</persistence-unit>
</persistence>
Listing 1-6todo/src/main/resources/META-INF/persistence.xml
这里没有必要声明映射的类或连接,因为 Spring Data 模块会处理它;你只需要声明一个persistence-unit名。
接下来,重要的是必须为应用登录,不仅是为了调试,而且你可以用它来了解你的应用正在发生什么。在resources文件夹中创建logback.xml文件(参见清单 1-7 )。
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns:="http://ch.qos.logback/xml/ns/logback"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback http://ch.qos.logback/xml/ns/logback/logback.xsd">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
</Pattern>
</layout>
</appender>
<logger name="org.springframework" level="info" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.springframework.jdbc" level="debug" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
<logger name="com.apress.todo" level="debug" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
<root level="error">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Listing 1-7todo/src/main/resources/logback.xml
再说一次,这里没什么特别的。请注意,com.apress.todo的记录级别被设置为DEBUG。
班级
接下来,是时候为 ToDo REST API 创建实际代码了。让我们从创建域模型开始:ToDo域类。在src/main/java文件夹中创建类。请记住,Maven 工具并没有创建这种结构;我们需要手动创建它(参见清单 1-8 )。
package com.apress.todo.domain;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.sql.Timestamp;
@Entity
public class ToDo {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
private String description;
private Timestamp created;
private Timestamp modified;
private boolean completed;
public ToDo() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Timestamp getCreated() {
return created;
}
public void setCreated(Timestamp created) {
this.created = created;
}
public Timestamp getModified() {
return modified;
}
public void setModified(Timestamp modified) {
this.modified = modified;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
}
Listing 1-8todo/src/main/java/com/apress/todo/domain/ToDo.java
正如你所看到的,它只是一个普通的 Java 类,但是因为这个应用持久化数据(在本例中是 ToDo ),所以有必要用@Entity注释标记这个类,并用@Id注释声明主键。这个类还使用额外的注释为主键生成一个 36 个随机字符的 GUID。
接下来,让我们创建一个包含所有 CRUD 操作的存储库。在这里,应用使用了 Spring Data 模块的强大功能,它隐藏了所有带有表和 keep 会话的样板映射类,甚至还进行事务处理。Spring Data 实现了所有的 CRUD 换句话说,您不需要担心如何保存、更新、删除和查找记录。
创建从CrudRepository接口扩展而来的ToDoRepository接口(参见清单 1-9 )。
package com.apress.todo.repository;
import com.apress.todo.domain.ToDo;
import org.springframework.data.repository.CrudRepository;
public interface ToDoRepository extends CrudRepository<ToDo,String> {
}
Listing 1-9todo/src/main/java/com/apress/todo/repository/ToDoRepository.java
清单 1-9 显示了一个接口。这个ToDoRepository接口从CrudRepository<T,K>扩展而来,是一个通用接口。CrudRepository需要一个域类和主键类型;在这种情况下,域类是ToDo类,主键类型是String(标有@Id注释的那个)。
在 XML 配置中,您使用了<jpa:repositories/>标签。该标签指向ToDoRepository包,这意味着 Spring Data 保持记录,并且它连接了与扩展CrudRepository接口的接口相关的一切。
接下来,让我们创建接受用户请求的 web 控制器。创建ToDoController类(参见清单 1-10 )。
package com.apress.todo.controller;
import com.apress.todo.domain.ToDo;
import com.apress.todo.repository.ToDoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("/")
public class ToDoController {
private ToDoRepository repository;
@Autowired
public ToDoController(ToDoRepository repository) {
this.repository = repository;
}
@GetMapping
public ModelAndView index(ModelAndView modelAndView, HttpServletRequest request) {
modelAndView.setViewName("index");
return modelAndView;
}
@RequestMapping(value = "/toDos", method = { RequestMethod.GET }, produces = {
MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE})
public ResponseEntity<Iterable<ToDo>> getToDos(@RequestHeader HttpHeaders headers) {
return new ResponseEntity<Iterable<ToDo>>(this.repository.findAll(), headers, HttpStatus.OK);
}
}
Listing 1-10todo/src/main/java/com/apress/todo/controller/ToDoController.java
清单 1-10 显示了网络控制器。花点时间复习一下。这里我们需要一整本书来描述所有的 Spring MVC 模块和每个特性。
这里重要的是这个类用@Controller注释进行了标记。还记得<mv:annotation-driven/>标签吗?该标签查找每个标记为@Controller的类,并向所有具有@GetMapping、@RequestMapping和@PostMapping注释的方法注册控制器,以基于定义的路径接受请求。在这种情况下,仅定义了/和/toDos路径。
这个类使用了一个将ToDoRepository作为参数的构造函数。这是由 Spring 容器通过@Autowired注释注入的。如果您使用的是 Spring 4.3 版本,可以省略这个注释;默认情况下,Spring container 识别出构造函数需要依赖项,并自动注入它们。这就像说,“嘿,Spring container,我需要注入ToDoRepository豆,因为我将使用它*。*“这就是 Spring 使用依赖注入的方式(还有方法注入、字段注入和 setter 注入)。
@GetMapping ( @RequestMapping默认情况下也是如此)响应/路径和视图名称;在这种情况下,它返回对应于the WEB-INF/view/index.jsp JSP 页面的索引名。@RequestMapping是做同样事情的另一种方式(@GetMapping,但是这次它声明的是/toDos路径。这个方法响应取决于请求者发送的头的种类,比如application/json或application/xml。它使用ResponseEntity作为响应;它使用存储库实例来调用从数据库返回所有 ToDo 的findAll方法,因为在 JSON 和 XML 映射器中声明的配置表明引擎会处理这种转换。
再次,花时间分析正在发生的事情。运行应用后,您可以使用所有这些注释。
接下来,让我们创建视图,这是在请求/路径时调用的 JSP。在WEB-INF/views文件夹中创建index.jsp(参见清单 1-11 )。
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Simple Directory Web App</title>
<link rel="stylesheet" type="text/css"
href="webjars/bootstrap/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css"
href="webjars/bootstrap/3.3.7/css/bootstrap-theme.min.css">
</head>
<body>
<div class="container theme-showcase" role="main">
<div class="jumbotron">
<h1>ToDo Application</h1>
<p>A simple Rest API Spring MVC application</p>
</div>
<div class="page-header">
<h1>API</h1>
<a href="toDos">Current ToDos</a>
</div>
</div>
</body>
</html>
Listing 1-11todo/src/main/webapp/WEB-INF/views/index.jsp
我认为这里唯一需要注意的是资源的使用,比如 WebJars。该应用正在使用引导 CSS。但是这些资源从哪里来呢?首先,声明pom.xml中的org.webjars:bootstrap依赖项。其次,在配置中使用了<mvc:resources/>标签来说明在哪里可以找到这些资源。
运行应用
您已经完成了运行应用所需的所有配置和代码。现在,是应用服务器的时候了。要运行该应用,请按照下列步骤操作。
-
打开一个终端,转到您的根项目(
todo/)。执行下一个 maven 命令。$ mvn clean package该命令将您的应用打包到一个 WAR 文件(web 归档文件)中,准备在应用服务器中部署。文件在
target/文件夹中,命名为todo.war。
图 1-2
-
下载 Tomcat 应用服务器。(运行这个 app 不需要很重的应用服务器;一只轻盈的雄猫就可以了)。可以从
https://tomcat.apache.org/download-90.cgi下载。 -
解压并安装在任何目录下。
-
将
target/todo.war复制到<tomcat-installation>/webapps/文件夹中。 -
运行你的雄猫。进入浏览器,点击
http://localhost:8080/todo网址(见图 1-2 )。
如果你点击链接,你应该有一个 XML 响应(见图 1-3 )。
图 1-3
如何获得 JSON 响应?打开终端并执行以下命令。
$ curl -H "Accept: application/json" localhost:8080/todo/toDos
[ {
"id" : "7fd921cfd2b64dc7b995633e8209f385",
"description" : "Buy Milk",
"created" : "2018-09-23 15:00:01",
"modified" : "2018-09-23 15:00:01",
"completed" : false
}, {
"id" : "5820a4c2abe74f409da89217bf905a0c",
"description" : "Read a Book",
"created" : "2018-09-02 16:00:01",
"modified" : "2018-09-02 16:00:01",
"completed" : false
}, {
"id" : "a44b6db26aef49e39d1b68622f55c347",
"description" : "Go to Spring One 2018",
"created" : "2018-09-18 12:00:00",
"modified" : "2018-09-18 12:00:00",
"completed" : false
} ]
你可以用application/xml来测试,看到和浏览器一样的结果。
恭喜你!您已经创建了第一个 Spring MVC REST API 应用。
使用 Java 配置
您可能认为 XML 对于创建配置来说太冗长了。嗯,有时候可以,但是 Spring 有另一种方式来配置 Spring 容器,那就是通过注释和 Java config 类。
如果您想尝试一下,您可以创建ToDoConfig类并添加清单 1-12 中所示的代码。
package com.apress.todo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.WebJarsResourceResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import javax.sql.DataSource;
import java.text.SimpleDateFormat;
import java.util.List;
@Configuration
@EnableJpaRepositories(basePackages="com.apress.todo.repository")
@EnableTransactionManagement
@EnableWebMvc
public class ToDoConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/","/resources/","/webjars/")
.resourceChain(true).addResolver(new WebJarsResourceResolver());
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
@Bean
public InternalResourceViewResolver jspViewResolver() {
InternalResourceViewResolver bean = new InternalResourceViewResolver();
bean.setPrefix("/WEB-INF/views/");
bean.setSuffix(".jsp");
return bean;
}
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.H2).addScript("META-INF/sql/schema.sql")
.addScript("META-INF/sql/data.sql").build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setDataSource(dataSource());
return factory;
}
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory().getNativeEntityManagerFactory());
return txManager;
}
}
Listing 1-12todo/src/main/java/com/apress/todo/config/ToDoConfig.java
清单 1-12 实际上与 XML 配置相同,但是这一次,它使用了 Java Config类,其中我们以编程方式声明了 Spring beans,并且有必要覆盖一些 web 配置。
如果你想运行它来测试这个JavaConfig类,你需要做一些事情。打开dispatcherServlet-servlet.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="com.apress.todo" />
</beans>
最后,需要告诉 Spring 在哪里可以找到@Configuration标记的类(另一个替代方法是使用WebApplicationInitializer类);一旦找到它,它就根据 Java Config类的声明连接一切。
记得用mvn clean包清理并重新打包你的应用,再次生成WAR文件。您可以运行它,并获得与使用 XML 配置相同的结果。
那么,你觉得 Spring 框架怎么样?是的,你需要了解发生了什么。您需要知道 Spring beans 生命周期是如何工作的,以及依赖注入是如何使用的。此外,了解一点 AOP(面向方面编程)也很重要,因为这是连接一切为我们工作的魔法的一部分。
你认为这太过分了吗?好吧,如果你试图用一个普通的 Java 2 EE 概要文件来制作同一个应用,那就更麻烦了。记住,它不仅仅是公开一个 REST API,而是使用数据库、事务、消息转换器、视图解析器等等;这就是为什么使用 Spring,web 应用更容易创建。
但是你猜怎么着?Spring Boot 为你做了所有的样板配置,通过创建企业 Spring 应用加快了开发速度!
注意
记住你可以从 Apress 网站或者 GitHub 的 https://github.com/Apress/pro-spring-boot-2 获得这本书的源代码。
摘要
关于 Spring 框架以及它在 Spring Boot 中扮演的角色,还有很多需要学习的地方。一章是不够的。所以,如果你想了解更多,我鼓励你在 https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/ 查阅 Spring 文档。
在下一章中,我们从 Spring Boot 开始,了解创建与本章相同的应用是多么容易,只是“需要启动”
二、Spring Boot 简介
在前一章中,我向您展示了什么是 Spring 框架,它的一些主要特性(比如依赖注入设计模式的实现),以及如何使用它(通过创建一个简单的 web/数据应用并将其部署到 Tomcat 服务器)。我还向您展示了创建 Spring 应用所需的每个步骤(例如,添加各种 XML 文件的配置选项,以及如何运行应用)。
在这一章中,我将向您展示什么是 Spring 它的主要组件,如何使用它来创建 Spring 应用,以及如何运行或部署它。这是创建 Spring 应用的一种更简单的方式。本书的其余部分涵盖了更多的细节;这只是对 Spring Boot 技术的一个小介绍。
Spring Boot
我可以说 Spring Boot 是 Spring 框架的下一个篇章,但是不要误解我:Spring Boot 不会取代 Spring 框架,因为 Spring Boot 是 Spring 框架的!您可以将 Spring Boot 视为轻松创建 Spring 应用的新方法。
Spring Boot 简化了我们的开发方式,因为它使创建生产就绪的基于 Spring 的应用变得容易,您可以“直接运行”你会发现,使用 Spring Boot,你可以创建带有嵌入式服务器的独立应用(默认情况下是 Tomcat,如果你使用新的 web-reactive 模块,则是 Netty),使它们 100%可运行和可部署。应用。
Spring Boot 最重要的特性之一是一个固执己见的运行时,它帮助你遵循创建健壮的、可扩展的、可伸缩的 Spring 应用的最佳实践。
你可以在 https://projects.spring.io/spring-boot/ 找到 Spring Boot 项目。非常广泛的文档在 https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ 。Spring Boot 主页如图 2-1 所示。
图 2-1
Spring Boot 首页( http://projects.spring.io/spring-boot/ )
Spring Boot 来了
要创建 Spring 应用,您需要了解所有的配置技术和/或技术需求。运行最简单的 Spring 应用也需要很多步骤。四年前,Spring 团队推出了第一个测试版,我有幸测试了它。结果是惊人的。现在,随着更多的特性加入到技术中,它已经真正成为创建 Spring 应用的“事实上的”方式。Spring Boot 让创建企业级应用变得更加容易。
如果你看一下 Spring Boot 项目的网页,会发现这样一句话:绝对不需要代码生成,也不需要 XML 配置。也许您想知道如何创建 Spring 应用并在没有任何配置的情况下运行它们。Spring 容器至少需要知道如何连接你的类,对吗?或者 Spring 容器需要知道如何使用你添加到应用中的技术。别担心。我会告诉你这项惊人技术背后的所有秘密。但是首先,让我们尽可能创建最简单的 Spring web 应用(参见清单 2-1 )。
@RestController
class WebApp{
@GetMapping("/")
String welcome(){
"<h1><font face="verdana">Spring Boot Rocks!</font></h1>"
}
}
Listing 2-1
app.groovy
清单 2-1 是一个 Groovy 应用,也是最简单的 Spring web 应用。为什么这么棒?我总是告诉我的学生,如果你懂 Java,那么你就懂 Groovy。Groovy 去掉了所有的 Java 样板文件,加上几行代码,你就有了一个 web app(不过不用担心,这本书大部分都是讲 Java 的;除了在最后一章我谈到了 Groovy 和 Kotlin,这是 Spring 语言支持的一个新的补充。你是怎么经营的?就像执行一样简单
$ spring run app.groovy
然后,您应该会看到带有 Spring Boot 标语的输出日志、Tomcat 容器初始化以及应用已经在端口 8080 上启动的提示。如果你打开浏览器并点击http://localhost:8080,那么你应该会看到文本 Spring Boot 摇滚!
你可能会说,“等一下!这个spring run命令是什么?怎么安装?我还需要什么?这是 Spring Boot 吗?”这是创建和运行 Spring 应用的众多方法之一。这是我第一次尝试展示这项技术的威力(四年前),一个简单的脚本可以运行一个完整的 Spring web 应用。Spring Boot 团队创建了 Spring Boot CLI 。
Spring Boot 命令行界面
Spring Boot CLI(命令行界面)是创建 Spring 应用的许多方法之一,但这种方法通常用于原型应用。你可以把它当成 Spring Boot 的游乐场。参考模型将在以下章节中介绍。我只是想使用简单的 Groovy 或 Java 脚本让您感受一下 Spring Boot 的强大。对我来说,Spring Boot CLI 是 Spring Boot 生态系统的重要组成部分。
现在,让我们回到前面的代码。您是否注意到清单 2-1 中没有导入?Spring Boot CLI 如何了解 web 应用以及如何运行它?
Spring Boot CLI 检查您的代码,并基于 Spring MVC 注释(@RestController和@GetMapping),它尝试使用嵌入式 Tomcat 服务器将您的代码作为 web 应用执行,并从内部运行 web 应用。幕后的神奇之处在于,Groovy 编程语言提供了一种简单的方法,通过使用 AST(抽象语法树)来截取语句和创建动态代码;因此,很容易注入缺失的 Spring 代码并运行它。换句话说,Spring Boot CLI 发现了您的应用,并注入了缺失的部分,以使一个完整的 Spring web 应用启动并运行。
还记得我说过它也可以运行 Java 脚本吗?让我们看一下同一个 web 应用的 Java 版本。我将暂时向您展示代码;如果你想运行这些应用,你可以阅读附录,在那里我解释了如何安装 Spring Boot CLI 及其特性(见清单 2-2 )。
package com.apress.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class SimpleWebApp {
public static void main(String[] args) {
SpringApplication.run(SimpleWebApp.class, args);
}
@RequestMapping("/")
public String greetings(){
return "<h1>Spring Boot Rocks in Java too!</h1>";
}
}
Listing 2-2
SimpleWebApp.java
清单 2-2 显示了 Java 中 Spring Boot 应用的入口点。首先,它在执行应用的 main 方法中使用了一个@SpringBootApplication注释和SpringApplication singleton 类。SpringApplication.run方法调用接受两个参数。第一个参数是包含@Configuration注释的主配置类(恰好是这个类的名字;但稍后将对此进行更多讨论)。第二个参数是应用参数(我们将在后面的章节中讨论)。从这个 Java 版本中可以看出,我们使用了 Spring MVC 注释:@RestController和@GetMapping。
您可以通过执行以下命令来运行此示例
$ spring run SimpleWebApp.java
如果你打开浏览器,点击http://localhost:8080/,你会看到“Spring Boot 也用 Java 摇滚了!”。
如果您想设置您的 Spring Boot CLI,您可以跳转到附录,在那里我包括一个分步安装,其所有功能,以及 Spring Boot CLI 的好处。对于 Spring 云应用的快速原型,Spring Boot CLI 是完美的播放器;这就是我在本书中包含 Spring Boot CLI 的原因。
Spring Boot 应用模型
Spring Boot 定义了一种轻松创建 Spring 应用的方法,以及一种遵循 Spring 应用最佳实践的编程模型。要创建 Spring Boot 应用,您需要以下组件:
-
一个构建/依赖管理工具,比如 Maven 或者 Gradle (Spring Boot 也支持 Ant 和Ivy;在本书中,每个例子只需要 Maven 或 Gradle)。
-
构建工具中正确的依赖管理和插件。如果你使用 Maven,需要一个
<parent/>标签(当然,有更多的方法来配置 Spring Boot,但是添加一个<parent/>标签是最简单的)和spring-boot-maven-plugin。如果你正在使用 Gradle,你需要应用org.springframework.boot和io.spring.dependency-management插件。- 使用
spring-boot-starters添加所需的依赖项。
- 使用
-
创建包含以下内容的主类
-
@SpringBootApplication注解 -
main 方法中的
SpringApplication.run语句。
-
在下一节中,我们将创建我们的第一个 Spring Boot 应用,我将解释所有前面的组件。这非常简单,但是我们如何开始呢?有什么工具可以帮助我们开始一个 Spring Boot 项目吗?答案是肯定的!我们实际上可以使用 Spring Boot CLI,因为它提供了一种创建 Spring Boot 项目的方法。我们还有 ide(集成开发环境),比如STS(Spring Tool Suitehttps://spring.io/tools)、来自 JetBrains(https://www.jetbrains.com/idea/)NetBeans(https://netbeans.org)、GitHubAtom(https://atom.io)、微软 VSCode ( https://code.visualstudio.com )。Atom 和 VSCode 都有插件,可以以非常简单的方式处理 Spring Boot 应用;但是Spring Initializr(http://start.spring.io)是我启动 Spring Boot 项目的偏好。在本书中,我使用 IntelliJ IDEA。
让我们通过创建第一个 Spring Boot 应用来看看如何使用 Spring Boot Initializr web 服务。
我的第一份 Spring Boot 申请
要创建我们的第一个 Spring Boot 应用,打开您的浏览器并进入 http://start.spring.io (见图 2-2 )。
图 2-2
图 2-2 显示了 Spring Boot Initializr 的主页,这是 Pivotal 提供的一项网络服务,可以帮助您轻松创建 Spring Boot 项目。
-
Let’s start by filling out the fields.
-
组:
com.apress -
神器:
demo -
依赖关系:
web
您可以选择 Maven 或 Gradle 项目类型。您可以选择编程语言(Java、Groovy 或 Kotlin)和 Spring Boot 版本。在 Generate Project 按钮下面,有一个链接,上面写着“切换到完整版本”链接,它显示了您需要的依赖项。在这种情况下,您可以在依赖项字段中输入 Web 并点击回车,如图 2-2 所示。
-
-
单击 Generate Project 按钮保存一个名为
demo.zip的文件。 -
解压缩
demo.zip文件并将项目导入到您喜欢的 IDE 中(我使用 IntelliJ IDEA)。如果你仔细观察,你会发现。zip 文件有一个包装器,这取决于您选择的项目类型。如果是 Gradle 项目,那么有一个grad le(grad le wrapper);如果它是一个 Maven 项目,那么它应该是一个 mvnw (Maven 包装器)。这意味着你不需要安装任何这些构建/管理工具,因为 Spring Boot Initializr 会把它们带给你。 -
检查构建/依赖项管理文件。打开
pom.xml或build.gradle文件。如果您选择了 Maven,请参见清单 2-3 。
<?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.apress</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Listing 2-3
Maven pom.xml
如您所见,我们拥有主要组件的一部分:<parent/>标签、spring-boot-starter-web依赖项和spring-boot-maven-plugin。
如果您选择了 Gradle,请参见清单 2-4 。
buildscript {
ext {
springBootVersion = '2.0.0.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.apress'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
Listing 2-4
build.gradle
build.gradle文件显示了一些必需的组件:org.springframework.boot和io.spring.dependency-management插件,以及spring-boot-starter-web依赖项。
- 打开
com.apress.demo.DemoApplication.java类(参见清单 2-5 )。
package com.apress.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Listing 2-5com.apress.demo.DemoApplication.java
如您所见,我们有运行应用的其他组件:@SpringBootApplication注释和SpringApplication.run语句。
- 为 web 控制器添加一个显示为文本的新类。创建
com.apress.demo.WebController.java类(参见清单 2-6 )。
package com.apress.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class WebController {
@GetMapping
public String index(){
return "Hello Spring Boot";
}
}
Listing 2-6com.apress.demo.WebController.java
这与脚本非常相似——一个简单的返回字符串的@RestController。
- 要运行您的应用,您可以使用 IDE,也可以转到项目的根目录并执行以下命令。对于 maven:
./mvnw spring-boot:run,对于 Gradle:./gradlew bootRun。
然后你可以去你的浏览器点击http://localhost:8080。你会看到“你好,Spring Boot”的短信。
恭喜你!您刚刚创建了第一个 Spring Boot 应用。
注意
这本书的所有配套代码都在 press 网站上。在本例中,我创建了两个项目:一个用 Maven,另一个用 Gradle。
为什么是 Spring Boot?
我们为什么要用 Spring Boot?这是一项令人惊叹的技术,适用于
-
遵循网飞工程团队开发的 12 个因素模式的云原生应用(
http://12factor.net) -
通过减少开发和部署时间提高生产效率。
-
企业生产就绪的 Spring 应用。
-
非功能性需求,如 Spring Boot 执行器(一个模块,提供与新平台无关的千分尺 (
https://micrometer.io)、健康检查和管理)和用于运行 web 应用的嵌入式容器(Tomcat、Netty、Undertow、Jetty 等)。). -
微服务,它因创建可伸缩、高可用性和健壮的应用而受到关注。Spring Boot 允许开发人员只关注业务逻辑,把重担留给 Spring 框架。
Spring Boot 特色
Spring Boot 有很多特色,我会在接下来的章节中向你展示,但我可以在这一节描述其中的一些。
Spring Boot
-
提供了
SpringApplication类。我向您展示了在 Java Spring Boot 应用中,main 方法执行这个单例类。这个特殊的类提供了一种便捷的方式来启动 Spring 应用。 -
允许您创建应用,而不需要任何 XML 配置。Spring Boot 不做任何代码生成。
-
通过
SpringApplicationBuildersingleton 类提供了一个 fluent builder API,它允许您创建具有多个应用上下文的层次结构。这个特性与 Spring 框架及其内部工作方式更相关。如果你是一个 Spring 开发者,我会在接下来的章节中解释这个特性,但是如果你是 Spring 和 Spring Boot 的新手,那么你只需要知道你可以扩展 Spring Boot 来获得对你的应用更多的控制。 -
提供了更多配置 Spring 应用事件和监听器的方法。
-
提供“固执己见”的技术;这个特性试图创建正确类型的应用,既可以是 web 应用(嵌入式 Tomcat、Netty、Undertow 或 Jetty 容器),也可以是单个应用。
-
提供了
org.springframework.boot.ApplicationArguments接口,允许访问任何应用参数。当您尝试使用参数运行应用时,这是一个有用的特性。 -
允许您在应用启动后执行代码。你唯一需要做的就是实现
CommandLineRunner接口,并提供run(String ...args)方法的实现。一个特殊的例子是在启动时初始化数据库中的记录,或者您可能希望在应用执行之前检查服务是否正在运行。 -
允许您通过使用
application.properties或application.yml文件来具体化配置。在接下来的章节中会有更多的介绍。 -
允许您通过启用
application.properties or application.yml文件中的spring.application.admin.enabled属性来添加与管理相关的功能,通常是通过 JMX。 -
允许您拥有配置文件,帮助您的应用在不同的环境中运行。
-
允许您以非常简单的方式配置和使用日志记录。
-
通过使用 starter poms,提供了一种配置和管理依赖项的简单方法。换句话说,如果你要创建一个 web 应用,你只需要在你的 Maven
pom.xml或build.gradle文件中包含spring-boot-start-web依赖项。 -
通过使用具有新的测微计平台无关框架的 Spring Boot 致动器,提供开箱即用的非功能性要求,这允许您对您的应用进行仪器化。
-
提供
@Enable<feature>注释,帮助您包含、配置和使用数据库(SQL 和 NoSQL)、缓存*、调度、消息传递、* Spring Integration*、* Spring 批处理*、* Spring Cloud 等等技术。
Spring Boot 拥有所有这些特征,甚至更多。我将在接下来的章节中详细介绍这些特性。现在,是时候通过了解 Spring Boot 的内部运作来开始了解它了。
摘要
在这一章中,我向您简要介绍了 Spring Boot 技术,该技术专门用于轻松创建 Spring 企业级应用。
在接下来的章节中,我将向您展示 Spring Boot 的内部原理,以及根据您的依赖项和代码创建正确应用的幕后魔术。当你创建不同的项目时,我会谈到 Spring Boot 所有的酷功能。
三、Spring Boot 内部结构和特点
在前一章,我简要介绍了 Spring Boot,创建 Spring Boot 应用的主要组件,并讨论了使用 Spring Initializr 创建 Spring Boot 项目是多么容易。
在这一章中,我将向您展示当 Spring Boot 启动您的应用时,在幕后发生了什么。一切都是关于自动配置!我从 Groovy 脚本开始(同样,您可以跳到附录部分并安装 Spring Boot CLI)。我使用一个普通的 Java 项目,就像第二章中的 Spring Boot 应用一样。让我们从学习自动配置是如何工作的开始。
自动配置
自动配置是 Spring Boot 的重要特性之一,因为它根据类路径、注释和任何其他配置声明(如 JavaConfig 类或 XML)来配置您的 Spring Boot 应用。
清单 3-1 是前几章中的同一个例子,但是在这种情况下,我用它来解释当 Spring Boot 运行它时在幕后发生了什么。
@RestController
class WebApp{
@GetMapping('/')
String index(){
"Spring Boot Rocks"
}
}
Listing 3-1app.groovy
您可以使用 Spring Boot CLI(命令行界面)运行此程序
$ spring run app.groovy
Spring Boot 不会生成任何源代码,但它会动态添加一些。这是 Groovy 的优势之一:您可以在运行时访问 AST(抽象语法树)。Spring Boot 从导入缺失的依赖项开始,比如org.springframework.web.bind.annotation.RestController注释,以及其他导入。
接下来,它确定您需要一个 spring-boot-starter-web (我将在接下来的章节中详细讨论),因为您分别用@RestController和@GetMapping注释标记了您的类和方法。它给代码添加了@Grab("spring-boot-web-starter")注释(对于 Groovy 脚本中的导入很有用)。
接下来,它添加了触发自动配置的必要注释,即@EnableAutoConfiguration注释(稍后,我会谈到这个注释,它恰好是 Spring Boot 背后的魔法),然后它添加了作为应用入口点的 main 方法。您可以在清单 3-2 中看到结果代码。
import org.springframework.web.bind.annotation.RestController
// Other Imports
@Grab("spring-boot-web-starter")
@EnableAutoConfiguration
@RestController
class WebApp{
@GetMapping("/")
String greetings(){
"Spring Boot Rocks"
}
public static void main(String[] args) {
SpringApplication.run(WebApp.class, args);
}
}
Listing 3-2app.groovy Modified by Spring Boot
清单 3-2 显示了 Spring Boot 运行的实际修改后的程序。您可以看到自动配置是如何工作的,但是通过运行带有- debug 参数的清单 3-1 。让我们来看看。
$ spring run app.groovy --debug
...
DEBUG 49009 --- [] autoConfigurationReportLoggingInitializer :
=========================
AUTO-CONFIGURATION REPORT
=========================
Positive matches:
-----------------
//You will see all the conditions that were met to enable a Web application. And this is because you have the //@RestController annotation.
Negative matches:
-----------------
//You will find all the conditions that failed. For example you will find that the ActiveMQAutoConfiguration class did //not match, because you don't have any reference of the ActiveMQConnectionFactory.
在终端中查看该命令的输出。请注意 Spring Boot 在运行这个简单的应用之前所做的所有正面和负面匹配。因为您正在运行 Spring Boot CLI,所以它会尝试猜测您想要运行哪种应用。当你创建一个 Maven 或者 Gradle 项目,并且你指定了依赖关系(pom.xml或者build.gradle),你就在帮助 Spring Boot 根据你的依赖关系做出决定。
禁用特定的自动配置
在第二章中,我谈到了@SpringBootApplication注释,它是 Spring Boot 应用的主要组件之一。这个注释相当于声明了@Configuration、@ComponentScan和@EnableAutoConfiguration注释。我为什么要提这个?因为您可以通过在类中使用@EnableAutoConfiguration或@SpringBootApplication注释添加exclude参数来禁用特定的自动配置。让我们看看清单 3-3 中 Groovy 脚本的一个例子。
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration
@RestController
@EnableAutoConfiguration(exclude=[ActiveMQAutoConfiguration.class])
class WebApp{
@RequestMapping("/")
String greetings(){
"Spring Boot Rocks"
}
}
Listing 3-3
app.groovy
清单 3-3 显示了具有 exclude 参数的@EnableAutoConfiguration注释。此参数接收自动配置类的数组。如果您使用下面的代码再次运行此操作,您会看到您所做的操作被排除。
$ spring run app.groovy --debug
...
Exclusions:
-----------
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration
...
当您希望 Spring Boot 跳过某些不必要的自动配置 s 时,这对于 Groovy 脚本来说是一项非常有用的技术。
让我们看看如何在 Java Spring Boot 应用上使用它(参见清单 3-4 )。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
@SpringBootApplication(exclude={ActiveMQAutoConfiguration.class,DataSourceAutoConfiguration.class})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Listing 3-4DemoApplication.java: Spring Boot Snippet
清单 3-4 显示了一个 Java 版本;在这个例子中,主类只声明了@SpringBootApplication注释,在这个注释中,您可以排除自动配置类。清单 3-4 显示了两个被排除的类:ActiveMQAutoConfiguration和DataSourceAutoConfiguration。为什么不使用@EnableAutoConfiguration标注?记住,@SpringBootApplication注释继承了@EnableAutoConfiguration、@Configuration和@ComponentScan,这就是为什么您可以在@SpringBootApplication注释中使用 exclude 参数。
当使用 debug 选项运行 Maven 或 Gradle 项目(使用示例清单 3-4 )时,您会在控制台输出中看到如下内容:
...
Exclusions:
-----------
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
...
@EnableAutoConfiguration 和@Enable 批注
Spring 框架及其部分模块,如 Spring Data、Spring AMQP、Spring Integration,提供了@Enable<Technology>注解;例如,@EnableTransactionManagement、@EnableRabbit和@EnableIntegration就是上述模块的一部分。在 Spring 应用中,您可以使用这些注释来遵循配置模式上的约定,使您的应用更容易开发和维护,而不必太担心它的配置。
Spring Boot 利用这些注释,在@EnableAutoConfiguration注释中使用它们来进行自动配置。让我们仔细看看@EnableAutoConfiguration注释,看看它背后的逻辑以及@Enable<Technology>注释适合在哪里(参见清单 3-5 )。
...
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
Listing 3-5org.springframework.boot.autoconfigure.EnableAutoConfiguration.java
清单 3-5 显示了@EnableAutoConfiguration标注;正如您已经知道的,这个类试图配置您的应用可能需要的 beans。自动配置类是基于类路径和应用定义的 beans 来应用的,但是这使得寻找所有必要配置类的org.springframework.boot.autoconfigure.AutoConfigurationImportSelector类更加强大。
AutoConfigurationImportSelector类有几个方法,但是对于自动配置最重要的一个是getCandidateConfigurations方法(参见清单 3-6 )。
...
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you
are using a custom packaging, make sure that file is correct.");
return configurations;
}
...
Listing 3-6org.springframework.boot.autoconfigure.AutoConfigurationImportSelector Snippet
清单 3-6 展示了AutoConfigurationImportSelector类的一个片段,其中getCandidateConfigurations方法返回一个SpringFactoriesLoader.loadFactoryNames。SpringFactoriesLoader.loadFactoryNames寻找spring-boot-autoconfigure jar 中定义的META-INF/spring.factories(参见清单 3-7 了解其内容)。
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
....
....
Listing 3-7spring-boot-autoconfigure-<version>.jar/META-INF/spring.factories Snippet
从清单 3-7 中可以看出,spring.factories定义了所有的自动配置类,用于设置应用运行所需的任何配置。让我们来看看CloudAutoConfiguration级(见清单 3-8 )。
package org.springframework.boot.autoconfigure.cloud;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.Cloud;
import org.springframework.cloud.app.ApplicationInstanceInfo;
import org.springframework.cloud.config.java.CloudScan;
import org.springframework.cloud.config.java.CloudScanConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.core.Ordered;
@Configuration
@Profile("cloud")
@AutoConfigureOrder(CloudAutoConfiguration.ORDER)
@ConditionalOnClass(CloudScanConfiguration.class)
@ConditionalOnMissingBean(Cloud.class)
@ConditionalOnProperty(prefix = "spring.cloud", name = "enabled", havingValue = "true", matchIfMissing = true)
@Import(CloudScanConfiguration.class)
public class CloudAutoConfiguration {
// Cloud configuration needs to happen early (before data, mongo etc.)
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 20;
}
Listing 3-8org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration.java
清单 3-8 向您展示了CloudAutoConfiguration类。如您所见,这是一个非常短的类,但是如果它在应用类路径中找到 spring-cloud 类,它就配置了一个云应用,但是如何配置呢?它使用@ConditionalOnClass和@ConditionalOnMissingBean注释来决定应用是否是云应用。不要太担心这个,因为在本书的章节扩展 Spring Boot 中,当你创建自己的自动配置类时,你会用到这些注释。
在清单 3-8 中要看到的另一件事是@ConditionalOnProperty注释的使用,这仅在属性spring.cloud被启用时才适用。值得一提的是,这种自动配置是在云配置文件中执行的,由@Profile标注表示。只有当其他注释满足它们的条件时,才会应用@Import注释(使用(@Conditional*注释),这意味着如果类路径中有spring-cloud-*类,就会执行CloudScanConfiguration类的导入。我会在第十三章中详细介绍。现在,您需要理解自动配置使用您的类路径来决定为您的应用配置什么。这就是为什么我们说 Spring Boot 是一个固执己见的运行时,还记得吗?
Spring Boot 特色
在这一部分,我将向您展示 Spring Boot 的一些特性。Spring Boot 是高度可定制的,从设置应用的自动配置(基于类路径)到定制它如何启动、显示什么以及基于它自己的属性启用或禁用什么。因此,让我们来了解一些定制您的 Spring 应用的 Spring Boot 功能。
让我们使用 Spring Boot 的 Initializr 创建一个 Spring Boot Java 项目。打开浏览器,进入 https://start.spring.io 。将以下值添加到字段中。请确保单击“切换到完整版本”字段,以便您可以修改软件包名称。
-
组:
com.apress.spring -
神器:
spring-boot-simple -
名称:
spring-boot-simple -
包名:
com.apress.spring
您可以选择 Maven 或 Gradle 项目类型。然后点击 Generate Project 按钮,下载一个 ZIP 文件。将它解压缩到您喜欢的任何地方,并将其导入到您喜欢的 IDE 中(参见图 3-1 )。
图 3-1
Spring Boot 项目
注意
您可以从 Apress 网站下载源代码,在每个项目中您都可以找到 Maven pom.xml 和 Gradle build.gradle 文件,因此您可以选择您想要使用的构建工具。
现在,运行 Spring Boot 应用。如果您使用的是 Maven,请使用您的 IDE 或打开一个终端并执行以下命令。
$ ./mvnw spring-boot:run
如果你使用的是 Gradle,你可以执行
$ ./gradlew bootRun
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.0.RELEASE)
INFO 10669 --- [ main] c.a.spring.SpringBootSimpleApplication : Starting SpringBootSimpleApplication on ...
INFO 10669 --- [ main] c.a.spring.SpringBootSimpleApplication : No active profile set, falling back to default profiles: default
INFO 10669 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation...
INFO 10669 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
INFO 10669 --- [ main] c.a.spring.SpringBootSimpleApplication : Started SpringBootSimpleApplication in 1.582 seconds (JVM running for 4.518)
INFO 10669 --- [Thread-3] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation...
INFO 10669 --- [Thread-3] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
您应该会看到类似于以下输出的内容。它展示了一面旗帜(Spring Boot)和一些日志。让我们看看清单 3-9 中的主要应用。
package com.apress.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootSimpleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSimpleApplication.class, args);
}
}
Listing 3-9src/main/java/com/apress/spring/SpringBootSimpleApplication.java
清单 3-9 显示了主应用。您已经从上一章了解了 Spring Boot 组件,但是让我们再回顾一下。
-
@SpringBootApplication。这个注释实际上是@ComponentScan、@Configuration和@EnableAutoConfiguration注释。从前面的章节中你已经了解了关于@EnableAutoConfiguration的一切。 -
SpringApplication。这个类为在 main 方法中执行的 Spring Boot 应用提供了引导。您需要传递被执行的类。
现在,您可以开始定制 Spring Boot 应用了。
SpringApplication 类
您可以使用SpringApplication进行更高级的配置,因为您可以用它创建一个实例并做更多的事情(参见清单 3-10 )。
package com.apress.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootSimpleApplication {
public static void main(String[] args) {
SpringApplication app =
new SpringApplication(SpringBootSimpleApplication.class);
//add more features here.
app.run(args);
}
}
Listing 3-10src/main/java/com/apress/spring/SpringBootSimpleApplication.java Version 2
SpringApplication允许你配置你的应用的行为方式,并且你可以控制所有 Spring beans 使用的主ApplicationContext。如果你需要了解更多关于ApplicationContext以及如何使用它,我推荐Pro Spring Framework 5(a press,2017),其中作者解释了关于 Spring 的一切。在这种情况下,我们重点关注 Spring Boot 的一些特性。让我们从酷的东西开始。
自定义横幅
每次运行应用时,您都会在应用的开头看到一个横幅。它可以用不同的方式定制。
实现org.springframework.boot.Banner接口(参见清单 3-11 )。
package com.apress.spring;
import java.io.PrintStream;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
@SpringBootApplication
public class SpringBootSimpleApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(SpringBootSimpleApplication.class);
app.setBanner(new Banner() {
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
out.print("\n\n\tThis is my own banner!\n\n".toUpperCase());
}
});
app.run(args);
}
}
Listing 3-11src/main/java/com/apress/spring/SpringBootSimpleApplication.java Version 3
当您运行该应用时,您会看到类似这样的内容:
$ ./mvnw spring-boot:run
THIS IS MY OWN BANNER!
INFO[main] c.a.spring.SpringBootSimpleApplication : Starting SpringBootSimpleApplication ...
...
...
INFO[main] c.a.spring.SpringBootSimpleApplication : Started SpringBootSimpleApplication in 0.789seconds (JVM running for 4.295)
INFO[Th-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@203f6b5: startup date [Thu Feb 25 19:00:34 MST 2016]; root of context hierarchy
INFO[Th-1] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
您可以创建自己的 ASCII 横幅并显示它。怎么做?有一个很酷的站点,从文本( http://patorjk.com )创建 ASCII 艺术,如图 3-2 。
图 3-2
文本到 ASCII 艺术生成器
图 3-2 所示为 http://patorjk.com 的网站。点击文本到 ASCII 艺术生成器链接。然后,在文本字段中添加 Pro Spring Boot 2.0 (或者任何你想要的东西)。然后,点击测试所有以查看所有 ASCII 艺术(参见图 3-3 )。
图 3-3
ASCII 类型
图 3-3 显示了所有的 ASCII 艺术(约 314)。现在,您可以选择一个。点击选择文本按钮,复制(Ctrl+C Windows/Cmd+C macOS)它,在src/main/resources/目录下创建一个名为banner.txt的文件(见图 3-4 )。
图 3-4
src/main/resource/banner.txt 内容
您可以再次运行您的应用。
$ ./mvnw spring-boot:run
您会看到您在banner.txt文件中添加的 ASCII 图片。如果您使用清单 3-11 (您设置横幅的地方)运行您的应用,它会覆盖它并使用您的类路径中的banner.txt文件;这是默认的。
默认情况下,Spring Boot 在类路径中查找banner.txt文件。但是你可以改变它的位置。在src/main/resources/META-INF/目录下创建另一个banner.txt文件(或者复制你已经有的文件)。通过传递一个-D参数来运行应用。如果您使用的是 Maven,请执行以下命令。
$ ./mvnw spring-boot:run -Dspring.banner.location=classpath:/META-INF/banner.txt
如果你正在使用 Gradle,你需要首先在build.gradle文件的末尾添加这个配置。
bootRun {
systemProperties = System.properties
}
执行以下命令。
$ ./gradlew bootRun -Dspring.banner.location=classpath:/META-INF/banner.txt
这个命令使用标志-D来传递指向新的类路径位置/META-INF/banner.txt的spring.banner.location属性。您可以在src/main/resources/application.properties文件中声明这个属性,如下所示。
spring.banner.location=classpath:/META-INF/banner.txt
如果使用 Maven,就像这样运行它:
$ ./mvnw spring-boot:run
如果您使用的是 Gradle,请像这样运行它:
$ ./gradlew bootRun
设置banner.txt文件有几个选项。
您可以完全移除横幅。你可以在src/main/resources/application.properties中这样定义它:
spring.main.banner-mode=off
该命令优先于位于classpath:banner.txt位置的默认banner.txt文件。此外,您可以通过编程来实现(只需记住注释掉该属性)(参见清单 3-12 )。
package com.apress.spring;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootSimpleApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(SpringBootSimpleApplication.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
}
Listing 3-12src/main/java/com/apress/spring/SpringBootSimpleApplication.java Version 4
SpringApplicationBuilder
SpringApplicationBuilder类提供了一个流畅的 API,并且是SpringApplication和ApplicationContext实例的构建器。它还提供了层次支持,并且我到目前为止向你展示的所有东西(用SpringApplication)都可以用这个构建器来设置(见清单 3-13 )。
package com.apress.spring;
import org.springframework.boot.Banner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class SpringBootSimpleApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.bannerMode(Banner.Mode.OFF)
.sources(SpringBootSimpleApplication.class)
.run(args);
}
}
Listing 3-13src/main/java/com/apress/spring/SpringBootSimpleApplication.java Version 5
清单 3-13 展示了SpringAplicationBuilder fluent API。接下来,我们来看更多的例子。
创建 Spring 应用时,可以有一个层次结构。(如果想了解更多 Spring 的应用上下文,我推荐 Pro Spring 第五版。)可以用 SpringApplicationBuilder 创建。
new SpringApplicationBuilder(SpringBootSimpleApplication.class)
.child(OtherConfig.class)
.run(args);
如果您有一个 web 配置,请确保它被声明为子配置。此外,父节点和子节点必须共享相同的org.springframework.core.env.Environment接口(这代表当前应用运行的环境;它与概要文件和属性声明相关)。
您可以在启动时记录信息;默认情况下,它被设置为 true。
new SpringApplicationBuilder(SpringBootSimpleApplication.class)
.logStartupInfo(false)
.run(args);
您可以激活配置文件。
new SpringApplicationBuilder(SpringBootSimpleApplication.class)
.profiles("prod","cloud")
.run(args);
稍后,我将向您展示概要文件,以便您能够理解前面的行。
您可以为一些ApplicationEvent事件附加监听器。
Logger log = LoggerFactory.getLogger(SpringBootSimpleApplication.class);
new SpringApplicationBuilder(SpringBootSimpleApplication.class)
.listeners(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
log.info("#### > " + event.getClass().getCanonicalName());
}
})
.run(args);
当您运行应用时,您应该会看到以下输出。
...
#### > org.springframework.boot.context.event.ApplicationPreparedEvent
...
#### > org.springframework.context.event.ContextRefreshedEvent
#### > org.springframework.boot.context.event.ApplicationReadyEvent
...
#### > org.springframework.context.event.ContextClosedEvent
...
您的应用可以添加必要的逻辑来处理这些事件。您还可以拥有这些额外的事件:ApplicationStartedEvent(这在开始时发送)、ApplicationEnvironmentPreparedEvent(这在环境已知时发送)、ApplicationPreparedEvent(这在 bean 定义之后发送)、ApplicationReadyEvent(这在应用准备就绪时发送)、以及ApplicationFailedEvent(这在启动期间出现异常时发送)。我向您展示了输出中的另一个(与 Spring 容器更相关)。
您可以阻止任何 web 环境自动配置的发生。请记住,Spring Boot 根据类路径猜测您正在运行哪种应用。对于一个 web app 来说,算法很简单;但是想象一下,你使用的库实际上是在没有 web 环境的情况下运行的,你的应用不是 web 应用,但是 Spring Boot 把它配置成 web 应用。
new SpringApplicationBuilder(SpringBootSimpleApplication.class)
.web(WebApplicationType.NONE)
.run(args);
你发现WebApplicationType是一个枚举。您的应用可以配置为WebApplicationType.NONE、WebApplicationType.SERVLET和WebApplicationType.REACTIVE。如你所见,它的意思很简单。
应用参数
Spring Boot 允许您将参数传递给应用。当您拥有SpringApplication.run(SpringBootSimpleApplication.class, args)时,您可以访问 beans 中的参数(参见清单 3-14 )。
package com.apress.spring;
import java.io.IOException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
@SpringBootApplication
public class SpringBootSimpleApplication {
public static void main(String[] args) throws IOException {
SpringApplication.run(SpringBootSimpleApplication.class, args);
}
}
@Component
class MyComponent {
private static final Logger log = LoggerFactory.getLogger(MyComponent.class);
@Autowired
public MyComponent(ApplicationArguments args) {
boolean enable = args.containsOption("enable");
if(enable)
log.info("## > You are enabled!");
List<String> _args = args.getNonOptionArgs();
log.info("## > extra args ...");
if(!_args.isEmpty())
_args.forEach(file -> log.info(file));
}
}
Listing 3-14src/main/java/com/apress/spring/SpringBootSimpleApplication.java -version 6
当您执行containsOption时,它期望参数是--<arg>。在清单 3-14 中,期待的是--enable的说法;getNonOptionArgs接受其他参数。要测试它,您可以执行下面的命令。
$ ./mvnw spring-boot:run -Dspring-boot.run.arguments="--enable"
你应该看看## > You are enabled。
此外,您可以像这样运行它:
$ ./mvnw spring-boot:run -Dspring-boot.run.arguments="arg1,arg2"
如果你正在使用 Gradle(在撰写本文时),你需要等待一段时间,因为传递参数仍然是一个问题(见https://github.com/spring-projects/spring-boot/issues/1176);但是您可以在可执行的 jar 中传递一个参数,我将在下一节中对此进行描述。
使用可执行 JAR 访问参数
您可以选择创建一个独立的应用——一个可执行的 JAR(您将了解到更多相关内容)。要创建一个可执行的 JAR,如果您使用的是 Maven,请执行下面的命令。
$ ./mvnw package
这个命令在target目录中创建可执行的 JAR。
或者,如果您使用的是 Gradle,您可以执行
$./gradlew build
这个命令在build/libs目录中创建一个可执行的 JAR。
现在您可以运行可执行的 JAR 了。
$ java -jar spring-boot-simple-0.0.1-SNAPSHOT.jar
您可以像这样传递参数:
$ java -jar spring-boot-simple-0.0.1-SNAPSHOT.jar --enable arg1 arg2
您应该得到相同的文本作为参数enable和列表arg1和arg2。
ApplicationRunner 和 CommandLineRunner
如果你在执行完SpringApplication后注意到 Spring Boot,它就结束了。如果您很好奇,在这个类被执行后,您不能使用 beans,但是有一个解决方案。Spring Boot 有公开运行方法的ApplicationRunner和CommandLineRunner接口(参见清单 3-15 )。
package com.apress.spring;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SpringBootSimpleApplication implements CommandLineRunner, ApplicationRunner{
private static final Logger log = LoggerFactory.getLogger(SpringBootSimpleApplication.class);
public static void main(String[] args) throws IOException {
SpringApplication.run(SpringBootSimpleApplication.class, args);
}
@Bean
String info(){
return "Just a simple String bean";
}
@Autowired
String info;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("## > ApplicationRunner Implementation...");
log.info("Accessing the Info bean: " + info);
args.getNonOptionArgs().forEach(file -> log.info(file));
}
@Override
public void run(String... args) throws Exception {
log.info("## > CommandLineRunner Implementation...");
log.info("Accessing the Info bean: " + info);
for(String arg:args)
log.info(arg);
}
}
Listing 3-15src/main/java/com/apress/spring/SpringBootSimpleApplication.java Version 7
清单 3-15 显示了CommandLineRunner和ApplicationRunner以及它们的实现。CommandLineRunner曝光public void run(String... args)方法,ApplicationRunner曝光public void run(ApplicationArguments args)方法。他们实际上是一样的。没有必要同时实现这两者。清单 3-16 显示了使用CommandLineRunner接口的另一种方式。
package com.apress.spring;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SpringBootSimpleApplication {
private static final Logger log = LoggerFactory.getLogger(SpringBootSimpleApplication.class);
public static void main(String[] args) throws IOException {
SpringApplication.run(SpringBootSimpleApplication.class, args);
}
@Bean
String info(){
return "Just a simple String bean";
}
@Autowired
String info;
@Bean
CommandLineRunner myMethod(){
return args -> {
log.info("## > CommandLineRunner Implementation...");
log.info("Accessing the Info bean: " + info);
for(String arg:args)
log.info(arg);
};
}
}
Listing 3-16src/main/java/com/apress/spring/SpringBootSimpleApplication.java Version 8
清单 3-16 显示了一个用@Bean注释的方法,返回一个CommandLineRunner实现。这个例子使用 Java 8 语法(lambda)进行返回。您可以添加任意多的返回CommadLineRunner的方法。如果您想以某种顺序执行,您可以使用@Order注释。
应用配置
我们开发人员知道我们永远不会摆脱应用配置。我们总是寻找持久存储的位置,例如 URL、IP、凭证、数据库信息等等——我们通常在应用中经常使用的数据。我们知道,作为一种最佳实践,我们需要避免对这种配置信息进行硬编码。我们需要外部化,以便它可以安全、易于使用和部署。
使用 Spring,您可以选择使用 XML 和<context:property-placeholder/>标签,或者您可以使用@PropertySource注释来声明您的属性,并指向声明它们的文件。Spring Boot 为你提供了同样的机制,但有更多的改进。
- Spring Boot 为您提供了不同的选项来保存您的应用配置。
-
您可以使用名为
application.properties的文件,该文件应该位于应用的根类路径中。(您可以在更多选项中添加该文件,稍后我将向您展示。) -
您可以使用名为
application.yml的 YAML 符号文件,该文件也需要位于根类路径中。(您可以在更多选项中添加该文件,稍后我将向您展示。) -
您可以使用环境变量。这正在成为云场景的默认实践。
-
您可以使用命令行参数。
-
请记住,Spring Boot 是一种固执己见的技术。它的大部分应用配置都是基于一个通用的application.properties或application.yml文件。如果没有指定,它已经将这些属性值作为默认值。您可以在 https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html 获得常用应用属性的完整列表。
Spring(和 Spring Boot)最好的特性之一是通过使用@Value注释(带有属性名)来访问属性值。或者可以从从org.springframework.core.env.PropertyResolver接口扩展而来的org.springframework.core.env.Environment接口访问它们。例如,如果您有一个带有
data.server=remoteserver:3030
您可以通过使用@Value注释来访问应用中的data.server属性,如下面的代码片段所示。
//...
@Service
public class MyService {
@Value("${data.server}")
private String server;
//...
}
这段代码片段展示了@Value注释的用法。Spring Boot 将来自application.properties文件的data.server属性值注入到带有值remoteserver:3030的服务器变量中。
如果您不想使用application.properties,您可以选择通过命令行注入属性。
$ java -jar target/myapp.jar --data.server=remoteserver:3030
你会得到同样的结果。如果您不喜欢application.properties文件或者您讨厌 YAML 语法,您可以使用一个名为SPRING_APPLICATION_JSON的专用环境变量来公开相同的属性及其值。
$ SPRING_APPLICATION_JSON='{ "data":{"server":"remoteserver:3030"}}' java -jar target/myapp.jar
同样,你会得到同样的结果。(Windows 用户应该先使用SET指令设置环境变量。)因此,Spring Boot 为您提供了公开应用属性的选项。
配置属性示例
让我们创建一个简单的项目,帮助您更好地理解应用配置。打开浏览器,进入 https://start.spring.io 。使用下列字段值。
-
组:
com.apress.spring -
ArtifactId:
spring-boot-config -
包名:
com.apress.spring -
名称:
spring-boot-config
你可以选择任何你觉得舒服的项目类型。单击 Generate Project 按钮,保存 ZIP 文件,将其解压缩并导入到您喜欢的 IDE 中。
在继续项目之前,如果要覆盖应用配置属性,您必须知道 Spring Boot 使用了一个顺序:
-
命令行参数
-
SPRING _ 应用 _JSON
-
JNDI (java:comp/env)
-
System.getProperties()
-
操作系统环境变量
-
RandomValuePropertySource(随机值)。*)
-
特定于配置文件(应用-{profile})。jar)在包 jar 之外。
-
特定于配置文件(应用-{profile})。jar)在包装罐的内部。
-
包 jar 外部的应用属性(application.properties)。
-
包 jar 中的应用属性(application.properties)。
-
@财产来源
-
spring application . set default properties
如您所见,这是覆盖应用属性的顺序。先说几个例子。
命令行参数
转到您的项目,编辑主类,看起来像清单 3-17 。
package com.apress.spring;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class SpringBootConfigApplication {
private static Logger log = LoggerFactory.getLogger(SpringBootConfigApplication.class);
public static void main(String[] args) {
SpringApplication.run(SpringBootConfigApplication.class, args);
}
@Value("${server.ip}")
String serverIp;
@Bean
CommandLineRunner values(){
return args -> {
log.info(" > The Server IP is: " + serverIp);
};
}
}
Listing 3-17src/main/java/com/apress/spring/SpringBootConfigApplication.java
清单 3-17 显示了主类。如您所见,它使用了@Value("${server.ip}")注释。这个注释翻译文本"${server.ip}"。它按照我之前提到的顺序查找这个属性及其值。
您可以通过在项目的根目录下执行以下命令来运行此示例。
$ ./mvnw spring-boot:run -Dserver.ip=192.168.12.1
如果您首先打包您的应用(创建一个可执行的 JAR ),然后如下运行它。
$ /.mvnw package
$ java -jar target/spring-boot-config-0.0.1-SNAPSHOT.jar --server.ip=192.168.12.1
在这两种情况下,您都会看到类似于下面的输出。
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.0.RELEASE)
INFO - [main] c.a.spring.SpringBootConfigApplication : Starting SpringBootConfigApplication v0.0..
INFO - [main] c.a.spring.SpringBootConfigApplication : No active profile set, falling back to de..
INFO - [main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.an..
INFO - [main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on sta..
INFO - [main] c.a.spring.SpringBootConfigApplication : Started SpringBootConfigApplication in 0...
INFO - [main] c.a.spring.SpringBootConfigApplication : > The Server IP is: 192.168.34.56
INFO - [main] c.a.spring.SpringBootConfigApplication : > App Name: My Config App
INFO - [main] c.a.spring.SpringBootConfigApplication : > App Info: This is an example
INFO - [ T-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annot..
INFO - [ T-2] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdo...
从输出中可以看到这个:> The Server IP is: 1921.68.12.1。
现在,让我们创建application.properties文件(参见清单 3-18 了解其内容)。
server.ip=192.168.23.4
Listing 3-18src/main/resources/application.properties
如果您使用相同的命令行参数运行应用,您会看到参数优先于来自application.properties文件的参数,但是如果您不使用参数运行它,如下所示。
$ ./mvnw spring-boot:run
或者
$ /.mvnw package
$ java -jar target/spring-boot-config-0.0.1-SNAPSHOT.jar
你得到了textThe Server IP is: 192.168.3.4。如果您做了大量的 JSON 格式化工作,也许您会对以这种格式传递属性感兴趣。您可以使用spring.application.json属性。所以,你可以这样运行它:
$ ./mvnw spring-boot:run -Dspring.application.json='{"server":{"ip":"192.168.145.78"}}'
或者
$ java -jar target/spring-boot-config-0.0.1-SNAPSHOT.jar --spring.application.json='{"server":{"ip":"192.168.145.78"}}'
您也可以将其作为环境变量添加。
$ SPRING_APPLICATION_JSON='{"server":{"ip":"192.168.145.78"}}' java -jar target/spring-boot-config-0.0.1-SNAPSHOT.jar
你看正文> The Server IP is: 192.168.145.78。是的,您可以添加引用您的属性的环境变量,如下所示:
$ SERVER_IP=192.168.150.46 ./mvnw spring-boot:run
或者
$ SERVER_IP=192.168.150.46 java -jar target/spring-boot-config-0.0.1-SNAPSHOT.jar
你看正文> The Server IP is: 192.168.150.46。
注意
请记住,对于 Windows 用户,您需要使用SET指令来使用环境变量。
Spring Boot 如何知道环境变量与属性server.ip相关?
宽松约束
Spring Boot 使用宽松的装订规则(见表 3-1 )。
表 3-1
Spring Boot 放松了约束
|财产
|
描述
| | --- | --- | | message.destinationName | 标准骆驼箱 | | 消息.目的地名称 | 虚线表示法,这是在 application.properties 或 YML 文件中添加的推荐方法。 | | 消息目的地名称 | 大写,这是操作系统环境变量的推荐用法 |
表 3-1 显示了适用于酒店名称的宽松规则。这就是为什么在前面的例子中,server.ip属性也被识别为SERVER_IP。这个宽松的规则与@ConfigurationProperties注释及其前缀有关,您将在后面的章节中看到。
更改位置和名称
Spring Boot 接到命令要找到application.properties或 YAML 的档案。
-
它查看位于当前目录中的
/config子目录。 -
当前目录
-
类路径/配置包
-
类路径根
您可以通过在当前目录下创建一个/config子目录并添加一个新的application.properties来进行测试,并测试顺序是否正确。记住,您应该已经在类路径根(src/main/resources)中有了一个application.properties文件。
Spring Boot 允许您更改属性文件的名称及其位置。所以举个例子,假设你使用的是/config子目录,属性文件的名字现在是mycfg.properties(其内容:server.ip=127.0.0.1)。然后,您可以使用以下命令运行该应用。
$./mvnw spring-boot:run -Dspring.config.name=mycfg
或者
$ java -jar target/spring-boot-config-0.0.1-SNAPSHOT.jar --spring.config.name=mycfg
或者
$ SPRING_CONFIG_NAME=mycfg java -jar target/spring-boot-config-0.0.1-SNAPSHOT.jar
你应该看到正文:> The Server IP is: 127.0.0.1。没有必要在名称中包含.properties;它会自动使用它。你也可以改变它的位置。比如创建一个名为app的子目录,添加一个mycfg.properties文件(其内容:server.ip=localhost)。然后,您可以运行或执行您的应用
$ ./mvnw spring-boot:run -Dspring.config.name=mycfg -Dspring.config.location=file:app/
或者
$ java -jar target/spring-boot-config-0.0.1-SNAPSHOT.jar --spring.config.location=file:app/ --spring.config.name=mycfg
或者您可以在src/main/resources/META-INF/conf中添加mycfg.properties(您可以创建它)并执行以下命令:
$ mkdir -p src/main/resources/META-INF/conf
$ cp config/mycfg.properties src/main/resources/META-INF/conf/
$ ./mvnw clean spring-boot:run -Dspring.config.name=mycfg -Dspring.config.location=classpath:META-INF/conf/
您应该会看到文本> The Server IP is: 127.0.0.1。尝试更改该属性值,这样您就可以看到它实际上正在类路径中查找。
Spring Boot 也有搜索属性文件的命令。
-
类路径
-
类路径:/配置
-
文件:
-
文件:配置/
除非用spring.config.location属性来改变它,否则要设置哪个环境变量来改变属性文件的位置呢?就是SPRING_CONFIG_LOCATION。
基于配置文件
如果我没弄错的话,从 3.1 版本开始,Spring 框架增加了一个很酷的特性,允许开发人员基于概要文件创建自定义属性和 beans。这是一种分离环境的有用方法,无需重新编译或打包 Spring 应用。唯一要做的事情是用@ActiveProfiles注释指定活动概要文件,或者获取当前环境并使用setActiveProfiles方法。或者你可以使用环境变量SPRING_PROFILES_ACTIVE,或者spring.profiles.active属性。
您可以使用以下格式的属性文件:application-{profile}.properties。让我们在你的config/子目录中创建两个文件:application-qa.properties和application-prod.properties。让我们看看每一个的内容。
-
应用-质量保证.属性
server.ip=localhost -
应用产品属性
server.ip=http://my-remote.server.com
现在,您可以使用
$ ./mvnw clean spring-boot:run -Dspring.profiles.active=prod
当您执行这个命令时,请看一下日志的开头。您应该会看到类似下面的输出。
...
INFO 58270 --- [main] c.a.spring... : The following profiles are active: prod
...
INFO 58270 --- [main] c.a.spring... : > The Server IP is: http://my-remote.server.com
INFO 58270 --- [main] c.a.spring... : > App Name: Super App
INFO 58270 --- [main] c.a.spring... : > App Info: This is production
您应该看到图例、The following profiles are active: prod,当然还有 profile 应用属性 active ( application-prod.properties)值:> The Server IP is: http://my-remote.server.com 。作为练习,尝试将application-prod.properties的名称改为mycfg-prod.properties,将application-qa.properties的名称改为mycfg-qa.properties。使用获得新名称的 Spring 属性。如果您没有设置任何活动的概要文件,它将使用默认值,这意味着它会抓取application.properties。
自定义属性前缀
Spring Boot 允许你为你的属性编写和使用你自己的自定义属性前缀。您唯一需要做的事情就是用@ConfigurationPropertie s 注释进行注释,这是一个 Java 类,它的属性是 setters 和 getters。
如果您正在使用 STS IDE,我建议在您的pom.xml或build.gradle文件中包含一个依赖项(取决于您正在使用哪个依赖项管理器)。这种依赖关系创建了一个代码洞察,它触发了属性编辑器的代码完成。所以如果你使用 Maven,你可以在你的pom.xml文件中添加下一个依赖项。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
如果您正在使用 Gradle,您可以将以下依赖项添加到您的build.gradle文件中。
dependencies {
optional "org.springframework.boot:spring-boot-configuration-processor"
}
compileJava.dependsOn(processResources)
这种依赖关系允许您处理自定义属性并完成代码。现在,让我们看看这个例子。修改您的src/main/resource/application.properties文件,看起来像清单 3-19 。
server.ip=192.168.3.5
myapp.server-ip=192.168.34.56
myapp.name=My Config App
myapp.description=This is an example
Listing 3-19src/main/resources/application.properties
清单 3-19 显示了application.properties文件。第二块是新的。它定义了以myapp为前缀的自定义属性。打开您的主应用类,将其编辑成清单 3-20 所示的样子。
package com.apress.spring;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@SpringBootApplication
public class SpringBootConfigApplication {
private static Logger log = LoggerFactory.getLogger(SpringBootConfigApplication.class);
public static void main(String[] args) {
SpringApplication.run(SpringBootConfigApplication.class, args);
}
@Value("${myapp.server-ip}")
String serverIp;
@Autowired
MyAppProperties props;
@Bean
CommandLineRunner values(){
return args -> {
log.info(" > The Server IP is: " + serverIp);
log.info(" > App Name: " + props.getName());
log.info(" > App Info: " + props.getDescription());
};
}
@Component
@ConfigurationProperties(prefix="myapp")
public static class MyAppProperties {
private String name;
private String description;
private String serverIp;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getServerIp() {
return serverIp;
}
public void setServerIp(String serverIp) {
this.serverIp = serverIp;
}
}
}
Listing 3-20src/main/java/com/apress/spring/SpringBootConfigApplication.java Version 9
清单 3-19 显示了主应用类。让我们检查一下。
-
@Value("${myapp.server-ip}")。现在注释有了一个 myapp.server-ip,这意味着该值等于 1921.68.34.56。 -
@Autowired MyAppProperties道具。这创建了一个MyAppProperties类型的实例。 -
@Component @ConfigurationProperties(prefix="myapp")。@ConfigurationProperties注释告诉 Spring Boot,该类用于前缀为myapp的application.properties文件中定义的所有属性。这意味着当你有myapp.serverIp(或myapp.server-ip)myapp.name和myapp.description时,它会识别。@Component注释只确保该类作为 bean 被拾取。
Spring Boot 使用宽松的规则将Environment属性绑定到@ConfigurationPropertiesbean,所以没有任何冲突名称。
现在,如果您运行您的应用,您应该会看到您所有的myapp属性。
$ ./mvnw clean spring-boot:run
...
> The Server IP is: 192.168.34.56
> App Name: My Config App
> App Info: This is an example
...
正如您所看到的,您有很多使用应用配置属性的选项,如果您想使用 YAML 语法,请参考 Spring Boot 文档中的示例。
通过提供有助于属性完成的元数据信息,可以向 IDE 添加命中。看看 https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html 上关于如何创建元数据的参考文档。
摘要
本章通过解释自动配置特性,包括@EnableAutoConfiguration注释如何在幕后工作,向您展示了 Spring Boot 洞察。您还学习了如何排除一些自动配置类。
您了解了一些 Spring Boot 功能以及如何使用应用配置属性。您还了解了如何通过添加前缀来自定义您的应用配置属性。
在下一章中,你将通过 Spring Boot 学习更多关于 web 应用的知识。