Spring Boot 項目是 Spring 大家族的后起之秀, 它通過
內(nèi)置的 Tomcat/Jetty, 可以創(chuàng)建獨立的Spring 程序, 無需依賴于 web 容器
自動配置, 封裝一些 starter 依賴庫, 大大簡化了以往繁瑣的依賴配置, 無需代碼生成,無需編寫一行 xml 配置文件
內(nèi)置了產(chǎn)品級的度量和健康檢查工具 actuator, 可用 yaml/properties 進行靈活的配置
Spring Boot 極大地降低了 Java Web Application 的開發(fā)成本, 提高了生產(chǎn)率, 沒那么繁瑣, 沒那么麻煩.
使用 Spring Boot 可以輕松地創(chuàng)建不依賴Web容器的, 具有產(chǎn)品級水準的基于Spring的應用程序, 它做了大量的封裝和接口的簡化, 提倡用更少的配置, 和更少代碼來創(chuàng)建Java Web 應用程序, 大大減輕了 Java 程序員的負擔.
Spring Boot 主要特點
- 可以方便快捷地創(chuàng)建獨立的 Spring 應用程序
- 直接嵌入Tomcat,Jetty或Undertow(無需部署WAR文件)
- 提供預定義的初始POM以簡化您的Maven配置
- 盡可能地自動配置 Spring, 約定優(yōu)于配置, 配置高于約定
- 提供產(chǎn)線就緒的功能,如指標,健康檢查和外部化配置
- 沒有代碼生成和無需XML配置
Spring Boot 還附帶了一個命令行工具,如果你想快速使用Spring原型,可以使用它來運行Groovy腳本,Groovy有著類似Java的語法,卻無需那么多的膠水代碼。
快速上手
Spring Boot 有一個用來快速生成應用骨架的頁面

選中所需的依賴庫, 會生成一個壓縮文件, 解開以后就是一個簡單的 Spring Boot 項目
java -jar target/hellospringboot-0.0.1-SNAPSHOT.jar --spring.profiles.active=production
或者也可以用命令行工具來生成, 例如在我的 macbook 上可以用 brew 來安裝 springboot 命令行工具
brew tap pivotal/tap
brew install springboot
spring init -n hellospringboot -a hellospringboot -g com.github.walterfan -d=web,jpa,thymeleaf,mysql hellospringboot
在其他 linux 系統(tǒng)上可通過 sdkman 來安裝, windows 上建議通過 vagrant 安裝一個 ubuntu 虛擬機
方法如下:
$ curl -s "https://get.sdkman.io" | bash
#另開一個新的終端
$ source ~/.sdkman/bin/sdkman-init.sh
$ sdk install springboot
$ spring init --java-version=1.8 --dependencies=web,data-jpa,thymeleaf,h2,security -packaging=jar --groupId=com.github.walterfan --artifactId=potato
構建工具可以選擇 Gradle 或傳統(tǒng)的 maven
也可以使用 https://start.spring.io, 選取你所需要的子模塊, 生成一個項目骨架
假設項目命名為 potato 土豆, 保存為 'potato.zip'
解開壓縮包
$ unzip potato.zip -d server
$ cd server
$ tree
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── github
│ │ └── walterfan
│ │ └── potato
│ │ └── DemoApplication.java
│ └── resources
│ ├── application.properties
│ ├── static
│ └── templates
└── test
└── java
└── com
└── github
└── walterfan
└── potato
└── DemoApplicationTests.java
讓我們添加一個 controller
package com.github.walterfan.potato.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Date;
@Controller
public class DemoController {
@RequestMapping(path={"/"})
public String welcome(@RequestParam(defaultValue = "Walter") String name, Model model) {
model.addAttribute("message", name + ", welcome to potato application at " + new Date());
return "welcome";
}
再添加一個頁面 src/main/resources/template/welcome.html
<!DOCTYPE HTML>
<html>
<head>
<title>Show me the codes, buddy</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style>
pre {
color: darkblue;
white-space: pre-wrap;
background: lightgray;
}
</style>
</head>
<body>
<div>Show me the codes, <p th:text="'Hi ' + ${message} + '!'" /> </div>
</body>
</html>
打開瀏覽器看一下
$ ./gradlew build
$ ./gradlew bootRun
打開網(wǎng)址 http://localhost/index?name=walter
顯示:
Show me the codes,
Hi Walter, welcome to potato application at Fri Feb 01 20:17:00 CST 2019!
Spring Boot 的三大亮點
下面我們扼要說明 Spring Boot 的三大亮點
- starter
- autoconfiguration
- acturator
1) Starter
以前基于 Spring 框架的Java 項目, 有個令人頭疼的依賴管理問題, 一是冗長的 pom.xml , 二是所引入的類之間可能存在臭名昭著的依賴黑洞問題, 各個依賴庫版本可不匹配, 各個庫所依賴的庫也可能有沖突, 程序員不得不用 dependency tree 細細察看, 手工排除有沖突的庫.
為解決此類問題, Spring Boot 提供了若干 spring-boot-starter 類, 大多數(shù)類也就是一個 pom.xml, 定義了一組功能相關的依賴模塊, 包含了所需依賴庫, 你導入它就行了, 而不必一個個導入并指定版本.
在 spring-boot-project 中的 spring-boot-starters 模塊包含了若干 spring-boot-starter-xxx 子模塊, 核心子模塊為 spring-boot-starter

