微服务之 Spring Boot
Spring Boot 项目是 Spring 大家族的后起之秀, 它通过
-
内置的 Tomcat/Jetty, 可以创建独立的Spring 程序, 无需依赖于 web 容器
-
自动配置, 封装一些 starter 依赖库, 大大简化了以往繁琐的依赖配置, 无需代码生成,无需编写一行 xml 配置文件
-
内置了产品级的度量和健康检查工具 actuator, 可用 yaml/properties 进行灵活的配置
Spring Boot 极大地降低了 Java Web Application 的开发成本, 提高了生产率, 没那么繁琐, 没那么麻烦.
使用 Spring Boot 可以轻松地创建不依赖Web容器的, 具有产品级水准的基于Spring的应用程序, 它做了大量的封装和接口的简化, 提倡用更少的配置, 和更少代码来创建Java Web 应用程序, 大大减轻了 Java 程序员的负担.
Spring Boot 主要特点
- 可以方便快捷地创建独立的 Spring 应用程序
- 直接嵌入Tomcat,Jetty或Undertow(无需部署WAR文件)
- 提供预定义的初始POM以简化您的Maven配置
- 尽可能地自动配置 Spring, 约定优于配置, 配置高于约定
- 提供产线就绪的功能,如指标,健康检查和外部化配置
- 没有代码生成和无需XML配置
Spring Boot 还附带了一个命令行工具,如果你想快速使用Spring原型,可以使用它来运行Groovy脚本,Groovy有着类似Java的语法,却无需那么多的胶水代码。
快速上手
Spring Boot 有一个用来快速生成应用骨架的页面
image.png选中所需的依赖库, 会生成一个压缩文件, 解开以后就是一个简单的 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 系统上可通过 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 或传统的 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
打开网址 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 类, 大多数类也就是一个 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 和反射根据某些条件自动创建出来的.
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 中发现有 h2 的 jar 包,
并且也没有手动配置任何的数据库连接, Spring Boot 就会自动配置一个 h2 的内存数据库
自动配置是非侵略性的, 如果已经有 DataSource 的手动配置, 自动配置便不会生效
在启动应用时添加 --debug 选项, 可以看到哪些自动配置被应用了, 自动配置背后的魔法就是使用了 @ConditionalOnClass, @ConditionalOnMissingBean, ConditionalOnProperty 等这一类的 注解, 意为当某种条件成立或不成立时来应用一些配置或创建某些 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 类的静态 get 方法中遍历其定义的类型 H2, DERBY, HSQL, 使用 ClassUtils.isPresent(driverClass) 在 classpath 中寻找相关 class, 如果发现 org.h2.Driver, 则返回 H2 这个 EmbeddedDatabaseConnection, 从而创建 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;
}
总结一下:
- SpringApplication 的 run 方法会调用 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) 类并创建对应的 EmbeddedDatabaseConnection
3) Actuator
写一个例子, 做一个原型, 与开发一个真正的产品区别不亚于搭帐篷与盖房子, 一个帐篷可以暂且栖身, 可是不耐风寒, 一幢房子才可以安家, 一个真正的产品需要产品级的监控, Spring Boot Actutor 是Spring Boot 的一个重要的子模块, 它可以提供用于生产环境的监视和管理功能, 可选择使用HTTP端点或JMX来管理和监视你的服务。 还可将审核,运行状况和指标收集功能应用于你的服务。
Actuator 是一个制造业的术语, 可翻译为驱动器, 一个可以驱动设备自动运行某些操作的装置
把 spring-boot-starter-actuator 加入你的依赖库即可开箱即用, 由于有些端点的数据比较敏感, 所以我们也加入 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 端点,可以监控应用程序并与之交互。 Spring Boot包含许多内置端点,你也可以添加自己的端点, 或通过配置启用或禁用每个端点, 以 JMX或HTTP 来公开你的端点。
- HTTP 端点
http://localhost:8080/actuator, HTTP 的默认端点只有 health 和 info
调整 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, 如下所示, 有这么多内置的 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 所使用的内存如下所示:
{
"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"
]
}
]
}