以 spring-boot-starter-actuator 模塊為例, 主要就是一個 pom.xml
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>${revision}</version>
</parent>
<artifactId>spring-boot-starter-actuator</artifactId>
<name>Spring Boot Actuator Starter</name>
<description>Starter for using Spring Boot's Actuator which provides production
ready features to help you monitor and manage your application</description>
<properties>
<main.basedir>${basedir}/../../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
</dependencies>
</project>
2) 自動配置
Spring Boot 號稱開箱即用, 不用繁瑣的 XML 配置文件, 也不用 Java Config 文件, 其秘訣在于自動配置, 也就是說 JavaConfig 文件其實還是需要的, 不過它們是通過 classloader 和反射根據(jù)某些條件自動創(chuàng)建出來的.
SpringBootApplication 其實是一個組合注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//...
}
這里的關鍵是 @EnableAutoConfiguration:
- SpringApplication 會在 classpath 中搜索所有 META-INF/spring.factories 配置文件, 然后將其中的 org.springframework.boot.autoconfigure.EnableAuthConfiguration 的 key 對應的配置項加載到 Spring 容器中
- 只有 spring.boot.enableautoconfiguration 為 true(默認值) 時, 才啟用自動配置
- EnableAuthConfiguration 在自動配置時也可以用 exclude 來排除某些自動配置, 例如
@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoCofiguration.class})
public class WebAppConfig {
}
在配置文件中, spring.autoconfigure.exclude 屬性可以達到相同效果.
基于項目所依賴的 Jar 包進行自動配置, 例如在 classpath 中發(fā)現(xiàn)有 h2 的 jar 包,
并且也沒有手動配置任何的數(shù)據(jù)庫連接, Spring Boot 就會自動配置一個 h2 的內(nèi)存數(shù)據(jù)庫
自動配置是非侵略性的, 如果已經(jīng)有 DataSource 的手動配置, 自動配置便不會生效
在啟動應用時添加 --debug 選項, 可以看到哪些自動配置被應用了, 自動配置背后的魔法就是使用了 @ConditionalOnClass, @ConditionalOnMissingBean, ConditionalOnProperty 等這一類的 注解, 意為當某種條件成立或不成立時來應用一些配置或創(chuàng)建某些 Bean.
DataSource 自動配置剖析
在 spring-boot-configuration 項目中有一個 spring.factories 文件, 其中定義了若干自動配置類, 其中有一個 DataSourceAutoConfiguration 類, 這個類又 import 了 EmbeddedDataSourceConfiguration 類
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
...
在 EmbeddedDataSourceConfiguration 配置類中定義了dataSource bean 為由 EmbeddedDatabaseBuilder 構建的 EmbeddedDatabase
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class EmbeddedDataSourceConfiguration implements BeanClassLoaderAware {
private EmbeddedDatabase database;
private ClassLoader classLoader;
private final DataSourceProperties properties;
public EmbeddedDataSourceConfiguration(DataSourceProperties properties) {
this.properties = properties;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Bean
public EmbeddedDatabase dataSource() {
this.database = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseConnection.get(this.classLoader).getType())
.setName(this.properties.determineDatabaseName()).build();
return this.database;
}
@PreDestroy
public void close() {
if (this.database != null) {
this.database.shutdown();
}
}
}
而上述 EmbeddedDatabaseConnection 類的靜態(tài) get 方法中遍歷其定義的類型 H2, DERBY, HSQL, 使用 ClassUtils.isPresent(driverClass) 在 classpath 中尋找相關 class, 如果發(fā)現(xiàn) org.h2.Driver, 則返回 H2 這個 EmbeddedDatabaseConnection, 從而創(chuàng)建 H2 這個EmbeddedDatabase 為 DataSource
public enum EmbeddedDatabaseConnection {
/**
* No Connection.
*/
NONE(null, null, null),
/**
* H2 Database Connection.
*/
H2(EmbeddedDatabaseType.H2, "org.h2.Driver",
"jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"),
/**
* Derby Database Connection.
*/
DERBY(EmbeddedDatabaseType.DERBY, "org.apache.derby.jdbc.EmbeddedDriver",
"jdbc:derby:memory:%s;create=true"),
/**
* HSQL Database Connection.
*/
HSQL(EmbeddedDatabaseType.HSQL, "org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:%s");
private final EmbeddedDatabaseType type;
private final String driverClass;
private final String url;
EmbeddedDatabaseConnection(EmbeddedDatabaseType type, String driverClass,
String url) {
this.type = type;
this.driverClass = driverClass;
this.url = url;
}
//...
public static EmbeddedDatabaseConnection get(ClassLoader classLoader) {
for (EmbeddedDatabaseConnection candidate :
EmbeddedDatabaseConnection.values()) {
if (candidate != NONE &&
ClassUtils.isPresent(candidate.getDriverClassName(),
classLoader)) {
return candidate;
}
}
return NONE;
}
總結(jié)一下:
- SpringApplication 的 run 方法會調(diào)用 SpringFactoriesLoader 的 loadSpringFactories 方法
- SpringFactoriesLoader.loadSpringFactories 方法會讀取 "META-INF/spring.factories"
- 在 spring.factories 中定義了由 org.springframework.boot.autoconfigure.EnableAutoConfiguration 為鍵值對應的若干 Configuration 類, 其中就有 DataSourceAutoConfiguration
- DataSourceAutoConfiguration 導入了 EmbeddedDataSourceConfiguration
- EmbeddedDataSourceConfiguration 中在classpath 中尋找相關 driver(org.hsqldb.jdbcDriver) 類并創(chuàng)建對應的 EmbeddedDatabaseConnection
3) Actuator
寫一個例子, 做一個原型, 與開發(fā)一個真正的產(chǎn)品區(qū)別不亞于搭帳篷與蓋房子, 一個帳篷可以暫且棲身, 可是不耐風寒, 一幢房子才可以安家, 一個真正的產(chǎn)品需要產(chǎn)品級的監(jiān)控, Spring Boot Actutor 是Spring Boot 的一個重要的子模塊, 它可以提供用于生產(chǎn)環(huán)境的監(jiān)視和管理功能, 可選擇使用HTTP端點或JMX來管理和監(jiān)視你的服務。 還可將審核,運行狀況和指標收集功能應用于你的服務。
Actuator 是一個制造業(yè)的術語, 可翻譯為驅(qū)動器, 一個可以驅(qū)動設備自動運行某些操作的裝置
把 spring-boot-starter-actuator 加入你的依賴庫即可開箱即用, 由于有些端點的數(shù)據(jù)比較敏感, 所以我們也加入 spring-boot-starter-security
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
- JMX 端點
啟動你的 Spring Boot 應用程序, 打開 jconsole 通過JMX 連接, 如下所示

通過 actuator 端點,可以監(jiān)控應用程序并與之交互。 Spring Boot包含許多內(nèi)置端點,你也可以添加自己的端點, 或通過配置啟用或禁用每個端點, 以 JMX或HTTP 來公開你的端點。
- HTTP 端點
http://localhost:8080/actuator, HTTP 的默認端點只有 health 和 info

調(diào)整 application.properties 配置如下
logging.level.org.springframework: DEBUG
spring.security.user.name=admin
spring.security.user.password=pass1234
spring.security.user.roles=USER
management.endpoints.web.exposure.include=*
management.endpont.shutdown.enabled=true
management.endpont.health.show-details=when_authorized
則可以看到所有的 HTTP Actuator 端點
{
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"auditevents": {
"href": "http://localhost:8080/actuator/auditevents",
"templated": false
},
"beans": {
"href": "http://localhost:8080/actuator/beans",
"templated": false
},
"caches-cache": {
"href": "http://localhost:8080/actuator/caches/{cache}",
"templated": true
},
"caches": {
"href": "http://localhost:8080/actuator/caches",
"templated": false
},
"health-component": {
"href": "http://localhost:8080/actuator/health/{component}",
"templated": true
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-component-instance": {
"href": "http://localhost:8080/actuator/health/{component}/{instance}",
"templated": true
},
"conditions": {
"href": "http://localhost:8080/actuator/conditions",
"templated": false
},
"configprops": {
"href": "http://localhost:8080/actuator/configprops",
"templated": false
},
"env": {
"href": "http://localhost:8080/actuator/env",
"templated": false
},
"env-toMatch": {
"href": "http://localhost:8080/actuator/env/{toMatch}",
"templated": true
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
},
"loggers-name": {
"href": "http://localhost:8080/actuator/loggers/{name}",
"templated": true
},
"loggers": {
"href": "http://localhost:8080/actuator/loggers",
"templated": false
},
"heapdump": {
"href": "http://localhost:8080/actuator/heapdump",
"templated": false
},
"threaddump": {
"href": "http://localhost:8080/actuator/threaddump",
"templated": false
},
"metrics-requiredMetricName": {
"href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
"templated": true
},
"metrics": {
"href": "http://localhost:8080/actuator/metrics",
"templated": false
},
"scheduledtasks": {
"href": "http://localhost:8080/actuator/scheduledtasks",
"templated": false
},
"httptrace": {
"href": "http://localhost:8080/actuator/httptrace",
"templated": false
},
"mappings": {
"href": "http://localhost:8080/actuator/mappings",
"templated": false
}
}
看看 http://localhost:8080/actuator/metrics, 如下所示, 有這么多內(nèi)置的 metrics 條目
{
"names": [
"jvm.memory.max",
"jvm.threads.states",
"http.server.requests",
"jdbc.connections.active",
"process.files.max",
"jvm.gc.memory.promoted",
"system.load.average.1m",
"jvm.memory.used",
"jvm.gc.max.data.size",
"jdbc.connections.max",
"jdbc.connections.min",
"jvm.gc.pause",
"jvm.memory.committed",
"system.cpu.count",
"logback.events",
"tomcat.global.sent",
"jvm.buffer.memory.used",
"tomcat.sessions.created",
"jvm.threads.daemon",
"system.cpu.usage",
"jvm.gc.memory.allocated",
"tomcat.global.request.max",
"hikaricp.connections.idle",
"hikaricp.connections.pending",
"tomcat.global.request",
"tomcat.sessions.expired",
"hikaricp.connections",
"jvm.threads.live",
"jvm.threads.peak",
"tomcat.global.received",
"hikaricp.connections.active",
"hikaricp.connections.creation",
"process.uptime",
"tomcat.sessions.rejected",
"process.cpu.usage",
"tomcat.threads.config.max",
"jvm.classes.loaded",
"hikaricp.connections.max",
"hikaricp.connections.min",
"jvm.classes.unloaded",
"tomcat.global.error",
"tomcat.sessions.active.current",
"tomcat.sessions.alive.max",
"jvm.gc.live.data.size",
"hikaricp.connections.usage",
"tomcat.threads.current",
"hikaricp.connections.timeout",
"process.files.open",
"jvm.buffer.count",
"jvm.buffer.total.capacity",
"tomcat.sessions.active.max",
"hikaricp.connections.acquire",
"tomcat.threads.busy",
"process.start.time"
]
}
打開 http://localhost:8080/actuator/metrics/jvm.memory.used 可以看到 jvm 所使用的內(nèi)存如下所示:
{
"name": "jvm.memory.used",
"description": "The amount of used memory",
"baseUnit": "bytes",
"measurements": [
{
"statistic": "VALUE",
"value": 301460880
}
],
"availableTags": [
{
"tag": "area",
"values": [
"heap",
"nonheap"
]
},
{
"tag": "id",
"values": [
"Compressed Class Space",
"PS Survivor Space",
"PS Old Gen",
"Metaspace",
"PS Eden Space",
"Code Cache"
]
}
]
}