增加README.md文章导航

This commit is contained in:
罗祥 2019-02-01 10:12:58 +08:00
parent 0c37fb0031
commit b43a723910
25 changed files with 6242 additions and 5871 deletions

View File

@ -1,314 +1,332 @@
# spring boot actuator
## 一、用例涉及到的概念综述
### 1.1 端点
执行器端点endpoints可用于监控应用及与应用进行交互Spring Boot包含很多内置的端点你也可以添加自己的。例如health端点提供了应用的基本健康信息。 端点暴露的方式取决于你采用的技术类型大部分应用选择HTTP监控端点的ID映射到一个URL。例如health端点默认映射到/health。
下面的端点都是可用的:
| ID | 描述 | 是否敏感 |
| ----------- | ------------------------------------------------------------ | -------- |
| actuator | 为其他端点提供基于超文本的导航页面需要添加Spring HATEOAS依赖 | true |
| autoconfig | 显示一个自动配置类的报告,该报告展示所有自动配置候选者及它们被应用或未被应用的原因 | true |
| beans | 显示一个应用中所有Spring Beans的完整列表 | true |
| configprops | 显示一个所有@ConfigurationProperties的集合列表 | true |
| dump | 执行一个线程转储 | true |
| env | 暴露来自Spring ConfigurableEnvironment的属性 | true |
| flyway | 显示数据库迁移路径,如果有的话 | true |
| health | 展示应用的健康信息(当使用一个未认证连接访问时显示一个简单的'status',使用认证连接访问则显示全部信息详情) | false |
| info | 显示任意的应用信息 | false |
| liquibase | 展示任何Liquibase数据库迁移路径如果有的话 | true |
| metrics | 展示当前应用的'metrics'信息 | true |
| mappings | 显示一个所有@RequestMapping路径的集合列表 | true |
| shutdown | 允许应用以优雅的方式关闭(默认情况下不启用) | true |
| trace | 显示trace信息默认为最新的100条HTTP请求 | true |
如果使用Spring MVC你还可以使用以下端点
| ID | 描述 | 是否敏感 |
| -------- | ------------------------------------------------------------ | -------- |
| docs | 展示Actuator的文档包括示例请求和响应需添加spring-boot-actuator-docs依赖 | false |
| heapdump | 返回一个GZip压缩的hprof堆转储文件 | true |
| jolokia | 通过HTTP暴露JMX beans依赖Jolokia | true |
| logfile | 返回日志文件内容如果设置logging.file或logging.path属性支持使用HTTP Range头接收日志文件内容的部分信息 | |
根据端点暴露的方式sensitive属性可用做安全提示例如在使用HTTP访问敏感sensitive端点时需要提供用户名/密码如果没有启用web安全可能会简化为禁止访问该端点
### 1.2 启用端点
默认情况下除了以外的所有端点shutdown都已启用。要配置端点的启用请使用其management.endpoint.<id>.enabled属性。以下示例启用shutdown端点
```properties
management.endpoint.shutdown.enabled = true
```
### 1.3 暴露端点
由于端点可能包含敏感信息,因此应仔细考虑何时公开它们。下表显示了内置端点的默认曝光情况:
| ID | JMX | Web |
| -------------- | ----- | ---- |
| auditevents | 是 | 没有 |
| beans | 是 | 没有 |
| conditions | 是 | 没有 |
| configprops | 是 | 没有 |
| env | 是 | 没有 |
| flyway | 是 | 没有 |
| health | 是 | 是 |
| heapdump | N / A | 没有 |
| httptrace | 是 | 没有 |
| info | 是 | 是 |
| jolokia | N / A | 没有 |
| logfile | N / A | 没有 |
| loggers | 是 | 没有 |
| liquibase | 是 | 没有 |
| metrics | 是 | 没有 |
| mappings | 是 | 没有 |
| prometheus | N / A | 没有 |
| scheduledtasks | 是 | 没有 |
| sessions | 是 | 没有 |
| shutdown | 是 | 没有 |
| threaddump | 是 | 没有 |
**可以选择是否暴露端点include或者排除端点exclude,其中排除优先于暴露:**
| 属性 | 默认 |
| ----------------------------------------- | ------------ |
| management.endpoints.jmx.exposure.exclude | |
| management.endpoints.jmx.exposure.include | * |
| management.endpoints.web.exposure.exclude | |
| management.endpoints.web.exposure.include | info, health |
### 1.4 健康检查信息
您可以使用健康信息来检查正在运行的应用程序的状态。health端点公开的信息取决于management.endpoint.health.show-details可以使用以下值之一配置的属性
| 名称 | 描述 |
| --------------- | ------------------------------------------------------------ |
| never | 细节永远不会显示。 |
| when-authorized | 详细信息仅向授权用户显示。授权角色可以使用配置 management.endpoint.health.roles。 |
| always | 详细信息显示给所有用户。 |
## 二、项目说明
#### 1.1 项目结构说明
1. CustomHealthIndicator 自定义健康指标;
2. CustomHealthAggregator自定义健康聚合规则
3. CustomEndPoint自定义端点。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-actuator.png"/> </div>
#### 1.2 主要依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
#### 1.3 项目配置
```yaml
management:
endpoints:
web:
exposure:
# 这里用* 代表暴露所有端点只是为了观察效果,实际中按照需进行端点暴露
include: "*"
endpoint:
health:
# 详细信息显示给所有用户。
show-details: always
health:
status:
http-mapping:
# 自定义健康检查返回状态码对应的http状态码
FATAL: 503
```
#### 1.4 查看监控状态
导入actuator 的 start 并进行配置后访问http://127.0.0.1:8080/actuator/health 就可以看到对应的项目监控状态。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health.png"/> </div>
需要注意的是这里的监控状态根据实际项目所用到的技术不同而不同。因为以下HealthIndicators情况在适当时由Spring Boot自动配置的
| 名称 | 描述 |
| ------------------------------------------------------------ | -------------------------------- |
| [CassandraHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraHealthIndicator.java) | 检查Cassandra数据库是否启动。 |
| [DiskSpaceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java) | 检查磁盘空间不足。 |
| [DataSourceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java) | 检查是否可以获得连接DataSource。 |
| [ElasticsearchHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.java) | 检查Elasticsearch集群是否启动。 |
| [InfluxDbHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java) | 检查InfluxDB服务器是否启动。 |
| [JmsHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jms/JmsHealthIndicator.java) | 检查JMS代理是否启动。 |
| [MailHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java) | 检查邮件服务器是否启动。 |
| [MongoHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mongo/MongoHealthIndicator.java) | 检查Mongo数据库是否启动。 |
| [Neo4jHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java) | 检查Neo4j服务器是否启动。 |
| [RabbitHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/amqp/RabbitHealthIndicator.java) | 检查Rabbit服务器是否启动。 |
| [RedisHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisHealthIndicator.java) | 检查Redis服务器是否启动。 |
| [SolrHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java) | 检查Solr服务器是否已启动。 |
## 三、自定义健康检查指标
```java
/**
* @author : heibaiying
* @description : 自定义健康检查指标
*/
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
double random = Math.random();
// 这里用随机数模拟健康检查的结果
if (random > 0.5) {
return Health.status("FATAL").withDetail("error code", "某健康专项检查失败").build();
} else {
return Health.up().withDetail("success code", "自定义检查一切正常").build();
}
}
}
```
自定义检查通过的情况下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-health-up.png"/> </div>
自定义检查失败的情况:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health-fatal-200.png"/> </div>
这里我们可以看到自定义检查不论是否通过都不会影响整体的status,两种情况下都是status都是“up”。如果我们想通过自定义的检查检查去影响最终的检查结果比如我们健康检查针对的是支付业务在支付业务的不可用的情况下我们就认为整个服务是不可用的。这个时候就需要实现自定义实现健康状态的聚合。
## 四、自定义健康状态聚合规则
```java
/**
* @author : heibaiying
* @description : 对所有的自定义健康指标进行聚合,按照自定义规则返回总和健康状态
*/
@Component
public class CustomHealthAggregator implements HealthAggregator {
@Override
public Health aggregate(Map<String, Health> healths) {
for (Health health : healths.values()) {
// 聚合规则可以自定义,这里假设我们自定义的监控状态中有一项FATAL,就认为整个服务都是不可用的,否则认为整个服务是可用的
if (health.getStatus().getCode().equals("FATAL")) {
return Health.status("FATAL").withDetail("error code", "综合判断后服务宕机").build();
}
}
return Health.up().build();
}
}
```
当我们自定义健康检查不通过时候的结果如下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-heath-503.png"/> </div>
这里需要注意的是返回我们自定义的聚合状态的时候状态码也变成了503,这是我们在配置文件中进行定义的:
```properties
management.health.status.http-mapping.FATAL = 503
```
下表显示了内置状态的默认状态映射:
| Status | Mapping |
| -------------- | -------------------------------------------- |
| DOWN | SERVICE_UNAVAILABLE (503) |
| OUT_OF_SERVICE | SERVICE_UNAVAILABLE (503) |
| UP | No mapping by default, so http status is 200 |
| UNKNOWN | No mapping by default, so http status is 200 |
## 五、@Endpoint自定义端点
#### 5.1 自定义端点
spring boot 支持使用@Endpoint来自定义端点暴露应用信息。这里我们采用第三方sigar来暴露服务所在硬件的监控信息。
Sigar是Hyperic-hq产品的基础包是Hyperic HQ主要的数据收集组件。Sigar.jar的底层是用C语言编写的它通过本地方法来调用操作系统API来获取系统相关数据 [jar包下载地址](https://sourceforge.net/projects/sigar/)。
Sigar为不同平台提供了不同的库文件,下载后需要将库文件放到服务所在主机的对应位置:
- Windows下配置根据自己的操作系统版本选择sigar-amd64-winnt.dll或sigar-x86-winnt.dll拷贝到C:\Windows\System32中
- Linux下配置将libsigar-amd64-linux.so或libsigar-x86-linux.so拷贝到/usr/lib64或/lib64或/lib或/usr/lib目录下如果不起作用还需要sudochmod 744修改libsigar-amd64-linux.so文件权限
```java
@Endpoint(id = "customEndPoint")
@Component
public class CustomEndPoint {
@ReadOperation
public Map<String, Object> getCupInfo() throws SigarException {
Map<String, Object> cupInfoMap = new LinkedHashMap<>();
Sigar sigar = new Sigar();
CpuInfo infoList[] = sigar.getCpuInfoList();
CpuPerc[] cpuList = sigar.getCpuPercList();
for (int i = 0; i < infoList.length; i++) {
CpuInfo info = infoList[i];
cupInfoMap.put("CPU " + i + " 的总量MHz", info.getMhz()); // CPU的总量MHz
cupInfoMap.put("CPU " + i + " 生产商", info.getVendor()); // 获得CPU的生产商Intel
cupInfoMap.put("CPU " + i + " 类别", info.getModel()); // 获得CPU的类别Core
cupInfoMap.put("CPU " + i + " 缓存数量", info.getCacheSize()); // 缓冲存储器数量
cupInfoMap.put("CPU " + i + " 用户使用率", CpuPerc.format(cpuList[i].getUser())); // 用户使用率
cupInfoMap.put("CPU " + i + " 系统使用率", CpuPerc.format(cpuList[i].getSys())); // 系统使用率
cupInfoMap.put("CPU " + i + " 当前等待率", CpuPerc.format(cpuList[i].getWait())); // 当前等待率
cupInfoMap.put("CPU " + i + " 当前错误率", CpuPerc.format(cpuList[i].getNice())); // 当前错误率
cupInfoMap.put("CPU " + i + " 当前空闲率", CpuPerc.format(cpuList[i].getIdle())); // 当前空闲率
cupInfoMap.put("CPU " + i + " 总的使用率", CpuPerc.format(cpuList[i].getCombined()));// 总的使用率
}
return cupInfoMap;
}
}
```
其中可用的方法注解由http操作决定
| operation | HTTP方法 |
| ---------------- | -------- |
| @ReadOperation | GET |
| @WriteOperation | POST |
| @DeleteOperation | DELETE |
#### 5.2 访问自定义端点http://127.0.0.1:8080/actuator/customEndPoint
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-customEndPoint.png"/> </div>
关于 Sigar 的 更多监控参数可以参考博客:[java读取计算机CPU、内存等信息Sigar使用](https://blog.csdn.net/wudiazu/article/details/73829324)
Sigar 下载包中也提供了各种参数的参考用例:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/sigar.png"/> </div>
# spring boot actuator
## 目录<br/>
<a href="#一用例涉及到的概念综述">一、用例涉及到的概念综述</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-端点">1.1 端点</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-启用端点">1.2 启用端点</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#13-暴露端点">1.3 暴露端点</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#14-健康检查信息">1.4 健康检查信息</a><br/>
<a href="#二项目说明">二、项目说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-主要依赖">1.2 主要依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#13-项目配置">1.3 项目配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#14-查看监控状态">1.4 查看监控状态</a><br/>
<a href="#三自定义健康检查指标">三、自定义健康检查指标</a><br/>
<a href="#四自定义健康状态聚合规则">四、自定义健康状态聚合规则</a><br/>
<a href="#五@Endpoint自定义端点">五、@Endpoint自定义端点</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#51-自定义端点">5.1 自定义端点</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#52-访问自定义端点http//1270018080/actuator/customEndPoint">5.2 访问自定义端点http://127.0.0.1:8080/actuator/customEndPoint</a><br/>
## 正文<br/>
## 一、用例涉及到的概念综述
### 1.1 端点
执行器端点endpoints可用于监控应用及与应用进行交互Spring Boot包含很多内置的端点你也可以添加自己的。例如health端点提供了应用的基本健康信息。 端点暴露的方式取决于你采用的技术类型大部分应用选择HTTP监控端点的ID映射到一个URL。例如health端点默认映射到/health。
下面的端点都是可用的:
| ID | 描述 | 是否敏感 |
| ----------- | ------------------------------------------------------------ | -------- |
| actuator | 为其他端点提供基于超文本的导航页面需要添加Spring HATEOAS依赖 | true |
| autoconfig | 显示一个自动配置类的报告,该报告展示所有自动配置候选者及它们被应用或未被应用的原因 | true |
| beans | 显示一个应用中所有Spring Beans的完整列表 | true |
| configprops | 显示一个所有@ConfigurationProperties的集合列表 | true |
| dump | 执行一个线程转储 | true |
| env | 暴露来自Spring ConfigurableEnvironment的属性 | true |
| flyway | 显示数据库迁移路径,如果有的话 | true |
| health | 展示应用的健康信息(当使用一个未认证连接访问时显示一个简单的'status',使用认证连接访问则显示全部信息详情) | false |
| info | 显示任意的应用信息 | false |
| liquibase | 展示任何Liquibase数据库迁移路径如果有的话 | true |
| metrics | 展示当前应用的'metrics'信息 | true |
| mappings | 显示一个所有@RequestMapping路径的集合列表 | true |
| shutdown | 允许应用以优雅的方式关闭(默认情况下不启用) | true |
| trace | 显示trace信息默认为最新的100条HTTP请求 | true |
如果使用Spring MVC你还可以使用以下端点
| ID | 描述 | 是否敏感 |
| -------- | ------------------------------------------------------------ | -------- |
| docs | 展示Actuator的文档包括示例请求和响应需添加spring-boot-actuator-docs依赖 | false |
| heapdump | 返回一个GZip压缩的hprof堆转储文件 | true |
| jolokia | 通过HTTP暴露JMX beans依赖Jolokia | true |
| logfile | 返回日志文件内容如果设置logging.file或logging.path属性支持使用HTTP Range头接收日志文件内容的部分信息 | |
根据端点暴露的方式sensitive属性可用做安全提示例如在使用HTTP访问敏感sensitive端点时需要提供用户名/密码如果没有启用web安全可能会简化为禁止访问该端点
### 1.2 启用端点
默认情况下除了以外的所有端点shutdown都已启用。要配置端点的启用请使用其management.endpoint.<id>.enabled属性。以下示例启用shutdown端点
```properties
management.endpoint.shutdown.enabled = true
```
### 1.3 暴露端点
由于端点可能包含敏感信息,因此应仔细考虑何时公开它们。下表显示了内置端点的默认曝光情况:
| ID | JMX | Web |
| -------------- | ----- | ---- |
| auditevents | 是 | 没有 |
| beans | 是 | 没有 |
| conditions | 是 | 没有 |
| configprops | 是 | 没有 |
| env | 是 | 没有 |
| flyway | 是 | 没有 |
| health | 是 | 是 |
| heapdump | N / A | 没有 |
| httptrace | 是 | 没有 |
| info | 是 | 是 |
| jolokia | N / A | 没有 |
| logfile | N / A | 没有 |
| loggers | 是 | 没有 |
| liquibase | 是 | 没有 |
| metrics | 是 | 没有 |
| mappings | 是 | 没有 |
| prometheus | N / A | 没有 |
| scheduledtasks | 是 | 没有 |
| sessions | 是 | 没有 |
| shutdown | 是 | 没有 |
| threaddump | 是 | 没有 |
**可以选择是否暴露端点include或者排除端点exclude,其中排除优先于暴露:**
| 属性 | 默认 |
| ----------------------------------------- | ------------ |
| management.endpoints.jmx.exposure.exclude | |
| management.endpoints.jmx.exposure.include | * |
| management.endpoints.web.exposure.exclude | |
| management.endpoints.web.exposure.include | info, health |
### 1.4 健康检查信息
您可以使用健康信息来检查正在运行的应用程序的状态。health端点公开的信息取决于management.endpoint.health.show-details可以使用以下值之一配置的属性
| 名称 | 描述 |
| --------------- | ------------------------------------------------------------ |
| never | 细节永远不会显示。 |
| when-authorized | 详细信息仅向授权用户显示。授权角色可以使用配置 management.endpoint.health.roles。 |
| always | 详细信息显示给所有用户。 |
## 二、项目说明
#### 1.1 项目结构说明
1. CustomHealthIndicator 自定义健康指标;
2. CustomHealthAggregator自定义健康聚合规则
3. CustomEndPoint自定义端点。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-actuator.png"/> </div>
#### 1.2 主要依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
#### 1.3 项目配置
```yaml
management:
endpoints:
web:
exposure:
# 这里用* 代表暴露所有端点只是为了观察效果,实际中按照需进行端点暴露
include: "*"
endpoint:
health:
# 详细信息显示给所有用户。
show-details: always
health:
status:
http-mapping:
# 自定义健康检查返回状态码对应的http状态码
FATAL: 503
```
#### 1.4 查看监控状态
导入actuator 的 start 并进行配置后访问http://127.0.0.1:8080/actuator/health 就可以看到对应的项目监控状态。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health.png"/> </div>
需要注意的是这里的监控状态根据实际项目所用到的技术不同而不同。因为以下HealthIndicators情况在适当时由Spring Boot自动配置的
| 名称 | 描述 |
| ------------------------------------------------------------ | -------------------------------- |
| [CassandraHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraHealthIndicator.java) | 检查Cassandra数据库是否启动。 |
| [DiskSpaceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java) | 检查磁盘空间不足。 |
| [DataSourceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java) | 检查是否可以获得连接DataSource。 |
| [ElasticsearchHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.java) | 检查Elasticsearch集群是否启动。 |
| [InfluxDbHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java) | 检查InfluxDB服务器是否启动。 |
| [JmsHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jms/JmsHealthIndicator.java) | 检查JMS代理是否启动。 |
| [MailHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java) | 检查邮件服务器是否启动。 |
| [MongoHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mongo/MongoHealthIndicator.java) | 检查Mongo数据库是否启动。 |
| [Neo4jHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java) | 检查Neo4j服务器是否启动。 |
| [RabbitHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/amqp/RabbitHealthIndicator.java) | 检查Rabbit服务器是否启动。 |
| [RedisHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisHealthIndicator.java) | 检查Redis服务器是否启动。 |
| [SolrHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java) | 检查Solr服务器是否已启动。 |
## 三、自定义健康检查指标
```java
/**
* @author : heibaiying
* @description : 自定义健康检查指标
*/
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
double random = Math.random();
// 这里用随机数模拟健康检查的结果
if (random > 0.5) {
return Health.status("FATAL").withDetail("error code", "某健康专项检查失败").build();
} else {
return Health.up().withDetail("success code", "自定义检查一切正常").build();
}
}
}
```
自定义检查通过的情况下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-health-up.png"/> </div>
自定义检查失败的情况:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health-fatal-200.png"/> </div>
这里我们可以看到自定义检查不论是否通过都不会影响整体的status,两种情况下都是status都是“up”。如果我们想通过自定义的检查检查去影响最终的检查结果比如我们健康检查针对的是支付业务在支付业务的不可用的情况下我们就认为整个服务是不可用的。这个时候就需要实现自定义实现健康状态的聚合。
## 四、自定义健康状态聚合规则
```java
/**
* @author : heibaiying
* @description : 对所有的自定义健康指标进行聚合,按照自定义规则返回总和健康状态
*/
@Component
public class CustomHealthAggregator implements HealthAggregator {
@Override
public Health aggregate(Map<String, Health> healths) {
for (Health health : healths.values()) {
// 聚合规则可以自定义,这里假设我们自定义的监控状态中有一项FATAL,就认为整个服务都是不可用的,否则认为整个服务是可用的
if (health.getStatus().getCode().equals("FATAL")) {
return Health.status("FATAL").withDetail("error code", "综合判断后服务宕机").build();
}
}
return Health.up().build();
}
}
```
当我们自定义健康检查不通过时候的结果如下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-heath-503.png"/> </div>
这里需要注意的是返回我们自定义的聚合状态的时候状态码也变成了503,这是我们在配置文件中进行定义的:
```properties
management.health.status.http-mapping.FATAL = 503
```
下表显示了内置状态的默认状态映射:
| Status | Mapping |
| -------------- | -------------------------------------------- |
| DOWN | SERVICE_UNAVAILABLE (503) |
| OUT_OF_SERVICE | SERVICE_UNAVAILABLE (503) |
| UP | No mapping by default, so http status is 200 |
| UNKNOWN | No mapping by default, so http status is 200 |
## 五、@Endpoint自定义端点
#### 5.1 自定义端点
spring boot 支持使用@Endpoint来自定义端点暴露应用信息。这里我们采用第三方sigar来暴露服务所在硬件的监控信息。
Sigar是Hyperic-hq产品的基础包是Hyperic HQ主要的数据收集组件。Sigar.jar的底层是用C语言编写的它通过本地方法来调用操作系统API来获取系统相关数据 [jar包下载地址](https://sourceforge.net/projects/sigar/)。
Sigar为不同平台提供了不同的库文件,下载后需要将库文件放到服务所在主机的对应位置:
- Windows下配置根据自己的操作系统版本选择sigar-amd64-winnt.dll或sigar-x86-winnt.dll拷贝到C:\Windows\System32中
- Linux下配置将libsigar-amd64-linux.so或libsigar-x86-linux.so拷贝到/usr/lib64或/lib64或/lib或/usr/lib目录下如果不起作用还需要sudochmod 744修改libsigar-amd64-linux.so文件权限
```java
@Endpoint(id = "customEndPoint")
@Component
public class CustomEndPoint {
@ReadOperation
public Map<String, Object> getCupInfo() throws SigarException {
Map<String, Object> cupInfoMap = new LinkedHashMap<>();
Sigar sigar = new Sigar();
CpuInfo infoList[] = sigar.getCpuInfoList();
CpuPerc[] cpuList = sigar.getCpuPercList();
for (int i = 0; i < infoList.length; i++) {
CpuInfo info = infoList[i];
cupInfoMap.put("CPU " + i + " 的总量MHz", info.getMhz()); // CPU的总量MHz
cupInfoMap.put("CPU " + i + " 生产商", info.getVendor()); // 获得CPU的生产商Intel
cupInfoMap.put("CPU " + i + " 类别", info.getModel()); // 获得CPU的类别Core
cupInfoMap.put("CPU " + i + " 缓存数量", info.getCacheSize()); // 缓冲存储器数量
cupInfoMap.put("CPU " + i + " 用户使用率", CpuPerc.format(cpuList[i].getUser())); // 用户使用率
cupInfoMap.put("CPU " + i + " 系统使用率", CpuPerc.format(cpuList[i].getSys())); // 系统使用率
cupInfoMap.put("CPU " + i + " 当前等待率", CpuPerc.format(cpuList[i].getWait())); // 当前等待率
cupInfoMap.put("CPU " + i + " 当前错误率", CpuPerc.format(cpuList[i].getNice())); // 当前错误率
cupInfoMap.put("CPU " + i + " 当前空闲率", CpuPerc.format(cpuList[i].getIdle())); // 当前空闲率
cupInfoMap.put("CPU " + i + " 总的使用率", CpuPerc.format(cpuList[i].getCombined()));// 总的使用率
}
return cupInfoMap;
}
}
```
其中可用的方法注解由http操作决定
| operation | HTTP方法 |
| ---------------- | -------- |
| @ReadOperation | GET |
| @WriteOperation | POST |
| @DeleteOperation | DELETE |
#### 5.2 访问自定义端点http://127.0.0.1:8080/actuator/customEndPoint
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-customEndPoint.png"/> </div>
关于 Sigar 的 更多监控参数可以参考博客:[java读取计算机CPU、内存等信息Sigar使用](https://blog.csdn.net/wudiazu/article/details/73829324)
Sigar 下载包中也提供了各种参数的参考用例:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/sigar.png"/> </div>

View File

@ -1,247 +1,259 @@
# spring-boot 基础
## 一、说明
#### 1.1 项目结构说明
1. 本项目搭建一个简单的hello spring 的 web工程简单说明spring-boot 的开箱即用的特性;
2. 模板引擎采用freemaker 和 thymeleaf 作为示例分别对应模板文件makershow.ftl 和 leafShow.html
3. spring boot 2.x 默认是不支持jsp的需要额外的配置关于使用jsp的整合可以参考[spring-boot-jsp](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-jsp)项目。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-base.png"/> </div>
#### 1.2 项目依赖
导入相关的starter(启动器)
```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-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heibaiying</groupId>
<artifactId>spring-boot-base</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-base</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web 启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok 插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--测试相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!--Spring Boot的Maven插件Spring Boot Maven plugin能够以Maven的方式为应用提供Spring Boot的支持-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
1. spring boot 项目默认继承自spring-boot-starter-parent而spring-boot-starter-parent继承自spring-boot-dependencies, spring-boot-dependencies中定义了关于spring boot 依赖的各种jar包的版本是spring boot 的版本管理中心。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dependencies.png"/> </div>
2. 关于spring boot 2.x官方支持的所有starter 可以参见官方文档 [Table 13.1. Spring Boot application starters](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#using-boot-starter)
# spring-boot 基础
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目依赖">1.2 项目依赖</a><br/>
<a href="#二spring-boot-主启动类">二、spring boot 主启动类</a><br/>
<a href="#三开箱即用的web工程">三、开箱即用的web工程</a><br/>
<a href="#四模板引擎">四、模板引擎</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-freemarker">4.1 freemarker</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-thymeleaf">4.2 thymeleaf</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-文档说明">4.3 文档说明</a><br/>
## 正文<br/>
## 二、spring boot 主启动类
如果采用IDEA 或者 Spring Tool Suite (STS) 等开发工具创建的spring boot 工程,会默认创建启动类,如果没有创建,需要手动创建启动类
```java
package com.heibaiying.springbootbase;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootBaseApplication {
// 启动类默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
public static void main(String[] args) {
SpringApplication.run(SpringBootBaseApplication.class, args);
}
}
```
@SpringBootApplication 注解是一个复合注解,里面包含了@ComponentScan注解,默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
```java
@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 {
...
}
```
## 三、开箱即用的web工程
在springbootBaseApplication.java 的同级目录创建controller文件夹并在其中创建RestfulController.java,启动项目访问localhost:8080/restful/programmers 即可看到项目搭建成功。
```java
/**
* @author : heibaiying
* @description : restful 控制器
*/
@RestController
@RequestMapping("restful")
public class RestfulController {
@GetMapping("programmers")
private List<Programmer> getProgrammers() {
List<Programmer> programmers = new ArrayList<>();
programmers.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
programmers.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
return programmers;
}
}
```
这里之所以能够开箱即用是因为我们在项目中导入spring-boot-starter-web启动器@SpringBootApplication 复合注解中默认开启了@EnableAutoConfiguration注解允许开启自动化配置spring在检查导入starter-web的依赖后就会开启web的自动化配置。
## 四、模板引擎
这里我们在一个项目中同时导入了freemaker 和 thymeleaf的starter虽然并不推荐但是在同一个项目中是可以混用这两种模板引擎的
#### 4.1 freemarker
```java
/**
* @author : heibaiying
* @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
*/
@Controller
@RequestMapping("freemarker")
public class FreeMarkerController {
@RequestMapping("show")
private String programmerShow(ModelMap modelMap){
List<Programmer> programmerList=new ArrayList<>();
programmerList.add(new Programmer("xiaoming",12,100000.00f,LocalDate.of(2019,Month.AUGUST,2)));
programmerList.add(new Programmer("xiaohong",23,900000.00f,LocalDate.of(2013,Month.FEBRUARY,2)));
modelMap.addAttribute("programmers",programmerList);
return "markerShow";
}
}
```
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>freemarker模板引擎</title>
</head>
<body>
<ul>
<#list programmers as programmer>
<li>姓名: ${programmer.name} 年龄: ${programmer.age}</li>
</#list>
</ul>
</body>
</html>
```
#### 4.2 thymeleaf
```java
/**
* @author : heibaiying
* @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
*/
@Controller
@RequestMapping("thymeleaf")
public class ThymeleafController {
@RequestMapping("show")
private String programmerShow(ModelMap modelMap) {
List<Programmer> programmerList = new ArrayList<>();
programmerList.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
programmerList.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
modelMap.addAttribute("programmers", programmerList);
return "leafShow";
}
}
```
```html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>thymeleaf模板引擎</title>
</head>
<body>
<ul th:each="programmer:${programmers}">
<li>
姓名:<span th:text="${programmer.name}"></span>
薪水:<span th:text="${programmer.salary}"></span>
</li>
</ul>
</body>
</html>
```
#### 4.3 文档说明
freemarker提供了完善的中文文档地址 http://freemarker.foofun.cn/
thymeleaf官方英文文档地址[thymeleaf 3.0.11RELEASE](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf)
## 一、说明
#### 1.1 项目结构说明
1. 本项目搭建一个简单的hello spring 的 web工程简单说明spring-boot 的开箱即用的特性;
2. 模板引擎采用freemaker 和 thymeleaf 作为示例分别对应模板文件makershow.ftl 和 leafShow.html
3. spring boot 2.x 默认是不支持jsp的需要额外的配置关于使用jsp的整合可以参考[spring-boot-jsp](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-jsp)项目。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-base.png"/> </div>
#### 1.2 项目依赖
导入相关的starter(启动器)
```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-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heibaiying</groupId>
<artifactId>spring-boot-base</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-base</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web 启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok 插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--测试相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!--Spring Boot的Maven插件Spring Boot Maven plugin能够以Maven的方式为应用提供Spring Boot的支持-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
1. spring boot 项目默认继承自spring-boot-starter-parent而spring-boot-starter-parent继承自spring-boot-dependencies, spring-boot-dependencies中定义了关于spring boot 依赖的各种jar包的版本是spring boot 的版本管理中心。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dependencies.png"/> </div>
2. 关于spring boot 2.x官方支持的所有starter 可以参见官方文档 [Table 13.1. Spring Boot application starters](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#using-boot-starter)
## 二、spring boot 主启动类
如果采用IDEA 或者 Spring Tool Suite (STS) 等开发工具创建的spring boot 工程,会默认创建启动类,如果没有创建,需要手动创建启动类
```java
package com.heibaiying.springbootbase;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootBaseApplication {
// 启动类默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
public static void main(String[] args) {
SpringApplication.run(SpringBootBaseApplication.class, args);
}
}
```
@SpringBootApplication 注解是一个复合注解,里面包含了@ComponentScan注解,默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
```java
@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 {
...
}
```
## 三、开箱即用的web工程
在springbootBaseApplication.java 的同级目录创建controller文件夹并在其中创建RestfulController.java,启动项目访问localhost:8080/restful/programmers 即可看到项目搭建成功。
```java
/**
* @author : heibaiying
* @description : restful 控制器
*/
@RestController
@RequestMapping("restful")
public class RestfulController {
@GetMapping("programmers")
private List<Programmer> getProgrammers() {
List<Programmer> programmers = new ArrayList<>();
programmers.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
programmers.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
return programmers;
}
}
```
这里之所以能够开箱即用是因为我们在项目中导入spring-boot-starter-web启动器@SpringBootApplication 复合注解中默认开启了@EnableAutoConfiguration注解允许开启自动化配置spring在检查导入starter-web的依赖后就会开启web的自动化配置。
## 四、模板引擎
这里我们在一个项目中同时导入了freemaker 和 thymeleaf的starter虽然并不推荐但是在同一个项目中是可以混用这两种模板引擎的
#### 4.1 freemarker
```java
/**
* @author : heibaiying
* @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
*/
@Controller
@RequestMapping("freemarker")
public class FreeMarkerController {
@RequestMapping("show")
private String programmerShow(ModelMap modelMap){
List<Programmer> programmerList=new ArrayList<>();
programmerList.add(new Programmer("xiaoming",12,100000.00f,LocalDate.of(2019,Month.AUGUST,2)));
programmerList.add(new Programmer("xiaohong",23,900000.00f,LocalDate.of(2013,Month.FEBRUARY,2)));
modelMap.addAttribute("programmers",programmerList);
return "markerShow";
}
}
```
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>freemarker模板引擎</title>
</head>
<body>
<ul>
<#list programmers as programmer>
<li>姓名: ${programmer.name} 年龄: ${programmer.age}</li>
</#list>
</ul>
</body>
</html>
```
#### 4.2 thymeleaf
```java
/**
* @author : heibaiying
* @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
*/
@Controller
@RequestMapping("thymeleaf")
public class ThymeleafController {
@RequestMapping("show")
private String programmerShow(ModelMap modelMap) {
List<Programmer> programmerList = new ArrayList<>();
programmerList.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
programmerList.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
modelMap.addAttribute("programmers", programmerList);
return "leafShow";
}
}
```
```html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>thymeleaf模板引擎</title>
</head>
<body>
<ul th:each="programmer:${programmers}">
<li>
姓名:<span th:text="${programmer.name}"></span>
薪水:<span th:text="${programmer.salary}"></span>
</li>
</ul>
</body>
</html>
```
#### 4.3 文档说明
freemarker提供了完善的中文文档地址 http://freemarker.foofun.cn/
thymeleaf官方英文文档地址[thymeleaf 3.0.11RELEASE](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf)
注:我在本仓库中也上传了一份[thymeleaf中文文档gangzi828(刘明刚 译)](https://github.com/heibaiying/spring-samples-for-all/tree/master/referenced%20documents)翻译的版本为3.0.5RELEASE

View File

@ -1,193 +1,203 @@
# spring boot data jpa
## 一、说明
#### 1.1 项目结构
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-data-jpa.png"/> </div>
#### 1.2 项目主要依赖
```xml
<dependencies>
<!-- data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!--单元测试包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
```
## 二、data jpa 的使用
#### 2.1 在application.yml 中配置数据源
```yaml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
#Hibernate默认创建的表是myisam引擎可以用以下方式指定为使用innodb创建表
database-platform: org.hibernate.dialect.MySQL57Dialect
show-sql: true
```
#### 2.2 新建查询接口
```java
/**
* @author : heibaiying
* @description : 查询接口继承自CrudRepository,CrudRepository 默认定义了部分增删改查方法
*/
public interface ProgRepository extends CrudRepository<Programmer, Integer> {
/*
* 方法名遵循命名规范的查询 更多命名规范可以参考官方文档所列出的这张表格
*/
List<Programmer> findAllByName(String name);
/*
*分页排序查询
*/
Page<Programmer> findAll(Pageable pageable);
# spring boot data jpa
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构">1.1 项目结构</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
<a href="#二data-jpa-的使用">二、data jpa 的使用</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-在applicationyml-中配置数据源">2.1 在application.yml 中配置数据源</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-新建查询接口">2.2 新建查询接口</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23--测试类">2.3 测试类</a><br/>
## 正文<br/>
/*
* 占位符查询
*/
@Query(value = "select u from Programmer u where u.name = ?1 or u.salary = ?2")
List<Programmer> findByConditionAndOrder(String name, float salary, Sort.Order order);
/*
* 传入参数名称
*/
@Query("select u from Programmer u where u.name = :name or u.age = :age")
Programmer findByParam(@Param("name") String name,
@Param("age") int age);
}
```
关于查询方法遵循的命名规范和关键词见下表:
| Keyword | Sample | JPQL snippet |
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| `And` | `findByLastnameAndFirstname` | `… where x.lastname = ?1 and x.firstname = ?2` |
| `Or` | `findByLastnameOrFirstname` | `… where x.lastname = ?1 or x.firstname = ?2` |
| `Is,Equals` | `findByFirstname`,`findByFirstnameIs`,<br>`findByFirstnameEquals` | `… where x.firstname = ?1` |
| `Between` | `findByStartDateBetween` | `… where x.startDate between ?1 and ?2` |
| `LessThan` | `findByAgeLessThan` | `… where x.age < ?1` |
| `LessThanEqual` | `findByAgeLessThanEqual` | `… where x.age <= ?1` |
| `GreaterThan` | `findByAgeGreaterThan` | `… where x.age > ?1` |
| `GreaterThanEqual` | `findByAgeGreaterThanEqual` | `… where x.age >= ?1` |
| `After` | `findByStartDateAfter` | `… where x.startDate > ?1` |
| `Before` | `findByStartDateBefore` | `… where x.startDate < ?1` |
| `IsNull` | `findByAgeIsNull` | `… where x.age is null` |
| `IsNotNull,NotNull` | `findByAge(Is)NotNull` | `… where x.age not null` |
| `Like` | `findByFirstnameLike` | `… where x.firstname like ?1` |
| `NotLike` | `findByFirstnameNotLike` | `… where x.firstname not like ?1` |
| `StartingWith` | `findByFirstnameStartingWith` | `… where x.firstname like ?1`(parameter bound with appended `%`) |
| `EndingWith` | `findByFirstnameEndingWith` | `… where x.firstname like ?1`(parameter bound with prepended `%`) |
| `Containing` | `findByFirstnameContaining` | `… where x.firstname like ?1`(parameter bound wrapped in `%`) |
| `OrderBy` | `findByAgeOrderByLastnameDesc` | `… where x.age = ?1 order by x.lastname desc` |
| `Not` | `findByLastnameNot` | `… where x.lastname <> ?1` |
| `In` | `findByAgeIn(Collection<Age> ages)` | `… where x.age in ?1` |
| `NotIn` | `findByAgeNotIn(Collection<Age> ages)` | `… where x.age not in ?1` |
| `True` | `findByActiveTrue()` | `… where x.active = true` |
| `False` | `findByActiveFalse()` | `… where x.active = false` |
| `IgnoreCase` | `findByFirstnameIgnoreCase` | `… where UPPER(x.firstame) = UPPER(?1)` |
#### 2.3 测试类
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class DataJPATests {
@Autowired
private ProgRepository repository;
/**
* 保存数据测试
*/
@Test
public void save() {
// 保存单条数据
repository.save(new Programmer("pro01", 12, 2121.34f, new Date()));
// 保存多条数据
List<Programmer> programmers = new ArrayList<>();
programmers.add(new Programmer("pro02", 22, 3221.34f, new Date()));
programmers.add(new Programmer("pro03", 32, 3321.34f, new Date()));
programmers.add(new Programmer("pro04", 44, 4561.34f, new Date()));
programmers.add(new Programmer("pro01", 44, 4561.34f, new Date()));
repository.saveAll(programmers);
}
/**
* 查询数据测试
*/
@Test
public void get() {
// 遵循命名规范的查询
List<Programmer> programmers = repository.findAllByName("pro01");
programmers.forEach(System.out::println);
// 传入参数名称
Programmer param = repository.findByParam("pro02", 22);
System.out.println("findByParam:" + param);
// 占位符查询
List<Programmer> byCondition = repository.findByConditionAndOrder("pro03", 3321.34f, Sort.Order.asc("salary"));
System.out.println("byCondition:" + byCondition);
//条件与分页查询 需要注意的是这里的页数是从第0页开始计算的
Page<Programmer> page = repository.findAll(PageRequest.of(0, 10, Sort.Direction.DESC, "salary"));
page.get().forEach(System.out::println);
}
/**
* 更新数据测试
*/
@Test
public void update() {
// 保存主键相同的数据就认为是更新操作
repository.save(new Programmer(1, "updatePro01", 12, 2121.34f, new Date()));
Optional<Programmer> programmer = repository.findById(1);
Assert.assertEquals(programmer.get().getName(), "updatePro01");
}
/**
* 删除数据测试
*/
@Test
public void delete() {
Optional<Programmer> programmer = repository.findById(2);
if (programmer.isPresent()) {
repository.deleteById(2);
}
Assert.assertFalse(programmer.isPresent());
}
}
```
## 一、说明
#### 1.1 项目结构
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-data-jpa.png"/> </div>
#### 1.2 项目主要依赖
```xml
<dependencies>
<!-- data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!--单元测试包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
```
## 二、data jpa 的使用
#### 2.1 在application.yml 中配置数据源
```yaml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
#Hibernate默认创建的表是myisam引擎可以用以下方式指定为使用innodb创建表
database-platform: org.hibernate.dialect.MySQL57Dialect
show-sql: true
```
#### 2.2 新建查询接口
```java
/**
* @author : heibaiying
* @description : 查询接口继承自CrudRepository,CrudRepository 默认定义了部分增删改查方法
*/
public interface ProgRepository extends CrudRepository<Programmer, Integer> {
/*
* 方法名遵循命名规范的查询 更多命名规范可以参考官方文档所列出的这张表格
*/
List<Programmer> findAllByName(String name);
/*
*分页排序查询
*/
Page<Programmer> findAll(Pageable pageable);
/*
* 占位符查询
*/
@Query(value = "select u from Programmer u where u.name = ?1 or u.salary = ?2")
List<Programmer> findByConditionAndOrder(String name, float salary, Sort.Order order);
/*
* 传入参数名称
*/
@Query("select u from Programmer u where u.name = :name or u.age = :age")
Programmer findByParam(@Param("name") String name,
@Param("age") int age);
}
```
关于查询方法遵循的命名规范和关键词见下表:
| Keyword | Sample | JPQL snippet |
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| `And` | `findByLastnameAndFirstname` | `… where x.lastname = ?1 and x.firstname = ?2` |
| `Or` | `findByLastnameOrFirstname` | `… where x.lastname = ?1 or x.firstname = ?2` |
| `Is,Equals` | `findByFirstname`,`findByFirstnameIs`,<br>`findByFirstnameEquals` | `… where x.firstname = ?1` |
| `Between` | `findByStartDateBetween` | `… where x.startDate between ?1 and ?2` |
| `LessThan` | `findByAgeLessThan` | `… where x.age < ?1` |
| `LessThanEqual` | `findByAgeLessThanEqual` | `… where x.age <= ?1` |
| `GreaterThan` | `findByAgeGreaterThan` | `… where x.age > ?1` |
| `GreaterThanEqual` | `findByAgeGreaterThanEqual` | `… where x.age >= ?1` |
| `After` | `findByStartDateAfter` | `… where x.startDate > ?1` |
| `Before` | `findByStartDateBefore` | `… where x.startDate < ?1` |
| `IsNull` | `findByAgeIsNull` | `… where x.age is null` |
| `IsNotNull,NotNull` | `findByAge(Is)NotNull` | `… where x.age not null` |
| `Like` | `findByFirstnameLike` | `… where x.firstname like ?1` |
| `NotLike` | `findByFirstnameNotLike` | `… where x.firstname not like ?1` |
| `StartingWith` | `findByFirstnameStartingWith` | `… where x.firstname like ?1`(parameter bound with appended `%`) |
| `EndingWith` | `findByFirstnameEndingWith` | `… where x.firstname like ?1`(parameter bound with prepended `%`) |
| `Containing` | `findByFirstnameContaining` | `… where x.firstname like ?1`(parameter bound wrapped in `%`) |
| `OrderBy` | `findByAgeOrderByLastnameDesc` | `… where x.age = ?1 order by x.lastname desc` |
| `Not` | `findByLastnameNot` | `… where x.lastname <> ?1` |
| `In` | `findByAgeIn(Collection<Age> ages)` | `… where x.age in ?1` |
| `NotIn` | `findByAgeNotIn(Collection<Age> ages)` | `… where x.age not in ?1` |
| `True` | `findByActiveTrue()` | `… where x.active = true` |
| `False` | `findByActiveFalse()` | `… where x.active = false` |
| `IgnoreCase` | `findByFirstnameIgnoreCase` | `… where UPPER(x.firstame) = UPPER(?1)` |
#### 2.3 测试类
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class DataJPATests {
@Autowired
private ProgRepository repository;
/**
* 保存数据测试
*/
@Test
public void save() {
// 保存单条数据
repository.save(new Programmer("pro01", 12, 2121.34f, new Date()));
// 保存多条数据
List<Programmer> programmers = new ArrayList<>();
programmers.add(new Programmer("pro02", 22, 3221.34f, new Date()));
programmers.add(new Programmer("pro03", 32, 3321.34f, new Date()));
programmers.add(new Programmer("pro04", 44, 4561.34f, new Date()));
programmers.add(new Programmer("pro01", 44, 4561.34f, new Date()));
repository.saveAll(programmers);
}
/**
* 查询数据测试
*/
@Test
public void get() {
// 遵循命名规范的查询
List<Programmer> programmers = repository.findAllByName("pro01");
programmers.forEach(System.out::println);
// 传入参数名称
Programmer param = repository.findByParam("pro02", 22);
System.out.println("findByParam:" + param);
// 占位符查询
List<Programmer> byCondition = repository.findByConditionAndOrder("pro03", 3321.34f, Sort.Order.asc("salary"));
System.out.println("byCondition:" + byCondition);
//条件与分页查询 需要注意的是这里的页数是从第0页开始计算的
Page<Programmer> page = repository.findAll(PageRequest.of(0, 10, Sort.Direction.DESC, "salary"));
page.get().forEach(System.out::println);
}
/**
* 更新数据测试
*/
@Test
public void update() {
// 保存主键相同的数据就认为是更新操作
repository.save(new Programmer(1, "updatePro01", 12, 2121.34f, new Date()));
Optional<Programmer> programmer = repository.findById(1);
Assert.assertEquals(programmer.get().getName(), "updatePro01");
}
/**
* 删除数据测试
*/
@Test
public void delete() {
Optional<Programmer> programmer = repository.findById(2);
if (programmer.isPresent()) {
repository.deleteById(2);
}
Assert.assertFalse(programmer.isPresent());
}
}
```

View File

@ -1,196 +1,207 @@
# spring boot 整合 druid+mybatis
## 一、说明
#### 1.1 项目结构
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下
2. 为了使用druid控制台的功能项目以web的方式构建。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid-mybatis.png"/> </div>
#### 1.2 项目主要依赖
需要说明的是按照spring 官方对于自定义的starter 命名规范的推荐:
- 官方的starter命名spring-boot-starter-XXXX
- 其他第三方starter命名XXXX-spring-boot-starte
所以mybatis的starter命名为mybatis-spring-boot-starter如果有自定义starter需求也需要按照此命名规则进行命名。
```xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!--druid 依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
```
spring boot 与 mybatis 版本的对应关系:
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
| --------------------------- | ------------------------------------------------------------ | ------------- |
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
# spring boot 整合 druid+mybatis
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构">1.1 项目结构</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
<a href="#二整合-druid-+-mybatis">二、整合 druid + mybatis</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-在applicationyml-中配置数据源">2.1 在application.yml 中配置数据源</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22--新建查询接口和controller">2.2 新建查询接口和controller</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-关于druid监控数据的外部化调用">2.3 关于druid监控数据的外部化调用</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#24-druid-控制台的使用默认访问地址-http//localhost8080/druid/loginhtml">2.4 druid 控制台的使用,默认访问地址 http://localhost:8080/druid/login.html</a><br/>
## 正文<br/>
## 二、整合 druid + mybatis
#### 2.1 在application.yml 中配置数据源
本用例采用druid作为数据库连接池虽然druid性能略逊于Hikari但是提供了更为全面的监控管理可以按照实际需求选用druid或者Hikari。关于Hikari数据源的配置可以参考[spring-boot-mybatis项目](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mybatis)
```yaml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 使用 druid 作为连接池 更多配置的说明可以参见 druid starter 中文文档 https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法或者第一次getConnection时
initialSize: 5
# 最小连接池数量
minIdle: 5
# 最大连接池数量
maxActive: 10
# 获取连接时最大等待时间单位毫秒。配置了maxWait之后缺省启用公平锁并发效率会有所下降如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
maxWait: 60000
# Destroy线程会检测连接的间隔时间如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
timeBetweenEvictionRunsMillis: 60000
# 连接保持空闲而不被驱逐的最小时间
minEvictableIdleTimeMillis: 300000
# 用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL
validationQuery: SELECT 1
# 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
testWhileIdle: true
# 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
testOnBorrow: false
# 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
testOnReturn: false
# 是否自动回收超时连接
removeAbandoned: true
# 超时时间(以秒数为单位)
remove-abandoned-timeout: 180
# druid 监控的配置 如果不使用 druid 的监控功能的话 以下配置就不是必须的
# 本项目监控台访问地址: http://localhost:8080/druid/login.html
# WebStatFilter用于采集web-jdbc关联监控的数据。
# 更多配置可参见: https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter
web-stat-filter:
# 是否开启 WebStatFilter 默认是true
enabled: true
# 需要拦截的url
url-pattern: /*
# 排除静态资源的请求
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
# Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。
# 更多配置可参见:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
stat-view-servlet:
#是否启用StatViewServlet 默认值true
enabled: true
# 需要拦截的url
url-pattern: /druid/*
# 允许清空统计数据
reset-enable: true
login-username: druid
login-password: druid
# mybatis 相关配置
mybatis:
configuration:
# 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
# oracle数据库建议配置为JdbcType.NULL, 默认是Other
jdbc-type-for-null: 'null'
# 是否打印sql语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
```
#### 2.2 新建查询接口和controller
```java
@Mapper
public interface ProgrammerDao {
@Select("select * from programmer")
List<Programmer> selectAll();
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
void save(Programmer programmer);
@Select("select * from programmer where name = #{id}")
Programmer selectById(int id);
@Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
int modify(Programmer programmer);
@Delete(" delete from programmer where id = #{id}")
void delete(int id);
}
```
```xml
@RestController
public class ProgrammerController {
@Autowired
private ProgrammerDao programmerDao;
@GetMapping("/programmers")
public List<Programmer> get() {
return programmerDao.selectAll();
}
}
```
#### 2.3 关于druid监控数据的外部化调用
```java
/**
* @author : heibaiying
* @description :在 Spring Boot 中可以通过 HTTP 接口将 Druid 监控数据以JSON 的形式暴露出去,
* 实际使用中你可以根据你的需要自由地对监控数据、暴露方式进行扩展。
*/
@RestController
public class DruidStatController {
@GetMapping("/stat")
public Object druidStat() {
// DruidStatManagerFacade#getDataSourceStatDataList 该方法可以获取所有数据源的监控数据
return DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
}
}
```
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/druid-status.png"/> </div>
#### 2.4 druid 控制台的使用,默认访问地址 http://localhost:8080/druid/login.html
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid%20%E6%8E%A7%E5%88%B6%E5%8F%B0.png"/> </div>
## 一、说明
#### 1.1 项目结构
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下
2. 为了使用druid控制台的功能项目以web的方式构建。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid-mybatis.png"/> </div>
#### 1.2 项目主要依赖
需要说明的是按照spring 官方对于自定义的starter 命名规范的推荐:
- 官方的starter命名spring-boot-starter-XXXX
- 其他第三方starter命名XXXX-spring-boot-starte
所以mybatis的starter命名为mybatis-spring-boot-starter如果有自定义starter需求也需要按照此命名规则进行命名。
```xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!--druid 依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
```
spring boot 与 mybatis 版本的对应关系:
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
| --------------------------- | ------------------------------------------------------------ | ------------- |
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
## 二、整合 druid + mybatis
#### 2.1 在application.yml 中配置数据源
本用例采用druid作为数据库连接池虽然druid性能略逊于Hikari但是提供了更为全面的监控管理可以按照实际需求选用druid或者Hikari。关于Hikari数据源的配置可以参考[spring-boot-mybatis项目](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mybatis)
```yaml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 使用 druid 作为连接池 更多配置的说明可以参见 druid starter 中文文档 https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法或者第一次getConnection时
initialSize: 5
# 最小连接池数量
minIdle: 5
# 最大连接池数量
maxActive: 10
# 获取连接时最大等待时间单位毫秒。配置了maxWait之后缺省启用公平锁并发效率会有所下降如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
maxWait: 60000
# Destroy线程会检测连接的间隔时间如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
timeBetweenEvictionRunsMillis: 60000
# 连接保持空闲而不被驱逐的最小时间
minEvictableIdleTimeMillis: 300000
# 用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL
validationQuery: SELECT 1
# 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
testWhileIdle: true
# 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
testOnBorrow: false
# 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
testOnReturn: false
# 是否自动回收超时连接
removeAbandoned: true
# 超时时间(以秒数为单位)
remove-abandoned-timeout: 180
# druid 监控的配置 如果不使用 druid 的监控功能的话 以下配置就不是必须的
# 本项目监控台访问地址: http://localhost:8080/druid/login.html
# WebStatFilter用于采集web-jdbc关联监控的数据。
# 更多配置可参见: https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter
web-stat-filter:
# 是否开启 WebStatFilter 默认是true
enabled: true
# 需要拦截的url
url-pattern: /*
# 排除静态资源的请求
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
# Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。
# 更多配置可参见:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
stat-view-servlet:
#是否启用StatViewServlet 默认值true
enabled: true
# 需要拦截的url
url-pattern: /druid/*
# 允许清空统计数据
reset-enable: true
login-username: druid
login-password: druid
# mybatis 相关配置
mybatis:
configuration:
# 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
# oracle数据库建议配置为JdbcType.NULL, 默认是Other
jdbc-type-for-null: 'null'
# 是否打印sql语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
```
#### 2.2 新建查询接口和controller
```java
@Mapper
public interface ProgrammerDao {
@Select("select * from programmer")
List<Programmer> selectAll();
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
void save(Programmer programmer);
@Select("select * from programmer where name = #{id}")
Programmer selectById(int id);
@Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
int modify(Programmer programmer);
@Delete(" delete from programmer where id = #{id}")
void delete(int id);
}
```
```xml
@RestController
public class ProgrammerController {
@Autowired
private ProgrammerDao programmerDao;
@GetMapping("/programmers")
public List<Programmer> get() {
return programmerDao.selectAll();
}
}
```
#### 2.3 关于druid监控数据的外部化调用
```java
/**
* @author : heibaiying
* @description :在 Spring Boot 中可以通过 HTTP 接口将 Druid 监控数据以JSON 的形式暴露出去,
* 实际使用中你可以根据你的需要自由地对监控数据、暴露方式进行扩展。
*/
@RestController
public class DruidStatController {
@GetMapping("/stat")
public Object druidStat() {
// DruidStatManagerFacade#getDataSourceStatDataList 该方法可以获取所有数据源的监控数据
return DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
}
}
```
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/druid-status.png"/> </div>
#### 2.4 druid 控制台的使用,默认访问地址 http://localhost:8080/druid/login.html
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid%20%E6%8E%A7%E5%88%B6%E5%8F%B0.png"/> </div>

View File

@ -1,348 +1,362 @@
# spring boot 整合 dubbo
## 一、 项目结构说明
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中所以项目采用maven多模块的构建方式在spring-boot-dubbo下构建三个子模块
1. boot-dubbo-common 是公共模块用于存放公共的接口和bean,被boot-dubbo-provider和boot-dubbo-consumer在pom.xml中引用
2. boot-dubbo-provider 是服务的提供者,提供商品的查询服务;
3. boot-dubbo-consumer是服务的消费者调用provider提供的查询服务。
1.2 本项目dubbo的搭建采用zookeeper作为注册中心 关于zookeeper的安装和基本操作可以参见我的手记 [Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dubbo.png"/> </div>
## 二、关键依赖
在父工程的项目中统一导入依赖dubbo的starter父工程的pom.xml如下
```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>
<packaging>pom</packaging>
<modules>
<module>boot-dubbo-common</module>
<module>boot-dubbo-consumer</module>
<module>boot-dubbo-provider</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heibaiying</groupId>
<artifactId>spring-boot-dubbo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-dubbo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入dubbo start依赖-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
</project>
```
## 三、公共模块boot-dubbo-common
- api 下为公共的调用接口;
- bean 下为公共的实体类。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-common.png"/> </div>
## 四、 服务提供者boot-dubbo-provider
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-provider.png"/> </div>
#### 4.1 提供方配置
```yaml
dubbo:
application:
name: boot-duboo-provider
# 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心并且在start依赖中引入了zookeeper的java客户端Curator
registry:
protocol: zookeeper
address: 127.0.0.1:2181
protocol.name: dubbo
```
#### 4.2 使用注解@Service暴露服务
需要注意的是这里的@Service注解不是spring的注解而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
```java
package com.heibaiying.dubboprovider.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.heibaiying.api.IProductService;
import com.heibaiying.bean.Product;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author : heibaiying
* @description : 产品提供接口实现类
*/
@Service(timeout = 5000)
public class ProductService implements IProductService {
private static List<Product> productList = new ArrayList<>();
static {
for (int i = 0; i < 20; i++) {
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
}
}
public Product queryProductById(int id) {
for (Product product : productList) {
if (product.getId() == id) {
return product;
}
}
return null;
}
public List<Product> queryAllProducts() {
return productList;
}
}
```
## 五、服务消费者boot-dubbo-consumer
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-consumer1.png"/> </div>
#### 1.消费方的配置
```yaml
dubbo:
application:
name: boot-duboo-provider
# 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心并且在start依赖中引入了zookeeper的java客户端Curator
registry:
protocol: zookeeper
address: 127.0.0.1:2181
protocol.name: dubbo
# 关闭所有服务的启动时检查 (没有提供者时报错)视实际情况设置
consumer:
check: false
server:
port: 8090
```
#### 2.使用注解@Reference引用远程服务
```java
package com.heibaiying.dubboconsumer.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.heibaiying.api.IProductService;
import com.heibaiying.bean.Product;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("sell")
public class SellController {
// dubbo远程引用注解
@Reference
private IProductService productService;
@RequestMapping
public String productList(Model model) {
List<Product> products = productService.queryAllProducts();
model.addAttribute("products", products);
return "products";
}
@RequestMapping("product/{id}")
public String productDetail(@PathVariable int id, Model model) {
Product product = productService.queryProductById(id);
model.addAttribute("product", product);
return "product";
}
}
```
## 六、项目构建的说明
因为在项目中consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
```shell
mvn install -Dmaven.test.skip = true
```
consumer中 pom.xml如下
```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>
<artifactId>spring-boot-dubbo</artifactId>
<groupId>com.heibaiying</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>boot-dubbo-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-dubbo-consumer</name>
<description>dubbo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<!--引入对公共模块的依赖-->
<dependencies>
<dependency>
<groupId>com.heibaiying</groupId>
<artifactId>boot-dubbo-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
provider中 pom.xml如下
```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>
<artifactId>spring-boot-dubbo</artifactId>
<groupId>com.heibaiying</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>boot-dubbo-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-dubbo-provider</name>
<description>dubbo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<!--引入对公共模块的依赖-->
<dependencies>
<dependency>
<groupId>com.heibaiying</groupId>
<artifactId>boot-dubbo-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
## 七、关于dubbo新版本管理控制台的安装说明
安装:
```sh
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
cd /var/tmp/dubbo-ops
mvn clean package
```
配置:
```sh
配置文件为:
dubbo-admin-backend/src/main/resources/application.properties
主要的配置有 默认的配置就是127.0.0.1:2181
dubbo.registry.address=zookeeper://127.0.0.1:2181
```
启动:
```sh
mvn --projects dubbo-admin-backend spring-boot:run
```
访问:
```
http://127.0.0.1:8080
# spring boot 整合 dubbo
## 目录<br/>
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
<a href="#二关键依赖">二、关键依赖</a><br/>
<a href="#三公共模块boot-dubbo-common">三、公共模块boot-dubbo-common</a><br/>
<a href="#四-服务提供者boot-dubbo-provider">四、 服务提供者boot-dubbo-provider</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-提供方配置">4.1 提供方配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42--使用注解@Service暴露服务">4.2 使用注解@Service暴露服务</a><br/>
<a href="#五服务消费者boot-dubbo-consumer">五、服务消费者boot-dubbo-consumer</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#1消费方的配置">1.消费方的配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2使用注解@Reference引用远程服务">2.使用注解@Reference引用远程服务</a><br/>
<a href="#六项目构建的说明">六、项目构建的说明</a><br/>
<a href="#七关于dubbo新版本管理控制台的安装说明">七、关于dubbo新版本管理控制台的安装说明</a><br/>
## 正文<br/>
## 一、 项目结构说明
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中所以项目采用maven多模块的构建方式在spring-boot-dubbo下构建三个子模块
1. boot-dubbo-common 是公共模块用于存放公共的接口和bean,被boot-dubbo-provider和boot-dubbo-consumer在pom.xml中引用
2. boot-dubbo-provider 是服务的提供者,提供商品的查询服务;
3. boot-dubbo-consumer是服务的消费者调用provider提供的查询服务。
1.2 本项目dubbo的搭建采用zookeeper作为注册中心 关于zookeeper的安装和基本操作可以参见我的手记 [Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dubbo.png"/> </div>
## 二、关键依赖
在父工程的项目中统一导入依赖dubbo的starter父工程的pom.xml如下
```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>
<packaging>pom</packaging>
<modules>
<module>boot-dubbo-common</module>
<module>boot-dubbo-consumer</module>
<module>boot-dubbo-provider</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heibaiying</groupId>
<artifactId>spring-boot-dubbo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-dubbo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入dubbo start依赖-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
</project>
```
## 三、公共模块boot-dubbo-common
- api 下为公共的调用接口;
- bean 下为公共的实体类。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-common.png"/> </div>
## 四、 服务提供者boot-dubbo-provider
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-provider.png"/> </div>
#### 4.1 提供方配置
```yaml
dubbo:
application:
name: boot-duboo-provider
# 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心并且在start依赖中引入了zookeeper的java客户端Curator
registry:
protocol: zookeeper
address: 127.0.0.1:2181
protocol.name: dubbo
```
#### 4.2 使用注解@Service暴露服务
需要注意的是这里的@Service注解不是spring的注解而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
```java
package com.heibaiying.dubboprovider.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.heibaiying.api.IProductService;
import com.heibaiying.bean.Product;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author : heibaiying
* @description : 产品提供接口实现类
*/
@Service(timeout = 5000)
public class ProductService implements IProductService {
private static List<Product> productList = new ArrayList<>();
static {
for (int i = 0; i < 20; i++) {
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
}
}
public Product queryProductById(int id) {
for (Product product : productList) {
if (product.getId() == id) {
return product;
}
}
return null;
}
public List<Product> queryAllProducts() {
return productList;
}
}
```
## 五、服务消费者boot-dubbo-consumer
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-consumer1.png"/> </div>
#### 1.消费方的配置
```yaml
dubbo:
application:
name: boot-duboo-provider
# 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心并且在start依赖中引入了zookeeper的java客户端Curator
registry:
protocol: zookeeper
address: 127.0.0.1:2181
protocol.name: dubbo
# 关闭所有服务的启动时检查 (没有提供者时报错)视实际情况设置
consumer:
check: false
server:
port: 8090
```
#### 2.使用注解@Reference引用远程服务
```java
package com.heibaiying.dubboconsumer.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.heibaiying.api.IProductService;
import com.heibaiying.bean.Product;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("sell")
public class SellController {
// dubbo远程引用注解
@Reference
private IProductService productService;
@RequestMapping
public String productList(Model model) {
List<Product> products = productService.queryAllProducts();
model.addAttribute("products", products);
return "products";
}
@RequestMapping("product/{id}")
public String productDetail(@PathVariable int id, Model model) {
Product product = productService.queryProductById(id);
model.addAttribute("product", product);
return "product";
}
}
```
## 六、项目构建的说明
因为在项目中consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
```shell
mvn install -Dmaven.test.skip = true
```
consumer中 pom.xml如下
```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>
<artifactId>spring-boot-dubbo</artifactId>
<groupId>com.heibaiying</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>boot-dubbo-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-dubbo-consumer</name>
<description>dubbo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<!--引入对公共模块的依赖-->
<dependencies>
<dependency>
<groupId>com.heibaiying</groupId>
<artifactId>boot-dubbo-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
provider中 pom.xml如下
```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>
<artifactId>spring-boot-dubbo</artifactId>
<groupId>com.heibaiying</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>boot-dubbo-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-dubbo-provider</name>
<description>dubbo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<!--引入对公共模块的依赖-->
<dependencies>
<dependency>
<groupId>com.heibaiying</groupId>
<artifactId>boot-dubbo-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
## 七、关于dubbo新版本管理控制台的安装说明
安装:
```sh
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
cd /var/tmp/dubbo-ops
mvn clean package
```
配置:
```sh
配置文件为:
dubbo-admin-backend/src/main/resources/application.properties
主要的配置有 默认的配置就是127.0.0.1:2181
dubbo.registry.address=zookeeper://127.0.0.1:2181
```
启动:
```sh
mvn --projects dubbo-admin-backend spring-boot:run
```
访问:
```
http://127.0.0.1:8080
```

View File

@ -1,84 +1,94 @@
# spring boot 内置容器 整合 jsp
# spring boot 内置容器 整合 jsp
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构">1.1 项目结构</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
<a href="#二整合-jsp">二、整合 jsp</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-导入整合的依赖">2.1 导入整合的依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-在applicationyml-中指定访问视图文件的前缀和后缀">2.2 在application.yml 中指定访问视图文件的前缀和后缀 </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23--新建controller和showjsp-测试整合是否成功">2.3 新建controller和show.jsp 测试整合是否成功</a><br/>
## 正文<br/>
## 一、说明
#### 1.1 项目结构
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-jsp.png"/> </div>
#### 1.2 项目主要依赖
```xml
<!--整合 jsp 依赖包-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!--jsp jstl 标签支持-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
```
## 二、整合 jsp
#### 2.1 导入整合的依赖
```xml
<!--整合 jsp 依赖包-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!--jsp jstl 标签支持-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
```
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀
```yml
spring:
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
```
#### 2.3 新建controller和show.jsp 测试整合是否成功
```java
@Controller
@RequestMapping("index")
public class JspController {
@RequestMapping
public String jsp(Model model){
Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
model.addAttribute("programmer",programmer);
return "show";
}
}
```
```jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>programmer</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
</head>
<body>
<ul>
<li>姓名: ${programmer.name}</li>
<li>年龄: ${programmer.age}</li>
</ul>
</body>
</html>
```
## 一、说明
#### 1.1 项目结构
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-jsp.png"/> </div>
#### 1.2 项目主要依赖
```xml
<!--整合 jsp 依赖包-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!--jsp jstl 标签支持-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
```
## 二、整合 jsp
#### 2.1 导入整合的依赖
```xml
<!--整合 jsp 依赖包-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!--jsp jstl 标签支持-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
```
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀
```yml
spring:
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
```
#### 2.3 新建controller和show.jsp 测试整合是否成功
```java
@Controller
@RequestMapping("index")
public class JspController {
@RequestMapping
public String jsp(Model model){
Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
model.addAttribute("programmer",programmer);
return "show";
}
}
```
```jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>programmer</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
</head>
<body>
<ul>
<li>姓名: ${programmer.name}</li>
<li>年龄: ${programmer.age}</li>
</ul>
</body>
</html>
```

View File

@ -1,373 +1,396 @@
# spring boot 整合 kafka
## 一、kafka的相关概念
### 1.主题和分区
kafka 的消息通过主题进行分类。一个主题可以被分为若干个分区一个分区就是一个提交日志。消息以追加的方式写入分区然后以先入先出的顺序读取。kafka通过分区来实现数据的冗余和伸缩性分区可以分布在不同的服务器上也就是说一个主题可以横跨多个服务器以此来提供比单个服务器更强大的性能类比HDFS分布式文件系统
注意:由于一个主题包含多个分区,因此无法在整个主题范围内保证消息的顺序性,**但可以保证消息在单个分区内的顺序性**。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka主题和分区.png"/> </div>
### 2.分区复制
每个主题被分为若干个分区,每个分区有多个副本。那些副本被保存在 broker 上,每个 broker 可以保存成百上千个属于不同主题和分区的副本。副本有以下两种类型
- 首领副本 每个分区都有一个首领副本 。 为了保证一致性,所有生产者请求和消费者请求都会经过这个副本。
- 跟随者副本 首领以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,它们唯一的任务就是从首领那里复制消息,保持与首领一致的状态。如果首领发生崩渍,其中的一个跟随者会被提升为新首领。
### 3. 生产者
- 默认情况下生产者在把消息均衡地分布到在主题的所有分区上,而并不关心特定消息会被写到那个分区;
- 如果指定消息键,则通过对消息键的散列来实现分区;
- 也可以通过消息键和分区器来实现把消息直接写到指定的分区这个需要自定义分区器需要实现Partitioner 接口并重写其中的partition方法。
### 4. 消费者
消费者是**消费者群组**的一部分。也就是说,会有一个或者多个消费者共同读取一个主题,群组保证每个分区只能被一个消费者使用。
**一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组里面的多个消费者读取。多个消费者群组可以共同读取同一个主题,彼此之间互不影响**。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka消费者.png"/> </div>
### 5.broker和集群
一个独立的kafka服务器被称为broker。broker 接收来自生产者的消息为消息设置偏移量并提交消息到磁盘保存。broker为消费者提供服务对读取分区的请求做出响应返回已经提交到磁盘的消息。
broker是集群的组成部分。每一个集群都有一个broker同时充当了集群控制器的角色自动从集群的活跃成员中选举出来。控制器负责管理工作包括将分区分配给broker和监控broker。**在集群中一个分区从属一个broker,该broker被称为分区的首领**。一个分区可以分配给多个broker,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余如果有一个broker失效其他broker可以接管领导权。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka集群复制.png"/> </div>
更多kafka 的说明可以参考我的个人笔记:[《Kafka权威指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka/%E3%80%8AKafka%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#53-%E5%A4%8D%E5%88%B6)
## 二、项目说明
#### 1.1 项目结构说明
本项目提供kafka发送简单消息、对象消息、和多消费者组消费消息三种情况下的sample。
1. kafkaSimpleConsumer 用于普通消息的监听;
2. kafkaBeanConsumer 用于对象消息监听;
3. kafkaGroupConsumer 用于多消费者组和多消费者对主题分区消息监听的情况。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-kafka.png"/> </div>
#### 1.2 主要依赖
```xml
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
```
## 二、 整合 kafka
#### 2.1 kafka基本配置
```yaml
spring:
kafka:
# 以逗号分隔的地址列表用于建立与Kafka集群的初始连接(kafka 默认的端口号为9092)
bootstrap-servers: 127.0.0.1:9092
producer:
# 发生错误后,消息重发的次数。
retries: 0
#当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
batch-size: 16384
# 设置生产者内存缓冲区的大小。
buffer-memory: 33554432
# 键的序列化方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
# 值的序列化方式
value-serializer: org.apache.kafka.common.serialization.StringSerializer
# acks=0 生产者在成功写入消息之前不会等待任何来自服务器的响应。
# acks=1 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
acks: 1
consumer:
# 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式如1S,1M,2H,5D
auto-commit-interval: 1S
# 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
# latest默认值在偏移量无效的情况下消费者将从最新的记录开始读取数据在消费者启动之后生成的记录
# earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
auto-offset-reset: earliest
# 是否自动提交偏移量默认值是true,为了避免出现重复数据和数据丢失可以把它设置为false,然后手动提交偏移量
enable-auto-commit: true
# 键的反序列化方式
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 值的反序列化方式
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
listener:
# 在侦听器容器中运行的线程数。
concurrency: 5
```
这里需要说明的是:
在spring boot 2.X 版本 auto-commit-interval自动提交的时间间隔采用的是值的类型为Duration Duration 是 jdk 1.8 版本之后引入的类,在其源码中我们可以看到对于其字符串的表达需要符合一定的规范,即数字+单位如下的写法1s 1.5s 0s 0.001S 1h 2d 在yaml 中都是有效的。如果传入无效的字符串则spring boot 在启动阶段解析配置文件的时候就会抛出异常。
```java
public final class Duration
implements TemporalAmount, Comparable<Duration>, Serializable {
/**
* The pattern for parsing.
*/
private static final Pattern PATTERN =
Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" +
"(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?", Pattern.CASE_INSENSITIVE);
........
}
```
#### 2.2 KafkaTemplate实现消息发送
```java
@Component
@Slf4j
public class KafKaCustomrProducer {
@Autowired
private KafkaTemplate kafkaTemplate;
public void sendMessage(String topic, Object object) {
/*
* 这里的ListenableFuture类是spring对java原生Future的扩展增强,是一个泛型接口,用于监听异步方法的回调
* 而对于kafka send 方法返回值而言,这里的泛型所代表的实际类型就是 SendResult<K, V>,而这里K,V的泛型实际上
* 被用于ProducerRecord<K, V> producerRecord,即生产者发送消息的key,value 类型
*/
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, object);
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onFailure(Throwable throwable) {
log.info("发送消息失败:" + throwable.getMessage());
}
@Override
public void onSuccess(SendResult<String, Object> sendResult) {
System.out.println("发送结果:" + sendResult.toString());
}
});
}
}
```
#### 2.3 @KafkaListener注解实现消息的监听
```java
@Component
@Slf4j
public class KafkaSimpleConsumer {
// 简单消费者
@KafkaListener(groupId = "simpleGroup", topics = Topic.SIMPLE)
public void consumer1_1(ConsumerRecord<String, Object> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) {
System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic);
/*
* 如果需要手工提交异步 consumer.commitSync();
* 手工同步提交 consumer.commitAsync()
*/
}
}
```
#### 2.4 测试整合结果
```java
@Slf4j
@RestController
public class SendMsgController {
@Autowired
private KafKaCustomrProducer producer;
@Autowired
private KafkaTemplate kafkaTemplate;
/***
* 发送消息体为基本类型的消息
*/
@GetMapping("sendSimple")
public void sendSimple() {
producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka");
}
}
```
## 三、关于多消费者组的测试
#### 3.1 创建多分区主题
```java
/**
* @author : heibaiying
* @description : kafka配置类
*/
@Configuration
public class KafkaConfig {
@Bean
public NewTopic groupTopic() {
// 指定主题名称,分区数量,和复制因子
return new NewTopic(Topic.GROUP, 10, (short) 2);
}
}
```
#### 3.2 多消费者组对同一主题的监听
1. 消费者1-1 监听主题的 0、1 分区
2. 消费者1-2 监听主题的 2、3 分区
3. 消费者1-3 监听主题的 0、1 分区
4. 消费者2-1 监听主题的所有分区
```java
/**
* @author : heibaiying
* @description : kafka 消费者组
* <p>
* 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。
*/
@Component
@Slf4j
public class KafkaGroupConsumer {
// 分组1 中的消费者1
@KafkaListener(id = "consumer1-1", groupId = "group1", topicPartitions =
{@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
})
public void consumer1_1(ConsumerRecord<String, Object> record) {
System.out.println("consumer1-1 收到消息:" + record.value());
}
// 分组1 中的消费者2
@KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions =
{@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"})
})
public void consumer1_2(ConsumerRecord<String, Object> record) {
System.out.println("consumer1-2 收到消息:" + record.value());
}
// 分组1 中的消费者3
@KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions =
{@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
})
public void consumer1_3(ConsumerRecord<String, Object> record) {
System.out.println("consumer1-3 收到消息:" + record.value());
}
// 分组2 中的消费者
@KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP)
public void consumer2_1(ConsumerRecord<String, Object> record) {
System.err.println("consumer2-1 收到消息:" + record.value());
}
}
```
#### 3.2 发送消息时候指定主题的具体分区
```java
/***
* 多消费者组、组中多消费者对同一主题的消费情况
*/
@GetMapping("sendGroup")
public void sendGroup() {
for (int i = 0; i < 4; i++) {
// 第二个参数指定分区,第三个参数指定消息键 分区优先
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i);
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onFailure(Throwable throwable) {
log.info("发送消息失败:" + throwable.getMessage());
}
@Override
public void onSuccess(SendResult<String, Object> sendResult) {
System.out.println("发送结果:" + sendResult.toString());
}
});
}
}
```
测试结果:
```yaml
# 主要看每次发送结果中的 partition 属性代表四次消息分别发送到了主题的0,1,2,3分区
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=1, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 1, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-1@13]
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=0, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 0, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-0@19]
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=3, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 3, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-3@13]
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=2, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 2, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-2@13]
# 消费者组2 接收情况
consumer2-1 收到消息:hello group 1
consumer2-1 收到消息:hello group 0
consumer2-1 收到消息:hello group 2
consumer2-1 收到消息:hello group 3
# 消费者1-1接收情况
consumer1-1 收到消息:hello group 1
consumer1-1 收到消息:hello group 0
# 消费者1-3接收情况
consumer1-3 收到消息:hello group 1
consumer1-3 收到消息:hello group 0
# 消费者1-2接收情况
consumer1-2 收到消息:hello group 3
consumer1-2 收到消息:hello group 2
```
#### 3.4 测试结果
1. 和kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响;
2. 和kafka原本机制不一样的是这里我们消费者1-1和消费1-3共同属于同一个消费者组并且监听同样的分区按照原本kafka的机制群组保证每个分区只能被同一个消费者组的一个消费者使用但是按照spring的声明方式实现的消息监听这里被两个消费者都监听到了。
## 四、序列化与反序列化
用例采用的是第三方fastjson将实体类序列化为json后发送。实现如下
```java
/***
* 发送消息体为bean的消息
*/
@GetMapping("sendBean")
public void sendBean() {
Programmer programmer = new Programmer("xiaoming", 12, 21212.33f, new Date());
producer.sendMessage(Topic.BEAN, JSON.toJSON(programmer).toString());
}
```
```java
@Component
@Slf4j
public class KafkaBeanConsumer {
@KafkaListener(groupId = "beanGroup",topics = Topic.BEAN)
public void consumer(ConsumerRecord<String, Object> record) {
System.out.println("消费者收到消息:" + JSON.parseObject(record.value().toString(), Programmer.class));
}
}
```
# spring boot 整合 kafka
## 目录<br/>
<a href="#一kafka的相关概念">一、kafka的相关概念</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#1主题和分区">1.主题和分区</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2分区复制">2.分区复制</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#3-生产者">3. 生产者</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#4-消费者">4. 消费者</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#5broker和集群">5.broker和集群</a><br/>
<a href="#二项目说明">二、项目说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-主要依赖">1.2 主要依赖</a><br/>
<a href="#二-整合-kafka">二、 整合 kafka</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-kafka基本配置">2.1 kafka基本配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-KafkaTemplate实现消息发送">2.2 KafkaTemplate实现消息发送</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23--@KafkaListener注解实现消息的监听">2.3 @KafkaListener注解实现消息的监听</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#24-测试整合结果">2.4 测试整合结果</a><br/>
<a href="#三关于多消费者组的测试">三、关于多消费者组的测试</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31--创建多分区主题">3.1 创建多分区主题</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-多消费者组对同一主题的监听">3.2 多消费者组对同一主题的监听</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-发送消息时候指定主题的具体分区">3.2 发送消息时候指定主题的具体分区</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#34-测试结果">3.4 测试结果</a><br/>
<a href="#四序列化与反序列化">四、序列化与反序列化</a><br/>
## 正文<br/>
## 一、kafka的相关概念
### 1.主题和分区
kafka 的消息通过主题进行分类。一个主题可以被分为若干个分区一个分区就是一个提交日志。消息以追加的方式写入分区然后以先入先出的顺序读取。kafka通过分区来实现数据的冗余和伸缩性分区可以分布在不同的服务器上也就是说一个主题可以横跨多个服务器以此来提供比单个服务器更强大的性能类比HDFS分布式文件系统
注意:由于一个主题包含多个分区,因此无法在整个主题范围内保证消息的顺序性,**但可以保证消息在单个分区内的顺序性**。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka主题和分区.png"/> </div>
### 2.分区复制
每个主题被分为若干个分区,每个分区有多个副本。那些副本被保存在 broker 上,每个 broker 可以保存成百上千个属于不同主题和分区的副本。副本有以下两种类型
- 首领副本 每个分区都有一个首领副本 。 为了保证一致性,所有生产者请求和消费者请求都会经过这个副本。
- 跟随者副本 首领以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,它们唯一的任务就是从首领那里复制消息,保持与首领一致的状态。如果首领发生崩渍,其中的一个跟随者会被提升为新首领。
### 3. 生产者
- 默认情况下生产者在把消息均衡地分布到在主题的所有分区上,而并不关心特定消息会被写到那个分区;
- 如果指定消息键,则通过对消息键的散列来实现分区;
- 也可以通过消息键和分区器来实现把消息直接写到指定的分区这个需要自定义分区器需要实现Partitioner 接口并重写其中的partition方法。
### 4. 消费者
消费者是**消费者群组**的一部分。也就是说,会有一个或者多个消费者共同读取一个主题,群组保证每个分区只能被一个消费者使用。
**一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组里面的多个消费者读取。多个消费者群组可以共同读取同一个主题,彼此之间互不影响**。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka消费者.png"/> </div>
### 5.broker和集群
一个独立的kafka服务器被称为broker。broker 接收来自生产者的消息为消息设置偏移量并提交消息到磁盘保存。broker为消费者提供服务对读取分区的请求做出响应返回已经提交到磁盘的消息。
broker是集群的组成部分。每一个集群都有一个broker同时充当了集群控制器的角色自动从集群的活跃成员中选举出来。控制器负责管理工作包括将分区分配给broker和监控broker。**在集群中一个分区从属一个broker,该broker被称为分区的首领**。一个分区可以分配给多个broker,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余如果有一个broker失效其他broker可以接管领导权。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka集群复制.png"/> </div>
更多kafka 的说明可以参考我的个人笔记:[《Kafka权威指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka/%E3%80%8AKafka%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#53-%E5%A4%8D%E5%88%B6)
## 二、项目说明
#### 1.1 项目结构说明
本项目提供kafka发送简单消息、对象消息、和多消费者组消费消息三种情况下的sample。
1. kafkaSimpleConsumer 用于普通消息的监听;
2. kafkaBeanConsumer 用于对象消息监听;
3. kafkaGroupConsumer 用于多消费者组和多消费者对主题分区消息监听的情况。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-kafka.png"/> </div>
#### 1.2 主要依赖
```xml
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
```
## 二、 整合 kafka
#### 2.1 kafka基本配置
```yaml
spring:
kafka:
# 以逗号分隔的地址列表用于建立与Kafka集群的初始连接(kafka 默认的端口号为9092)
bootstrap-servers: 127.0.0.1:9092
producer:
# 发生错误后,消息重发的次数。
retries: 0
#当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
batch-size: 16384
# 设置生产者内存缓冲区的大小。
buffer-memory: 33554432
# 键的序列化方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
# 值的序列化方式
value-serializer: org.apache.kafka.common.serialization.StringSerializer
# acks=0 生产者在成功写入消息之前不会等待任何来自服务器的响应。
# acks=1 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
acks: 1
consumer:
# 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式如1S,1M,2H,5D
auto-commit-interval: 1S
# 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
# latest默认值在偏移量无效的情况下消费者将从最新的记录开始读取数据在消费者启动之后生成的记录
# earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
auto-offset-reset: earliest
# 是否自动提交偏移量默认值是true,为了避免出现重复数据和数据丢失可以把它设置为false,然后手动提交偏移量
enable-auto-commit: true
# 键的反序列化方式
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 值的反序列化方式
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
listener:
# 在侦听器容器中运行的线程数。
concurrency: 5
```
这里需要说明的是:
在spring boot 2.X 版本 auto-commit-interval自动提交的时间间隔采用的是值的类型为Duration Duration 是 jdk 1.8 版本之后引入的类,在其源码中我们可以看到对于其字符串的表达需要符合一定的规范,即数字+单位如下的写法1s 1.5s 0s 0.001S 1h 2d 在yaml 中都是有效的。如果传入无效的字符串则spring boot 在启动阶段解析配置文件的时候就会抛出异常。
```java
public final class Duration
implements TemporalAmount, Comparable<Duration>, Serializable {
/**
* The pattern for parsing.
*/
private static final Pattern PATTERN =
Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" +
"(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?", Pattern.CASE_INSENSITIVE);
........
}
```
#### 2.2 KafkaTemplate实现消息发送
```java
@Component
@Slf4j
public class KafKaCustomrProducer {
@Autowired
private KafkaTemplate kafkaTemplate;
public void sendMessage(String topic, Object object) {
/*
* 这里的ListenableFuture类是spring对java原生Future的扩展增强,是一个泛型接口,用于监听异步方法的回调
* 而对于kafka send 方法返回值而言,这里的泛型所代表的实际类型就是 SendResult<K, V>,而这里K,V的泛型实际上
* 被用于ProducerRecord<K, V> producerRecord,即生产者发送消息的key,value 类型
*/
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, object);
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onFailure(Throwable throwable) {
log.info("发送消息失败:" + throwable.getMessage());
}
@Override
public void onSuccess(SendResult<String, Object> sendResult) {
System.out.println("发送结果:" + sendResult.toString());
}
});
}
}
```
#### 2.3 @KafkaListener注解实现消息的监听
```java
@Component
@Slf4j
public class KafkaSimpleConsumer {
// 简单消费者
@KafkaListener(groupId = "simpleGroup", topics = Topic.SIMPLE)
public void consumer1_1(ConsumerRecord<String, Object> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) {
System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic);
/*
* 如果需要手工提交异步 consumer.commitSync();
* 手工同步提交 consumer.commitAsync()
*/
}
}
```
#### 2.4 测试整合结果
```java
@Slf4j
@RestController
public class SendMsgController {
@Autowired
private KafKaCustomrProducer producer;
@Autowired
private KafkaTemplate kafkaTemplate;
/***
* 发送消息体为基本类型的消息
*/
@GetMapping("sendSimple")
public void sendSimple() {
producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka");
}
}
```
## 三、关于多消费者组的测试
#### 3.1 创建多分区主题
```java
/**
* @author : heibaiying
* @description : kafka配置类
*/
@Configuration
public class KafkaConfig {
@Bean
public NewTopic groupTopic() {
// 指定主题名称,分区数量,和复制因子
return new NewTopic(Topic.GROUP, 10, (short) 2);
}
}
```
#### 3.2 多消费者组对同一主题的监听
1. 消费者1-1 监听主题的 0、1 分区
2. 消费者1-2 监听主题的 2、3 分区
3. 消费者1-3 监听主题的 0、1 分区
4. 消费者2-1 监听主题的所有分区
```java
/**
* @author : heibaiying
* @description : kafka 消费者组
* <p>
* 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。
*/
@Component
@Slf4j
public class KafkaGroupConsumer {
// 分组1 中的消费者1
@KafkaListener(id = "consumer1-1", groupId = "group1", topicPartitions =
{@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
})
public void consumer1_1(ConsumerRecord<String, Object> record) {
System.out.println("consumer1-1 收到消息:" + record.value());
}
// 分组1 中的消费者2
@KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions =
{@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"})
})
public void consumer1_2(ConsumerRecord<String, Object> record) {
System.out.println("consumer1-2 收到消息:" + record.value());
}
// 分组1 中的消费者3
@KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions =
{@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
})
public void consumer1_3(ConsumerRecord<String, Object> record) {
System.out.println("consumer1-3 收到消息:" + record.value());
}
// 分组2 中的消费者
@KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP)
public void consumer2_1(ConsumerRecord<String, Object> record) {
System.err.println("consumer2-1 收到消息:" + record.value());
}
}
```
#### 3.2 发送消息时候指定主题的具体分区
```java
/***
* 多消费者组、组中多消费者对同一主题的消费情况
*/
@GetMapping("sendGroup")
public void sendGroup() {
for (int i = 0; i < 4; i++) {
// 第二个参数指定分区,第三个参数指定消息键 分区优先
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i);
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
@Override
public void onFailure(Throwable throwable) {
log.info("发送消息失败:" + throwable.getMessage());
}
@Override
public void onSuccess(SendResult<String, Object> sendResult) {
System.out.println("发送结果:" + sendResult.toString());
}
});
}
}
```
测试结果:
```yaml
# 主要看每次发送结果中的 partition 属性代表四次消息分别发送到了主题的0,1,2,3分区
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=1, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 1, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-1@13]
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=0, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 0, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-0@19]
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=3, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 3, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-3@13]
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=2, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 2, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-2@13]
# 消费者组2 接收情况
consumer2-1 收到消息:hello group 1
consumer2-1 收到消息:hello group 0
consumer2-1 收到消息:hello group 2
consumer2-1 收到消息:hello group 3
# 消费者1-1接收情况
consumer1-1 收到消息:hello group 1
consumer1-1 收到消息:hello group 0
# 消费者1-3接收情况
consumer1-3 收到消息:hello group 1
consumer1-3 收到消息:hello group 0
# 消费者1-2接收情况
consumer1-2 收到消息:hello group 3
consumer1-2 收到消息:hello group 2
```
#### 3.4 测试结果
1. 和kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响;
2. 和kafka原本机制不一样的是这里我们消费者1-1和消费1-3共同属于同一个消费者组并且监听同样的分区按照原本kafka的机制群组保证每个分区只能被同一个消费者组的一个消费者使用但是按照spring的声明方式实现的消息监听这里被两个消费者都监听到了。
## 四、序列化与反序列化
用例采用的是第三方fastjson将实体类序列化为json后发送。实现如下
```java
/***
* 发送消息体为bean的消息
*/
@GetMapping("sendBean")
public void sendBean() {
Programmer programmer = new Programmer("xiaoming", 12, 21212.33f, new Date());
producer.sendMessage(Topic.BEAN, JSON.toJSON(programmer).toString());
}
```
```java
@Component
@Slf4j
public class KafkaBeanConsumer {
@KafkaListener(groupId = "beanGroup",topics = Topic.BEAN)
public void consumer(ConsumerRecord<String, Object> record) {
System.out.println("消费者收到消息:" + JSON.parseObject(record.value().toString(), Programmer.class));
}
}
```

View File

@ -1,148 +1,161 @@
# spring boot 整合 mecached
## 一、说明
### 1.1 XMemcached客户端说明
spring boot 官方并没有提供关于 memcached 的starter所以我们这里还是采用XMemcached作为客户端进行整合。 XMemcached是基于java nio的memcached高性能客户端支持完整的memcached协议支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
### 1.2 项目结构说明
memcached的整合配置位于config文件夹下。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-memcached.png"/> </div>
### 1.3 主要依赖
```xml
<!--memcached java 客户端-->
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.5</version>
</dependency>
```
# spring boot 整合 mecached
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11--XMemcached客户端说明">1.1 XMemcached客户端说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#13-主要依赖">1.3 主要依赖</a><br/>
<a href="#二spring-boot-整合-memcached">二、spring boot 整合 memcached</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-单机配置">2.1 单机配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-集群配置">2.2 集群配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-存储基本类型测试用例">2.3 存储基本类型测试用例</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#25-存储实体对象测试用例">2.5 存储实体对象测试用例</a><br/>
<a href="#附memcached-基本命令">memcached 基本命令</a><br/>
## 正文<br/>
## 二、spring boot 整合 memcached
#### 2.1 单机配置
```java
@Bean
public MemcachedClient memcachedClient() {
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
MemcachedClient memcachedClient = null;
try {
memcachedClient = builder.build();
} catch (IOException e) {
e.printStackTrace();
}
return memcachedClient;
}
```
#### 2.2 集群配置
```java
@Bean
public MemcachedClient memcachedClientForCluster() {
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
addressList.add(new InetSocketAddress("192.168.200.201", 11211));
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
// 赋予权重
int[] weights = {1, 2};
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
// 设置连接池大小
builder.setConnectionPoolSize(10);
// 协议工厂
builder.setCommandFactory(new TextCommandFactory());
// 分布策略一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
// 设置序列化器
builder.setTranscoder(new SerializingTranscoder());
MemcachedClient memcachedClient = null;
try {
memcachedClient = builder.build();
} catch (IOException e) {
e.printStackTrace();
}
return memcachedClient;
}
```
#### 2.3 存储基本类型测试用例
xmemcached单机版本和集群版本注入的实例是相同的。
```java
/**
* @author : heibaiying
* @description : Memcached 操作基本对象
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MemSamples {
@Autowired
private MemcachedClient memcachedClient;
@Test
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
String value = memcachedClient.get("hello");
System.out.println("hello=" + value);
memcachedClient.delete("hello");
value = memcachedClient.get("hello");
System.out.println("hello=" + value);
}
}
```
#### 2.5 存储实体对象测试用例
```java
/**
* @author : heibaiying
* @description :Memcached 序列化与反序列化
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MemObjectSamples {
@Autowired
private MemcachedClient memcachedClient;
@Test
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
Programmer programmer = memcachedClient.get("programmer");
System.out.println("hello ," + programmer.getName());
memcachedClient.delete("programmer");
programmer = memcachedClient.get("programmer");
Assert.assertNull(programmer);
}
}
```
## 附memcached 基本命令
| 命令 | 格式 | 说明 |
| --------------- | -------------------------------------------------- | ------------------------------------- |
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下才可以插入 |
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
| 查询操作 get | get key | |
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
| 删除 delete | delete key | 将数据打一个删除标记 |
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
## 一、说明
### 1.1 XMemcached客户端说明
spring boot 官方并没有提供关于 memcached 的starter所以我们这里还是采用XMemcached作为客户端进行整合。 XMemcached是基于java nio的memcached高性能客户端支持完整的memcached协议支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
### 1.2 项目结构说明
memcached的整合配置位于config文件夹下。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-memcached.png"/> </div>
### 1.3 主要依赖
```xml
<!--memcached java 客户端-->
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.5</version>
</dependency>
```
## 二、spring boot 整合 memcached
#### 2.1 单机配置
```java
@Bean
public MemcachedClient memcachedClient() {
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
MemcachedClient memcachedClient = null;
try {
memcachedClient = builder.build();
} catch (IOException e) {
e.printStackTrace();
}
return memcachedClient;
}
```
#### 2.2 集群配置
```java
@Bean
public MemcachedClient memcachedClientForCluster() {
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
addressList.add(new InetSocketAddress("192.168.200.201", 11211));
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
// 赋予权重
int[] weights = {1, 2};
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
// 设置连接池大小
builder.setConnectionPoolSize(10);
// 协议工厂
builder.setCommandFactory(new TextCommandFactory());
// 分布策略一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
// 设置序列化器
builder.setTranscoder(new SerializingTranscoder());
MemcachedClient memcachedClient = null;
try {
memcachedClient = builder.build();
} catch (IOException e) {
e.printStackTrace();
}
return memcachedClient;
}
```
#### 2.3 存储基本类型测试用例
xmemcached单机版本和集群版本注入的实例是相同的。
```java
/**
* @author : heibaiying
* @description : Memcached 操作基本对象
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MemSamples {
@Autowired
private MemcachedClient memcachedClient;
@Test
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
String value = memcachedClient.get("hello");
System.out.println("hello=" + value);
memcachedClient.delete("hello");
value = memcachedClient.get("hello");
System.out.println("hello=" + value);
}
}
```
#### 2.5 存储实体对象测试用例
```java
/**
* @author : heibaiying
* @description :Memcached 序列化与反序列化
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MemObjectSamples {
@Autowired
private MemcachedClient memcachedClient;
@Test
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
Programmer programmer = memcachedClient.get("programmer");
System.out.println("hello ," + programmer.getName());
memcachedClient.delete("programmer");
programmer = memcachedClient.get("programmer");
Assert.assertNull(programmer);
}
}
```
## 附memcached 基本命令
| 命令 | 格式 | 说明 |
| --------------- | -------------------------------------------------- | ------------------------------------- |
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下才可以插入 |
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
| 查询操作 get | get key | |
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
| 删除 delete | delete key | 将数据打一个删除标记 |
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
| 清库 | flush_all | |

View File

@ -1,182 +1,192 @@
# spring boot 整合 mongodb
## 一、说明
#### 1.1 用例结构
1. 本用例提供mongdb的简单整合用例
2. 提供用MongoTemplate的方式操作mongdb见测试用例MongoOriginalTests.java
3. 提供基于spring data jpa 的方式操作mongodb(推荐)见测试用例MongoJPATests.java
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mongodb.png"/> </div>
#### 1.2 项目主要依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
```
# spring boot 整合 mongodb
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-用例结构">1.1 用例结构</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
<a href="#二整合-mongodb">二、整合 mongodb</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-在applicationyml-中配置mongodb数据源">2.1 在application.yml 中配置mongodb数据源</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22--基于MongoTemplate实现对mongodb的操作">2.2 基于MongoTemplate实现对mongodb的操作</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-使用-data-jpa-方式操作mongodb-推荐使用">2.3 使用 data jpa 方式操作mongodb (推荐使用)</a><br/>
## 正文<br/>
## 二、整合 mongodb
#### 2.1 在application.yml 中配置mongodb数据源
```yaml
spring:
data:
mongodb:
database: spring
uri: mongodb://192.168.0.108:27017
```
#### 2.2 基于MongoTemplate实现对mongodb的操作
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class MongoOriginalTests {
@Autowired
private MongoTemplate mongoTemplate;
@Test
public void insert() {
// 单条插入
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
List<Programmer> programmers = new ArrayList<Programmer>();
// 批量插入
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
mongoTemplate.insert(programmers, Programmer.class);
}
// 条件查询
@Test
public void select() {
Criteria criteria = new Criteria();
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
Query query = new Query(criteria);
Programmer one = mongoTemplate.findOne(query, Programmer.class);
System.out.println(one);
}
// 更新数据
@Test
public void MUpdate() {
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
System.out.println("更新记录数:" + updateResult.getModifiedCount());
}
// 删除指定数据
@Test
public void delete() {
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
System.out.println("影响记录数:" + result.getDeletedCount());
System.out.println("是否成功:" + result.wasAcknowledged());
}
}
```
#### 2.3 使用 data jpa 方式操作mongodb (推荐使用)
1.新建查询结构,查询方法按照支持的关键字命名
```java
public interface ProgrammerRepository extends MongoRepository<Programmer, String> {
void deleteAllByName(String name);
Programmer findAllByName(String names);
Programmer findByNameAndAge(String name, int age);
}
```
2.测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class MongoJPATests {
@Autowired
private ProgrammerRepository repository;
@Test
public void insert() {
// 单条插入
repository.save(new Programmer("python", 23, 21832.34f, new Date()));
// 批量插入
List<Programmer> programmers = new ArrayList<Programmer>();
programmers.add(new Programmer("java", 21, 52200.21f, new Date()));
programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
repository.saveAll(programmers);
}
// 条件查询
@Test
public void select() {
Programmer java = repository.findByNameAndAge("java", 21);
Assert.assertEquals(java.getSalary(), 52200.21f, 0.01);
}
// 更新数据
@Test
public void MUpdate() {
repository.save(new Programmer("Go", 8, 500.21f, new Date()));
Programmer go = repository.findAllByName("Go");
Assert.assertEquals(go.getAge(), 8);
}
// 删除指定数据
@Test
public void delete() {
repository.deleteAllByName("python");
Optional<Programmer> python = repository.findById("python");
Assert.assertFalse(python.isPresent());
}
}
```
查询方法支持的关键字如下,更多命名规范可以参见Spring Data MongoDB官方文档[Query Methods](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongodb.repositories.queries)
| Keyword | Sample | Logical result |
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| `After` | `findByBirthdateAfter(Date date)` | `{"birthdate" : {"$gt" : date}}` |
| `GreaterThan` | `findByAgeGreaterThan(int age)` | `{"age" : {"$gt" : age}}` |
| `GreaterThanEqual` | `findByAgeGreaterThanEqual(int age)` | `{"age" : {"$gte" : age}}` |
| `Before` | `findByBirthdateBefore(Date date)` | `{"birthdate" : {"$lt" : date}}` |
| `LessThan` | `findByAgeLessThan(int age)` | `{"age" : {"$lt" : age}}` |
| `LessThanEqual` | `findByAgeLessThanEqual(int age)` | `{"age" : {"$lte" : age}}` |
| `Between` | `findByAgeBetween(int from, int to)` | `{"age" : {"$gt" : from, "$lt" : to}}` |
| `In` | `findByAgeIn(Collection ages)` | `{"age" : {"$in" : [ages…]}}` |
| `NotIn` | `findByAgeNotIn(Collection ages)` | `{"age" : {"$nin" : [ages…]}}` |
| `IsNotNull`, `NotNull` | `findByFirstnameNotNull()` | `{"firstname" : {"$ne" : null}}` |
| `IsNull`, `Null` | `findByFirstnameNull()` | `{"firstname" : null}` |
| `Like`, `StartingWith`, `EndingWith` | `findByFirstnameLike(String name)` | `{"firstname" : name} (name as regex)` |
| `NotLike`, `IsNotLike` | `findByFirstnameNotLike(String name)` | `{"firstname" : { "$not" : name }} (name as regex)` |
| `Containing` on String | `findByFirstnameContaining(String name)` | `{"firstname" : name} (name as regex)` |
| `NotContaining` on String | `findByFirstnameNotContaining(String name)` | `{"firstname" : { "$not" : name}} (name as regex)` |
| `Containing` on Collection | `findByAddressesContaining(Address address)` | `{"addresses" : { "$in" : address}}` |
| `NotContaining` on Collection | `findByAddressesNotContaining(Address address)` | `{"addresses" : { "$not" : { "$in" : address}}}` |
| `Regex` | `findByFirstnameRegex(String firstname)` | `{"firstname" : {"$regex" : firstname }}` |
| `(No keyword)` | `findByFirstname(String name)` | `{"firstname" : name}` |
| `Not` | `findByFirstnameNot(String name)` | `{"firstname" : {"$ne" : name}}` |
| `Near` | `findByLocationNear(Point point)` | `{"location" : {"$near" : [x,y]}}` |
| `Near` | `findByLocationNear(Point point, Distance max)` | `{"location" : {"$near" : [x,y], "$maxDistance" : max}}` |
| `Near` | `findByLocationNear(Point point, Distance min, Distance max)` | `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}` |
| `Within` | `findByLocationWithin(Circle circle)` | `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}` |
| `Within` | `findByLocationWithin(Box box)` | `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}` |
| `IsTrue`, `True` | `findByActiveIsTrue()` | `{"active" : true}` |
| `IsFalse`, `False` | `findByActiveIsFalse()` | `{"active" : false}` |
| `Exists` | `findByLocationExists(boolean exists)` | `{"location" : {"$exists" : exists }}` |
## 一、说明
#### 1.1 用例结构
1. 本用例提供mongdb的简单整合用例
2. 提供用MongoTemplate的方式操作mongdb见测试用例MongoOriginalTests.java
3. 提供基于spring data jpa 的方式操作mongodb(推荐)见测试用例MongoJPATests.java
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mongodb.png"/> </div>
#### 1.2 项目主要依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
```
## 二、整合 mongodb
#### 2.1 在application.yml 中配置mongodb数据源
```yaml
spring:
data:
mongodb:
database: spring
uri: mongodb://192.168.0.108:27017
```
#### 2.2 基于MongoTemplate实现对mongodb的操作
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class MongoOriginalTests {
@Autowired
private MongoTemplate mongoTemplate;
@Test
public void insert() {
// 单条插入
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
List<Programmer> programmers = new ArrayList<Programmer>();
// 批量插入
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
mongoTemplate.insert(programmers, Programmer.class);
}
// 条件查询
@Test
public void select() {
Criteria criteria = new Criteria();
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
Query query = new Query(criteria);
Programmer one = mongoTemplate.findOne(query, Programmer.class);
System.out.println(one);
}
// 更新数据
@Test
public void MUpdate() {
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
System.out.println("更新记录数:" + updateResult.getModifiedCount());
}
// 删除指定数据
@Test
public void delete() {
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
System.out.println("影响记录数:" + result.getDeletedCount());
System.out.println("是否成功:" + result.wasAcknowledged());
}
}
```
#### 2.3 使用 data jpa 方式操作mongodb (推荐使用)
1.新建查询结构,查询方法按照支持的关键字命名
```java
public interface ProgrammerRepository extends MongoRepository<Programmer, String> {
void deleteAllByName(String name);
Programmer findAllByName(String names);
Programmer findByNameAndAge(String name, int age);
}
```
2.测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class MongoJPATests {
@Autowired
private ProgrammerRepository repository;
@Test
public void insert() {
// 单条插入
repository.save(new Programmer("python", 23, 21832.34f, new Date()));
// 批量插入
List<Programmer> programmers = new ArrayList<Programmer>();
programmers.add(new Programmer("java", 21, 52200.21f, new Date()));
programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
repository.saveAll(programmers);
}
// 条件查询
@Test
public void select() {
Programmer java = repository.findByNameAndAge("java", 21);
Assert.assertEquals(java.getSalary(), 52200.21f, 0.01);
}
// 更新数据
@Test
public void MUpdate() {
repository.save(new Programmer("Go", 8, 500.21f, new Date()));
Programmer go = repository.findAllByName("Go");
Assert.assertEquals(go.getAge(), 8);
}
// 删除指定数据
@Test
public void delete() {
repository.deleteAllByName("python");
Optional<Programmer> python = repository.findById("python");
Assert.assertFalse(python.isPresent());
}
}
```
查询方法支持的关键字如下,更多命名规范可以参见Spring Data MongoDB官方文档[Query Methods](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongodb.repositories.queries)
| Keyword | Sample | Logical result |
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| `After` | `findByBirthdateAfter(Date date)` | `{"birthdate" : {"$gt" : date}}` |
| `GreaterThan` | `findByAgeGreaterThan(int age)` | `{"age" : {"$gt" : age}}` |
| `GreaterThanEqual` | `findByAgeGreaterThanEqual(int age)` | `{"age" : {"$gte" : age}}` |
| `Before` | `findByBirthdateBefore(Date date)` | `{"birthdate" : {"$lt" : date}}` |
| `LessThan` | `findByAgeLessThan(int age)` | `{"age" : {"$lt" : age}}` |
| `LessThanEqual` | `findByAgeLessThanEqual(int age)` | `{"age" : {"$lte" : age}}` |
| `Between` | `findByAgeBetween(int from, int to)` | `{"age" : {"$gt" : from, "$lt" : to}}` |
| `In` | `findByAgeIn(Collection ages)` | `{"age" : {"$in" : [ages…]}}` |
| `NotIn` | `findByAgeNotIn(Collection ages)` | `{"age" : {"$nin" : [ages…]}}` |
| `IsNotNull`, `NotNull` | `findByFirstnameNotNull()` | `{"firstname" : {"$ne" : null}}` |
| `IsNull`, `Null` | `findByFirstnameNull()` | `{"firstname" : null}` |
| `Like`, `StartingWith`, `EndingWith` | `findByFirstnameLike(String name)` | `{"firstname" : name} (name as regex)` |
| `NotLike`, `IsNotLike` | `findByFirstnameNotLike(String name)` | `{"firstname" : { "$not" : name }} (name as regex)` |
| `Containing` on String | `findByFirstnameContaining(String name)` | `{"firstname" : name} (name as regex)` |
| `NotContaining` on String | `findByFirstnameNotContaining(String name)` | `{"firstname" : { "$not" : name}} (name as regex)` |
| `Containing` on Collection | `findByAddressesContaining(Address address)` | `{"addresses" : { "$in" : address}}` |
| `NotContaining` on Collection | `findByAddressesNotContaining(Address address)` | `{"addresses" : { "$not" : { "$in" : address}}}` |
| `Regex` | `findByFirstnameRegex(String firstname)` | `{"firstname" : {"$regex" : firstname }}` |
| `(No keyword)` | `findByFirstname(String name)` | `{"firstname" : name}` |
| `Not` | `findByFirstnameNot(String name)` | `{"firstname" : {"$ne" : name}}` |
| `Near` | `findByLocationNear(Point point)` | `{"location" : {"$near" : [x,y]}}` |
| `Near` | `findByLocationNear(Point point, Distance max)` | `{"location" : {"$near" : [x,y], "$maxDistance" : max}}` |
| `Near` | `findByLocationNear(Point point, Distance min, Distance max)` | `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}` |
| `Within` | `findByLocationWithin(Circle circle)` | `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}` |
| `Within` | `findByLocationWithin(Box box)` | `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}` |
| `IsTrue`, `True` | `findByActiveIsTrue()` | `{"active" : true}` |
| `IsFalse`, `False` | `findByActiveIsFalse()` | `{"active" : false}` |
| `Exists` | `findByLocationExists(boolean exists)` | `{"location" : {"$exists" : exists }}` |

View File

@ -1,249 +1,259 @@
# spring boot 整合 mybatis
# spring boot 整合 mybatis
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构">1.1 项目结构</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
<a href="#二整合-mybatis">二、整合 mybatis</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-在applicationyml-中配置数据源">2.1 在application.yml 中配置数据源</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22--xml方式的sql语句">2.2 xml方式的sql语句</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-注解方式的sql语句">2.3 注解方式的sql语句</a><br/>
## 正文<br/>
## 一、说明
#### 1.1 项目结构
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下
2. 关于mybatis sql的写法提供两种方式:
xml 写法对应的类为ProgrammerMapper.java 和 programmerMapper.xml用MybatisXmlTest进行测试
注解写法对应的类为Programmer.java 用MybatisAnnotationTest进行测试。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mybatis.png"/> </div>
#### 1.2 项目主要依赖
需要说明的是按照spring 官方对应自定义的starter 命名规范的推荐:
- 官方的starter命名spring-boot-starter-XXXX
- 其他第三方starter命名XXXX-spring-boot-starte
所以mybatis的starter命名为mybatis-spring-boot-starter如果有自定义starter需求也需要按照此命名规则进行命名。
```xml
<!--spring 1.5 x 以上版本对应 mybatis 1.3.x (1.3.1)
关于更多spring-boot 与 mybatis 的版本对应可以参见 <a href="http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/">-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
```
spring boot 与 mybatis 版本的对应关系:
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
| --------------------------- | ------------------------------------------------------------ | ------------- |
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
## 二、整合 mybatis
#### 2.1 在application.yml 中配置数据源
spring boot 2.x 版本默认采用Hikari作为数据库连接池Hikari是目前java平台性能最好的连接池性能好于druid。
```yaml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
# spring-boot 2 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
type: com.zaxxer.hikari.HikariDataSource
hikari:
# 池中维护的最小空闲连接数
minimum-idle: 10
# 池中最大连接数,包括闲置和使用中的连接
maximum-pool-size: 20
# 此属性控制从池返回的连接的默认自动提交行为。默认为true
auto-commit: true
# 允许最长空闲时间
idle-timeout: 30000
# 此属性表示连接池的用户定义名称主要显示在日志记录和JMX管理控制台中以标识池和池配置。 默认值:自动生成
pool-name: custom-hikari
#此属性控制池中连接的最长生命周期值0表示无限生命周期默认1800000即30分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认30秒即30000
connection-timeout: 30000
# 连接测试sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成 select 1 from dual
connection-test-query: SELECT 1
# mybatis 相关配置
mybatis:
# 指定 sql xml 文件的位置
mapper-locations: classpath*:mappers/*.xml
configuration:
# 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
# oracle数据库建议配置为JdbcType.NULL, 默认是Other
jdbc-type-for-null: 'null'
# 是否打印sql语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
```
#### 2.2 xml方式的sql语句
新建 ProgrammerMapper.java 和 programmerMapper.xml及其测试类
```java
@Mapper
public interface ProgrammerMapper {
void save(Programmer programmer);
Programmer selectById(int id);
int modify(Programmer programmer);
void delete(int id);
}
```
```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.heibaiying.springboot.dao.ProgrammerMapper">
<insert id="save">
insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})
</insert>
<select id="selectById" resultType="com.heibaiying.springboot.bean.Programmer">
select * from programmer where name = #{id}
</select>
<update id="modify">
update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}
</update>
<delete id="delete">
delete from programmer where id = #{id}
</delete>
</mapper>
```
测试类
```java
/***
* @description: xml Sql测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisXmlTest {
@Autowired
private ProgrammerMapper mapper;
@Test
public void save() {
mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
}
@Test
public void modify() {
mapper.modify(new Programmer(1, "xiaohong", 112, 347.34f, new Date()));
}
@Test
public void selectByCondition() {
Programmer programmers = mapper.selectById(1);
System.out.println(programmers);
}
@Test
public void delete() {
mapper.delete(2);
Programmer programmers = mapper.selectById(2);
Assert.assertNull(programmers);
}
}
```
#### 2.3 注解方式的sql语句
```java
@Mapper
public interface ProgrammerDao {
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
void save(Programmer programmer);
@Select("select * from programmer where name = #{id}")
Programmer selectById(int id);
@Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
int modify(Programmer programmer);
@Delete(" delete from programmer where id = #{id}")
void delete(int id);
}
```
测试类
```java
/***
* @description: 注解Sql测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisAnnotationTest {
@Autowired
private ProgrammerDao programmerDao;
@Test
public void save() {
programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
}
@Test
public void modify() {
programmerDao.modify(new Programmer(1, "xiaolan", 21, 347.34f, new Date()));
}
@Test
public void selectByCondition() {
Programmer programmers = programmerDao.selectById(1);
System.out.println(programmers);
}
@Test
public void delete() {
programmerDao.delete(3);
Programmer programmers = programmerDao.selectById(3);
Assert.assertNull(programmers);
}
}
```
## 一、说明
#### 1.1 项目结构
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下
2. 关于mybatis sql的写法提供两种方式:
xml 写法对应的类为ProgrammerMapper.java 和 programmerMapper.xml用MybatisXmlTest进行测试
注解写法对应的类为Programmer.java 用MybatisAnnotationTest进行测试。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mybatis.png"/> </div>
#### 1.2 项目主要依赖
需要说明的是按照spring 官方对应自定义的starter 命名规范的推荐:
- 官方的starter命名spring-boot-starter-XXXX
- 其他第三方starter命名XXXX-spring-boot-starte
所以mybatis的starter命名为mybatis-spring-boot-starter如果有自定义starter需求也需要按照此命名规则进行命名。
```xml
<!--spring 1.5 x 以上版本对应 mybatis 1.3.x (1.3.1)
关于更多spring-boot 与 mybatis 的版本对应可以参见 <a href="http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/">-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--引入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
```
spring boot 与 mybatis 版本的对应关系:
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
| --------------------------- | ------------------------------------------------------------ | ------------- |
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
## 二、整合 mybatis
#### 2.1 在application.yml 中配置数据源
spring boot 2.x 版本默认采用Hikari作为数据库连接池Hikari是目前java平台性能最好的连接池性能好于druid。
```yaml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
# spring-boot 2 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
type: com.zaxxer.hikari.HikariDataSource
hikari:
# 池中维护的最小空闲连接数
minimum-idle: 10
# 池中最大连接数,包括闲置和使用中的连接
maximum-pool-size: 20
# 此属性控制从池返回的连接的默认自动提交行为。默认为true
auto-commit: true
# 允许最长空闲时间
idle-timeout: 30000
# 此属性表示连接池的用户定义名称主要显示在日志记录和JMX管理控制台中以标识池和池配置。 默认值:自动生成
pool-name: custom-hikari
#此属性控制池中连接的最长生命周期值0表示无限生命周期默认1800000即30分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认30秒即30000
connection-timeout: 30000
# 连接测试sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成 select 1 from dual
connection-test-query: SELECT 1
# mybatis 相关配置
mybatis:
# 指定 sql xml 文件的位置
mapper-locations: classpath*:mappers/*.xml
configuration:
# 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
# oracle数据库建议配置为JdbcType.NULL, 默认是Other
jdbc-type-for-null: 'null'
# 是否打印sql语句 调试的时候可以开启
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
```
#### 2.2 xml方式的sql语句
新建 ProgrammerMapper.java 和 programmerMapper.xml及其测试类
```java
@Mapper
public interface ProgrammerMapper {
void save(Programmer programmer);
Programmer selectById(int id);
int modify(Programmer programmer);
void delete(int id);
}
```
```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.heibaiying.springboot.dao.ProgrammerMapper">
<insert id="save">
insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})
</insert>
<select id="selectById" resultType="com.heibaiying.springboot.bean.Programmer">
select * from programmer where name = #{id}
</select>
<update id="modify">
update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}
</update>
<delete id="delete">
delete from programmer where id = #{id}
</delete>
</mapper>
```
测试类
```java
/***
* @description: xml Sql测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisXmlTest {
@Autowired
private ProgrammerMapper mapper;
@Test
public void save() {
mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
}
@Test
public void modify() {
mapper.modify(new Programmer(1, "xiaohong", 112, 347.34f, new Date()));
}
@Test
public void selectByCondition() {
Programmer programmers = mapper.selectById(1);
System.out.println(programmers);
}
@Test
public void delete() {
mapper.delete(2);
Programmer programmers = mapper.selectById(2);
Assert.assertNull(programmers);
}
}
```
#### 2.3 注解方式的sql语句
```java
@Mapper
public interface ProgrammerDao {
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
void save(Programmer programmer);
@Select("select * from programmer where name = #{id}")
Programmer selectById(int id);
@Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
int modify(Programmer programmer);
@Delete(" delete from programmer where id = #{id}")
void delete(int id);
}
```
测试类
```java
/***
* @description: 注解Sql测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisAnnotationTest {
@Autowired
private ProgrammerDao programmerDao;
@Test
public void save() {
programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
}
@Test
public void modify() {
programmerDao.modify(new Programmer(1, "xiaolan", 21, 347.34f, new Date()));
}
@Test
public void selectByCondition() {
Programmer programmers = programmerDao.selectById(1);
System.out.println(programmers);
}
@Test
public void delete() {
programmerDao.delete(3);
Programmer programmers = programmerDao.selectById(3);
Assert.assertNull(programmers);
}
}
```

View File

@ -1,390 +1,404 @@
# spring boot 整合 rabbitmq
## 一、 项目结构说明
1.1 之前关于spring 整合 rabbitmq 我们采用的是单项目的方式为了使得用例更具有实际意义这里采用maven多模块的构建方式在spring-boot-rabbitmq下构建三个子模块
1. rabbitmq-common 是公共模块用于存放公共的接口、配置和bean,被rabbitmq-producer和rabbitmq-consumer在pom.xml中引用
2. rabbitmq-producer 是消息的生产者模块;
3. rabbitmq-consumer是消息的消费者模块。
1.2 关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-rabbitmq.png"/> </div>
## 二、关键依赖
在父工程的项目中统一导入依赖rabbitmq的starter(spring-boot-starter-amqp)父工程的pom.xml如下
```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>
<packaging>pom</packaging>
<modules>
<module>rabbitmq-consumer</module>
<module>rabbitmq-producer</module>
<module>rabbitmq-common</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heibaiying</groupId>
<artifactId>spring-boot-rabbitmq</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-rabbitmq</name>
<description>RabbitMQ project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
```
## 三、公共模块rabbitmq-common
- bean 下为公共的实体类。
- constant 下为公共配置,用静态常量引用。(这里我使用静态常量是为了方便引用,实际中也可以按照情况,抽取为公共配置文件)
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-common.png"/> </div>
```java
package com.heibaiying.constant;
/**
* @author : heibaiying
* @description : rabbit 公用配置信息
*/
public class RabbitInfo {
// queue 配置
public static final String QUEUE_NAME = "spring.boot.simple.queue";
public static final String QUEUE_DURABLE = "true";
// exchange 配置
public static final String EXCHANGE_NAME = "spring.boot.simple.exchange";
public static final String EXCHANGE_TYPE = "topic";
// routing key
public static final String ROUTING_KEY = "springboot.simple.*";
}
```
## 四、服务消费者rabbitmq-consumer
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-consumer.png"/> </div>
#### 4.1 消息消费者配置
```yaml
spring:
rabbitmq:
addresses: 127.0.0.1:5672
# RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
username: guest
password: guest
virtual-host: /
listener:
simple:
# 为了保证信息能够被正确消费,建议签收模式设置为手工签收,并在代码中实现手工签收
acknowledge-mode: manual
# 侦听器调用者线程的最小数量
concurrency: 10
# 侦听器调用者线程的最大数量
max-concurrency: 50
```
#### 4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者
1. 使用注解创建的交换机、队列、和绑定关系会在项目初始化的时候自动创建,但是不会重复创建;
2. 这里我们创建两个消息监听器,分别演示消息是基本类型和消息是对象时的配置区别。
```java
/**
* @author : heibaiying
* @description : 消息是对象的消费者
*/
@Component
@Slf4j
public class RabbitmqBeanConsumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = RabbitBeanInfo.QUEUE_NAME, durable = RabbitBeanInfo.QUEUE_DURABLE),
exchange = @Exchange(value = RabbitBeanInfo.EXCHANGE_NAME, type = RabbitBeanInfo.EXCHANGE_TYPE),
key = RabbitBeanInfo.ROUTING_KEY)
)
@RabbitHandler
public void onMessage(@Payload Programmer programmer, @Headers Map<String, Object> headers, Channel channel) throws Exception {
log.info("programmer:{} ", programmer);
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
channel.basicAck(deliveryTag, false);
}
}
```
```java
@Component
@Slf4j
public class RabbitmqConsumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = RabbitInfo.QUEUE_NAME, durable = RabbitInfo.QUEUE_DURABLE),
exchange = @Exchange(value = RabbitInfo.EXCHANGE_NAME, type = RabbitInfo.EXCHANGE_TYPE),
key = RabbitInfo.ROUTING_KEY)
)
@RabbitHandler
public void onMessage(Message message, Channel channel) throws Exception {
MessageHeaders headers = message.getHeaders();
// 获取消息头信息和消息体
log.info("msgInfo:{} ; payload:{} ", headers.get("msgInfo"), message.getPayload());
// DELIVERY_TAG 代表 RabbitMQ 向该Channel投递的这条消息的唯一标识ID是一个单调递增的正整数
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
// 第二个参数代表是否一次签收多条,当该参数为 true 时,则可以一次性确认 DELIVERY_TAG 小于等于传入值的所有消息
channel.basicAck(deliveryTag, false);
}
}
```
## 五、 消息生产者rabbitmq-producer
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-producer.png"/> </div>
#### 5.1 消息生产者配置
```yaml
spring:
rabbitmq:
addresses: 127.0.0.1:5672
# RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
username: guest
password: guest
virtual-host: /
# 是否启用发布者确认 具体确认回调实现见代码
publisher-confirms: true
# 是否启用发布者返回 具体返回回调实现见代码
publisher-returns: true
# 是否启用强制消息 保证消息的有效监听
template.mandatory: true
server:
port: 8090
```
#### 5.2 创建消息生产者
```java
/**
* @author : heibaiying
* @description : 消息生产者
*/
@Component
@Slf4j
public class RabbitmqProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendSimpleMessage(Map<String, Object> headers, Object message,
String messageId, String exchangeName, String key) {
// 自定义消息头
MessageHeaders messageHeaders = new MessageHeaders(headers);
// 创建消息
Message<Object> msg = MessageBuilder.createMessage(message, messageHeaders);
/* 确认的回调 确认消息是否到达 Broker 服务器 其实就是是否到达交换器
如果发送时候指定的交换器不存在 ack就是false 代表消息不可达 */
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
log.info("correlationData{} , ack:{}", correlationData.getId(), ack);
if (!ack) {
System.out.println("进行对应的消息补偿机制");
}
});
/* 消息失败的回调
* 例如消息已经到达交换器上,但路由键匹配任何绑定到该交换器的队列,会触发这个回调,此时 replyText: NO_ROUTE
*/
rabbitTemplate.setReturnCallback((message1, replyCode, replyText, exchange, routingKey) -> {
log.info("message:{}; replyCode: {}; replyText: {} ; exchange:{} ; routingKey:{}",
message1, replyCode, replyText, exchange, routingKey);
});
// 在实际中ID 应该是全局唯一 能够唯一标识消息 消息不可达的时候触发ConfirmCallback回调方法时可以获取该值进行对应的错误处理
CorrelationData correlationData = new CorrelationData(messageId);
rabbitTemplate.convertAndSend(exchangeName, key, msg, correlationData);
}
}
```
#### 5.3 以单元测试的方式发送消息
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqProducerTests {
@Autowired
private RabbitmqProducer producer;
/***
* 发送消息体为简单数据类型的消息
*/
@Test
public void send() {
Map<String, Object> heads = new HashMap<>();
heads.put("msgInfo", "自定义消息头信息");
// 模拟生成消息ID,在实际中应该是全局唯一的 消息不可达时候可以在setConfirmCallback回调中取得可以进行对应的重发或错误处理
String id = String.valueOf(Math.round(Math.random() * 10000));
producer.sendSimpleMessage(heads, "hello Spring", id, RabbitInfo.EXCHANGE_NAME, "springboot.simple.abc");
}
/***
* 发送消息体为bean的消息
*/
@Test
public void sendBean() {
String id = String.valueOf(Math.round(Math.random() * 10000));
Programmer programmer = new Programmer("xiaoMing", 12, 12123.45f, new Date());
producer.sendSimpleMessage(null, programmer, id, RabbitBeanInfo.EXCHANGE_NAME, RabbitBeanInfo.ROUTING_KEY);
}
}
```
## 六、项目构建的说明
因为在项目中consumer和producer模块均依赖公共模块,所以在构建consumer和producer项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
```shell
mvn install -Dmaven.test.skip = true
```
consumer中 pom.xml如下
```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>com.heibaiying</groupId>
<artifactId>spring-boot-rabbitmq</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>rabbitmq-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rabbitmq-consumer</name>
<description>RabbitMQ consumer project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.heibaiying</groupId>
<artifactId>rabbitmq-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
producer中 pom.xml如下
```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>com.heibaiying</groupId>
<artifactId>spring-boot-rabbitmq</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>rabbitmq-producer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rabbitmq-producer</name>
<description>RabbitMQ producer project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.heibaiying</groupId>
<artifactId>rabbitmq-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
# spring boot 整合 rabbitmq
## 目录<br/>
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
<a href="#二关键依赖">二、关键依赖</a><br/>
<a href="#三公共模块rabbitmq-common">三、公共模块rabbitmq-common</a><br/>
<a href="#四服务消费者rabbitmq-consumer">四、服务消费者rabbitmq-consumer</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-消息消费者配置">4.1 消息消费者配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-使用注解@RabbitListener和@RabbitHandler创建消息监听者">4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者</a><br/>
<a href="#五-消息生产者rabbitmq-producer">五、 消息生产者rabbitmq-producer</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#51-消息生产者配置">5.1 消息生产者配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#52--创建消息生产者">5.2 创建消息生产者</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#53--以单元测试的方式发送消息">5.3 以单元测试的方式发送消息</a><br/>
<a href="#六项目构建的说明">六、项目构建的说明</a><br/>
## 正文<br/>
## 一、 项目结构说明
1.1 之前关于spring 整合 rabbitmq 我们采用的是单项目的方式为了使得用例更具有实际意义这里采用maven多模块的构建方式在spring-boot-rabbitmq下构建三个子模块
1. rabbitmq-common 是公共模块用于存放公共的接口、配置和bean,被rabbitmq-producer和rabbitmq-consumer在pom.xml中引用
2. rabbitmq-producer 是消息的生产者模块;
3. rabbitmq-consumer是消息的消费者模块。
1.2 关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-rabbitmq.png"/> </div>
## 二、关键依赖
在父工程的项目中统一导入依赖rabbitmq的starter(spring-boot-starter-amqp)父工程的pom.xml如下
```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>
<packaging>pom</packaging>
<modules>
<module>rabbitmq-consumer</module>
<module>rabbitmq-producer</module>
<module>rabbitmq-common</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heibaiying</groupId>
<artifactId>spring-boot-rabbitmq</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-rabbitmq</name>
<description>RabbitMQ project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
```
## 三、公共模块rabbitmq-common
- bean 下为公共的实体类。
- constant 下为公共配置,用静态常量引用。(这里我使用静态常量是为了方便引用,实际中也可以按照情况,抽取为公共配置文件)
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-common.png"/> </div>
```java
package com.heibaiying.constant;
/**
* @author : heibaiying
* @description : rabbit 公用配置信息
*/
public class RabbitInfo {
// queue 配置
public static final String QUEUE_NAME = "spring.boot.simple.queue";
public static final String QUEUE_DURABLE = "true";
// exchange 配置
public static final String EXCHANGE_NAME = "spring.boot.simple.exchange";
public static final String EXCHANGE_TYPE = "topic";
// routing key
public static final String ROUTING_KEY = "springboot.simple.*";
}
```
## 四、服务消费者rabbitmq-consumer
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-consumer.png"/> </div>
#### 4.1 消息消费者配置
```yaml
spring:
rabbitmq:
addresses: 127.0.0.1:5672
# RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
username: guest
password: guest
virtual-host: /
listener:
simple:
# 为了保证信息能够被正确消费,建议签收模式设置为手工签收,并在代码中实现手工签收
acknowledge-mode: manual
# 侦听器调用者线程的最小数量
concurrency: 10
# 侦听器调用者线程的最大数量
max-concurrency: 50
```
#### 4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者
1. 使用注解创建的交换机、队列、和绑定关系会在项目初始化的时候自动创建,但是不会重复创建;
2. 这里我们创建两个消息监听器,分别演示消息是基本类型和消息是对象时的配置区别。
```java
/**
* @author : heibaiying
* @description : 消息是对象的消费者
*/
@Component
@Slf4j
public class RabbitmqBeanConsumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = RabbitBeanInfo.QUEUE_NAME, durable = RabbitBeanInfo.QUEUE_DURABLE),
exchange = @Exchange(value = RabbitBeanInfo.EXCHANGE_NAME, type = RabbitBeanInfo.EXCHANGE_TYPE),
key = RabbitBeanInfo.ROUTING_KEY)
)
@RabbitHandler
public void onMessage(@Payload Programmer programmer, @Headers Map<String, Object> headers, Channel channel) throws Exception {
log.info("programmer:{} ", programmer);
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
channel.basicAck(deliveryTag, false);
}
}
```
```java
@Component
@Slf4j
public class RabbitmqConsumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = RabbitInfo.QUEUE_NAME, durable = RabbitInfo.QUEUE_DURABLE),
exchange = @Exchange(value = RabbitInfo.EXCHANGE_NAME, type = RabbitInfo.EXCHANGE_TYPE),
key = RabbitInfo.ROUTING_KEY)
)
@RabbitHandler
public void onMessage(Message message, Channel channel) throws Exception {
MessageHeaders headers = message.getHeaders();
// 获取消息头信息和消息体
log.info("msgInfo:{} ; payload:{} ", headers.get("msgInfo"), message.getPayload());
// DELIVERY_TAG 代表 RabbitMQ 向该Channel投递的这条消息的唯一标识ID是一个单调递增的正整数
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
// 第二个参数代表是否一次签收多条,当该参数为 true 时,则可以一次性确认 DELIVERY_TAG 小于等于传入值的所有消息
channel.basicAck(deliveryTag, false);
}
}
```
## 五、 消息生产者rabbitmq-producer
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-producer.png"/> </div>
#### 5.1 消息生产者配置
```yaml
spring:
rabbitmq:
addresses: 127.0.0.1:5672
# RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
username: guest
password: guest
virtual-host: /
# 是否启用发布者确认 具体确认回调实现见代码
publisher-confirms: true
# 是否启用发布者返回 具体返回回调实现见代码
publisher-returns: true
# 是否启用强制消息 保证消息的有效监听
template.mandatory: true
server:
port: 8090
```
#### 5.2 创建消息生产者
```java
/**
* @author : heibaiying
* @description : 消息生产者
*/
@Component
@Slf4j
public class RabbitmqProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendSimpleMessage(Map<String, Object> headers, Object message,
String messageId, String exchangeName, String key) {
// 自定义消息头
MessageHeaders messageHeaders = new MessageHeaders(headers);
// 创建消息
Message<Object> msg = MessageBuilder.createMessage(message, messageHeaders);
/* 确认的回调 确认消息是否到达 Broker 服务器 其实就是是否到达交换器
如果发送时候指定的交换器不存在 ack就是false 代表消息不可达 */
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
log.info("correlationData{} , ack:{}", correlationData.getId(), ack);
if (!ack) {
System.out.println("进行对应的消息补偿机制");
}
});
/* 消息失败的回调
* 例如消息已经到达交换器上,但路由键匹配任何绑定到该交换器的队列,会触发这个回调,此时 replyText: NO_ROUTE
*/
rabbitTemplate.setReturnCallback((message1, replyCode, replyText, exchange, routingKey) -> {
log.info("message:{}; replyCode: {}; replyText: {} ; exchange:{} ; routingKey:{}",
message1, replyCode, replyText, exchange, routingKey);
});
// 在实际中ID 应该是全局唯一 能够唯一标识消息 消息不可达的时候触发ConfirmCallback回调方法时可以获取该值进行对应的错误处理
CorrelationData correlationData = new CorrelationData(messageId);
rabbitTemplate.convertAndSend(exchangeName, key, msg, correlationData);
}
}
```
#### 5.3 以单元测试的方式发送消息
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitmqProducerTests {
@Autowired
private RabbitmqProducer producer;
/***
* 发送消息体为简单数据类型的消息
*/
@Test
public void send() {
Map<String, Object> heads = new HashMap<>();
heads.put("msgInfo", "自定义消息头信息");
// 模拟生成消息ID,在实际中应该是全局唯一的 消息不可达时候可以在setConfirmCallback回调中取得可以进行对应的重发或错误处理
String id = String.valueOf(Math.round(Math.random() * 10000));
producer.sendSimpleMessage(heads, "hello Spring", id, RabbitInfo.EXCHANGE_NAME, "springboot.simple.abc");
}
/***
* 发送消息体为bean的消息
*/
@Test
public void sendBean() {
String id = String.valueOf(Math.round(Math.random() * 10000));
Programmer programmer = new Programmer("xiaoMing", 12, 12123.45f, new Date());
producer.sendSimpleMessage(null, programmer, id, RabbitBeanInfo.EXCHANGE_NAME, RabbitBeanInfo.ROUTING_KEY);
}
}
```
## 六、项目构建的说明
因为在项目中consumer和producer模块均依赖公共模块,所以在构建consumer和producer项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
```shell
mvn install -Dmaven.test.skip = true
```
consumer中 pom.xml如下
```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>com.heibaiying</groupId>
<artifactId>spring-boot-rabbitmq</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>rabbitmq-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rabbitmq-consumer</name>
<description>RabbitMQ consumer project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.heibaiying</groupId>
<artifactId>rabbitmq-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
producer中 pom.xml如下
```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>com.heibaiying</groupId>
<artifactId>spring-boot-rabbitmq</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>rabbitmq-producer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rabbitmq-producer</name>
<description>RabbitMQ producer project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.heibaiying</groupId>
<artifactId>rabbitmq-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```

View File

@ -1,444 +1,476 @@
# spring boot 整合 redis
## 一、说明
#### 1.1 项目结构
1. RedisConfig.java实现了redisTemplate 序列化与反序列化的配置;
2. RedisOperation和RedisObjectOperation分别封装了对基本类型和对象的操作。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-redis.png"/> </div>
#### 1.2 项目主要依赖
```xml
<!--redis starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--jackson 序列化包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
```
## 二、整合 Redis
#### 2.1 在application.yml 中配置redis数据源
```yaml
spring:
redis:
host: 127.0.0.1
port: 6379
# 默认采用的也是 0 号数据库 redis官方在4.0之后版本就不推荐采用单节点多数据库(db1-db15)的方式存储数据,如果有需要应该采用集群方式构建
database: 0
# 如果是集群节点 采用如下配置指定节点
#spring.redis.cluster.nodes
```
#### 2.2 封装redis基本操作
需要说明的是spring boot 提供了两个template 用于操作redis:
- StringRedisTemplate由于redis在大多数使用情况下都是操作字符串类型的存储所以spring boot 将对字符串的操作单独封装在StringRedisTemplate
- RedisTemplate<Object, Object> redis 用于操作任意类型的template。
```java
/**
* @author : heibaiying
* @description : redis 基本操作
*/
@Component
public class RedisOperation {
@Autowired
private StringRedisTemplate redisTemplate;
/***
* 操作普通字符串
*/
public void StringSet(String key, String value) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(key, value);
}
/***
* 操作列表
*/
public void ListSet(String key, List<String> values) {
ListOperations<String, String> listOperations = redisTemplate.opsForList();
values.forEach(value -> listOperations.leftPush(key, value));
}
/***
* 操作集合
*/
public void SetSet(String key, Set<String> values) {
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
values.forEach(value -> setOperations.add(key, value));
}
/***
* 获取字符串
*/
public String StringGet(String key) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
/***
* 列表弹出元素
*/
public String ListLeftPop(String key) {
ListOperations<String, String> listOperations = redisTemplate.opsForList();
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
}
/***
* 集合弹出元素
*/
public String SetPop(String key) {
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
return setOperations.pop(key);
}
}
```
```java
/**
* @author : heibaiying
* @description : redis 基本操作
*/
@Component
public class RedisObjectOperation {
@Autowired
private RedisTemplate<Object, Object> objectRedisTemplate;
/***
* 操作对象
*/
public void ObjectSet(Object key, Object value) {
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
valueOperations.set(key, value);
}
/***
* 操作元素为对象列表
*/
public void ListSet(Object key, List<Object> values) {
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
values.forEach(value -> listOperations.leftPush(key, value));
}
/***
* 操作元素为对象集合
*/
public void SetSet(Object key, Set<Object> values) {
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
values.forEach(value -> setOperations.add(key, value));
}
/***
* 获取对象
*/
public Object ObjectGet(Object key) {
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
return valueOperations.get(key);
}
/***
* 列表弹出元素
*/
public Object ListLeftPop(Object key) {
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
}
/***
* 集合弹出元素
*/
public Object SetPop(Object key) {
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
return setOperations.pop(key);
}
}
```
#### 2.3 redisTemplate 序列化为json格式与反序列化
这里需要说明的spring boot 的 redisTemplate 本身是实现了对象的序列化与反序列化的是支持直接存取对象的。但是这里的序列化默认采用的是JdkSerializationRedisSerializer.serialize()序列化为二进制码这个本身是不影响redisTemplate 的操作的因为redisTemplate在取出数据的时候会自动进行反序列化。
但是如果我们在命令行中使用get命令去获取数据时候得到的是一串不直观的二进制码所以我们尽量将其序列化为直观的json格式存储。
```java
/**
* @author : heibaiying
* @description : 自定义序列化器
* 不定义的话默认采用的是serializer.JdkSerializationRedisSerializer.serialize()序列化为二进制字节码 存储在数据库中不直观
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 需要导入依赖 com.fasterxml.jackson.core jackson-databind
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 第一个参数表示: 表示所有访问者都受到影响 包括 字段, getter / isGetter,settercreator
// 第二个参数表示: 所有类型的访问修饰符都是可接受的,不论是公有还有私有表变量都会被序列化
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置key,value 序列化规则
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
```
#### 2.4 测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTests {
@Autowired
private RedisOperation redisOperation;
@Test
public void StringOperation() {
redisOperation.StringSet("hello", "redis");
String s = redisOperation.StringGet("hello");
Assert.assertEquals(s, "redis");
}
@Test
public void ListOperation() {
redisOperation.ListSet("skill", Arrays.asList("java", "oracle", "vue"));
String s = redisOperation.ListLeftPop("skill");
Assert.assertEquals(s, "vue");
}
/*
* 需要注意的是Redis的集合set不仅不允许有重复元素并且集合中的元素是无序的
* 不能通过索引下标获取元素。哪怕你在java中传入的集合是有序的newLinkedHashSet但是实际在Redis存储的还是无序的集合
*/
@Test
public void SetOperation() {
redisOperation.SetSet("skillSet", Sets.newLinkedHashSet("java", "oracle", "vue"));
String s = redisOperation.SetPop("skillSet");
Assert.assertNotNull(s);
}
}
```
<br/>
## 附Redis的数据结构和操作命令
### 1.1 预备
#### 1.1.1 全局命令
1. 查看所有键: **keys \***
2. 查看键总数:**dbsize**
3. 检查键是否存在:**exists key**
4. 删除键:**del key [key ...]** 支持删除多个键
5. 键过期:**expire key seconds**
ttl命令会返回键的剩余过期时间 它有3种返回值
- 大于等于0的整数 键剩余的过期时间。
- -1 键没设置过期时间。
- -2 键不存在
6. 键的数据结构 **type key**
#### 1.1.2 数据结构和内部编码
type命令实际返回的就是当前键的数据结构类型 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合)
#### 1.1.3 单线程架构
1. 纯内存访问, Redis将所有数据放在内存中 内存的响应时长大约为100纳秒 这是Redis达到每秒万级别访问的重要基础。
2. 非阻塞I/O Redis使用epoll作为I/O多路复用技术的实现 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间 如图2-6所示。
3. 单线程避免了线程切换和竞态产生的消耗。
### 1.2 字符串
| 作用 | 格式 | 参数或示例 |
| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds 为键设置秒级过期时间。 <br/>px milliseconds 为键设置毫秒级过期时间。<br/>nx 键必须不存在, 才可以设置成功, 用于添加。<br/>xx 与nx相反 键必须存在, 才可以设置成功, 用于更新。 |
| 获取值 | get key | r如果获取的键不存在 则返回nil(空) |
| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 |
| 批量获取值 | mget key [key ...] | mget a b c d |
| 计数 | incr key decr key incrby key increment指定数值自增<br/>decrby key decrement指定数值自减<br/>incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。<br/>键不存在创建键并按照值为0自增或自减 返回结果为1。 |
| 追加值 | append key value | 向字符串的默认追加值 |
| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
| 设置并返回原值 | getset key value | |
| 设置指定位置的租字符串 | setrange key offeset value | |
| 获取部分字符串 | getrange key start end | |
### 1.3 哈希
| 作用 | 格式 | 参数或示例 |
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 设置值 | hset key field value | hset user:1 name tom<br/>hset user:1 age 12 |
| 获取值 | hget key field | hget user:1 name |
| 删除field | hdel key field [field ...] | |
| 计算field个数 | hlen key | |
| 批量设置或获取field-value | hmget key field [field]<br/>hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin<br/>hmget user:1 name city |
| 判断field是否存在 | hexists key field | |
| 获取所有field | hkeys key | |
| 获取所有value | hvals key | |
| 获取所有的filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。<br/>获取全部 可以使用hscan命令 该命令会渐进式遍历哈希类型 |
| 计数 | hincrby key field<br/>hincrbyfloat key field | |
### 1.4 列表
| 作用 | 格式 | 参数或示例 |
| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 增 | 左侧插入lpush key value [value ...] 右侧插入rpush key value [value ...] 某个指定元素前后插入linsert key before\|after pivot value | |
| 查 | 获取指定范围内的元素列表lrange key start end 获取列表指定索引下标的元素lindex key index 获取列表指定长度llen key | lrange listkey 0 -1 |
| 删 | 从列表左侧弹出元素lpop key 从列表右侧弹出元素rpop key 删除指定元素lrem key count value 截取列表ltrim key start end | count>0 从左到右, 删除最多count个元素。<br/>count<0 从右到左 删除最多count绝对值个元素<br/>count=0 删除所有 |
| 改 | 修改指定索引下标的元素lset key index newValue | |
| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout | key[key...] 多个列表的键。 timeout 阻塞时间\|等待时间(单位: 秒) |
### 1.5 集合
集合set 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素** 并且集合中的元素是无序的, **不能通过索引下标获取元素**
**集合内操作**
| 作用 | 格式 | 参数或示例 |
| -------------------- | ------------------------------ | ----------------------------------------- |
| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 |
| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 |
| 计算元素个数 | scard key | |
| 判断元素是否在集合中 | sismember key element | |
| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素count 默认为1 |
| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素spop 会 |
| 获取集合中所有元素 | smembers key | 可用sscan 代替 |
**集合间操作**
| 作用 | 格式 |
| ---------------------------- | ------------------------------------------------------------ |
| 求多个集合的交集 | sinter key [key ...] |
| 求多个集合的并集 | suinon key [key ...] |
| 求多个集合的差集 | sdiff key [key ...] |
| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...] <br/>suionstore destination key [key ...]<br/>sdiffstore destination key [key ...] |
### 1.6 有序集合
有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数score 作为排序的依据。
**集合内操作**
| 作用 | 格式 | 参数或示例 |
| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 添加成员 | zadd key score member [score member ...] | nx member必须不存在 才可设置成功, 用于添加。<br> xx member必须存在 才可以设置成功, 用于更新。<br/>ch 返回此次操作后, 有序集合元素和分数发生变化的个数<br/>incr 对score做增加 相当于后面介绍的zincrby。 |
| 计算成员个数 | zcard key | |
| 计算某个成员的分数 | zscore key member | |
| 计算某个成员的排名 | zrank key member zrevrank key member | zrank是从分数从低到高返回排名 zrevrank反之。 |
| 删除成员 | zrem key member [member ...] | |
| 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom |
| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回 zrevrange反之。 |
| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回 zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间小括号 和闭区间(中括号) -inf和+inf分别代表无限小和无限大 |
| 删除指定排名内的升序元素 | zremrangerank key start end | |
| 删除指定分数范围的成员 | zremrangebyscore key min max | |
**集合间操作**
| 作用 | 格式 |
| ---- | ------------------------------------------------------------ |
| 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
- destination 交集计算结果保存到这个键。
- numkeys 需要做交集计算键的个数。
- key[key...] 需要做交集计算的键。
- weights weight[weight...] 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重 每个键的权重默认是1。
- aggregate sum|min|max 计算成员交集后, 分值可以按照sum 、min最小值 、 max最大值 做汇总, 默认值是sum。
### 1.7 键管理
#### 1.7.1 单个键管理
##### 1.键重命名
**rename key newkey**
为了防止被强行rename Redis提供了renamenx命令 确保只有newKey不存在时候才被覆盖。
##### 2. 随机返回键
**random key**
##### 3.键过期
- expire key seconds 键在seconds秒后过期。
- expireat key timestamp 键在秒级时间戳timestamp后过期。
- pexpire key milliseconds 键在milliseconds毫秒后过期。
- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期
注意:
1. 如果expire key的键不存在 返回结果为0
2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样
3. persist key t命令可以将键的过期时间清除
4. 对于字符串类型键, 执行set命令会去掉过期时间 这个问题很容易在开发中被忽视
5. Redis不支持二级数据结构例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
6. setex命令作为set+expire的组合 不但是原子执行, 同时减少了一次网络通讯的时间
#### 1.7.2 键遍历
##### 1. 全量键遍历
**keys pattern**
##### 2. 渐进式遍历
scan cursor \[match pattern] \[count number]
- cursor是必需参数 实际上cursor是一个游标 第一次遍历从0开始 每次scan遍历完都会返回当前游标的值 直到游标值为0 表示遍历结束。
- match pattern是可选参数 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。
- count number是可选参数 它的作用是表明每次要遍历的键个数, 默认值是10 此参数可以适当增大。
#### 1.7.3 数据库管理
##### 1.切换数据库
**select dbIndex**
##### 2.flushdb/flushall
# spring boot 整合 redis
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构">1.1 项目结构</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
<a href="#二整合-Redis">二、整合 Redis</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-在applicationyml-中配置redis数据源">2.1 在application.yml 中配置redis数据源</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22--封装redis基本操作">2.2 封装redis基本操作</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-redisTemplate-序列化为json格式与反序列化">2.3 redisTemplate 序列化为json格式与反序列化</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#24-测试">2.4 测试</a><br/>
<a href="#附Redis的数据结构和操作命令">Redis的数据结构和操作命令</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-预备">1.1 预备</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#111-全局命令">1.1.1 全局命令</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#112-数据结构和内部编码">1.1.2 数据结构和内部编码</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#113-单线程架构">1.1.3 单线程架构</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-字符串">1.2 字符串</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#13-哈希">1.3 哈希</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#14-列表">1.4 列表</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#15-集合">1.5 集合</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#16-有序集合">1.6 有序集合</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#17-键管理">1.7 键管理</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#171-单个键管理">1.7.1 单个键管理</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#1键重命名">1.键重命名 </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2-随机返回键">2. 随机返回键 </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#3键过期">3.键过期</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#172-键遍历">1.7.2 键遍历</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#1-全量键遍历">1. 全量键遍历</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2-渐进式遍历">2. 渐进式遍历</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#173-数据库管理">1.7.3 数据库管理</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#1切换数据库">1.切换数据库</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2flushdb/flushall">2.flushdb/flushall </a><br/>
## 正文<br/>
## 一、说明
#### 1.1 项目结构
1. RedisConfig.java实现了redisTemplate 序列化与反序列化的配置;
2. RedisOperation和RedisObjectOperation分别封装了对基本类型和对象的操作。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-redis.png"/> </div>
#### 1.2 项目主要依赖
```xml
<!--redis starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--jackson 序列化包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
```
## 二、整合 Redis
#### 2.1 在application.yml 中配置redis数据源
```yaml
spring:
redis:
host: 127.0.0.1
port: 6379
# 默认采用的也是 0 号数据库 redis官方在4.0之后版本就不推荐采用单节点多数据库(db1-db15)的方式存储数据,如果有需要应该采用集群方式构建
database: 0
# 如果是集群节点 采用如下配置指定节点
#spring.redis.cluster.nodes
```
#### 2.2 封装redis基本操作
需要说明的是spring boot 提供了两个template 用于操作redis:
- StringRedisTemplate由于redis在大多数使用情况下都是操作字符串类型的存储所以spring boot 将对字符串的操作单独封装在StringRedisTemplate
- RedisTemplate<Object, Object> redis 用于操作任意类型的template。
```java
/**
* @author : heibaiying
* @description : redis 基本操作
*/
@Component
public class RedisOperation {
@Autowired
private StringRedisTemplate redisTemplate;
/***
* 操作普通字符串
*/
public void StringSet(String key, String value) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(key, value);
}
/***
* 操作列表
*/
public void ListSet(String key, List<String> values) {
ListOperations<String, String> listOperations = redisTemplate.opsForList();
values.forEach(value -> listOperations.leftPush(key, value));
}
/***
* 操作集合
*/
public void SetSet(String key, Set<String> values) {
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
values.forEach(value -> setOperations.add(key, value));
}
/***
* 获取字符串
*/
public String StringGet(String key) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}
/***
* 列表弹出元素
*/
public String ListLeftPop(String key) {
ListOperations<String, String> listOperations = redisTemplate.opsForList();
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
}
/***
* 集合弹出元素
*/
public String SetPop(String key) {
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
return setOperations.pop(key);
}
}
```
```java
/**
* @author : heibaiying
* @description : redis 基本操作
*/
@Component
public class RedisObjectOperation {
@Autowired
private RedisTemplate<Object, Object> objectRedisTemplate;
/***
* 操作对象
*/
public void ObjectSet(Object key, Object value) {
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
valueOperations.set(key, value);
}
/***
* 操作元素为对象列表
*/
public void ListSet(Object key, List<Object> values) {
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
values.forEach(value -> listOperations.leftPush(key, value));
}
/***
* 操作元素为对象集合
*/
public void SetSet(Object key, Set<Object> values) {
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
values.forEach(value -> setOperations.add(key, value));
}
/***
* 获取对象
*/
public Object ObjectGet(Object key) {
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
return valueOperations.get(key);
}
/***
* 列表弹出元素
*/
public Object ListLeftPop(Object key) {
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
}
/***
* 集合弹出元素
*/
public Object SetPop(Object key) {
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
return setOperations.pop(key);
}
}
```
#### 2.3 redisTemplate 序列化为json格式与反序列化
这里需要说明的spring boot 的 redisTemplate 本身是实现了对象的序列化与反序列化的是支持直接存取对象的。但是这里的序列化默认采用的是JdkSerializationRedisSerializer.serialize()序列化为二进制码这个本身是不影响redisTemplate 的操作的因为redisTemplate在取出数据的时候会自动进行反序列化。
但是如果我们在命令行中使用get命令去获取数据时候得到的是一串不直观的二进制码所以我们尽量将其序列化为直观的json格式存储。
```java
/**
* @author : heibaiying
* @description : 自定义序列化器
* 不定义的话默认采用的是serializer.JdkSerializationRedisSerializer.serialize()序列化为二进制字节码 存储在数据库中不直观
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 需要导入依赖 com.fasterxml.jackson.core jackson-databind
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 第一个参数表示: 表示所有访问者都受到影响 包括 字段, getter / isGetter,settercreator
// 第二个参数表示: 所有类型的访问修饰符都是可接受的,不论是公有还有私有表变量都会被序列化
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置key,value 序列化规则
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
```
#### 2.4 测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTests {
@Autowired
private RedisOperation redisOperation;
@Test
public void StringOperation() {
redisOperation.StringSet("hello", "redis");
String s = redisOperation.StringGet("hello");
Assert.assertEquals(s, "redis");
}
@Test
public void ListOperation() {
redisOperation.ListSet("skill", Arrays.asList("java", "oracle", "vue"));
String s = redisOperation.ListLeftPop("skill");
Assert.assertEquals(s, "vue");
}
/*
* 需要注意的是Redis的集合set不仅不允许有重复元素并且集合中的元素是无序的
* 不能通过索引下标获取元素。哪怕你在java中传入的集合是有序的newLinkedHashSet但是实际在Redis存储的还是无序的集合
*/
@Test
public void SetOperation() {
redisOperation.SetSet("skillSet", Sets.newLinkedHashSet("java", "oracle", "vue"));
String s = redisOperation.SetPop("skillSet");
Assert.assertNotNull(s);
}
}
```
<br/>
## 附Redis的数据结构和操作命令
### 1.1 预备
#### 1.1.1 全局命令
1. 查看所有键: **keys \***
2. 查看键总数:**dbsize**
3. 检查键是否存在:**exists key**
4. 删除键:**del key [key ...]** 支持删除多个键
5. 键过期:**expire key seconds**
ttl命令会返回键的剩余过期时间 它有3种返回值
- 大于等于0的整数 键剩余的过期时间。
- -1 键没设置过期时间。
- -2 键不存在
6. 键的数据结构 **type key**
#### 1.1.2 数据结构和内部编码
type命令实际返回的就是当前键的数据结构类型 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合)
#### 1.1.3 单线程架构
1. 纯内存访问, Redis将所有数据放在内存中 内存的响应时长大约为100纳秒 这是Redis达到每秒万级别访问的重要基础。
2. 非阻塞I/O Redis使用epoll作为I/O多路复用技术的实现 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间 如图2-6所示。
3. 单线程避免了线程切换和竞态产生的消耗。
### 1.2 字符串
| 作用 | 格式 | 参数或示例 |
| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds 为键设置秒级过期时间。 <br/>px milliseconds 为键设置毫秒级过期时间。<br/>nx 键必须不存在, 才可以设置成功, 用于添加。<br/>xx 与nx相反 键必须存在, 才可以设置成功, 用于更新。 |
| 获取值 | get key | r如果获取的键不存在 则返回nil(空) |
| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 |
| 批量获取值 | mget key [key ...] | mget a b c d |
| 计数 | incr key decr key incrby key increment指定数值自增<br/>decrby key decrement指定数值自减<br/>incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。<br/>键不存在创建键并按照值为0自增或自减 返回结果为1。 |
| 追加值 | append key value | 向字符串的默认追加值 |
| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
| 设置并返回原值 | getset key value | |
| 设置指定位置的租字符串 | setrange key offeset value | |
| 获取部分字符串 | getrange key start end | |
### 1.3 哈希
| 作用 | 格式 | 参数或示例 |
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 设置值 | hset key field value | hset user:1 name tom<br/>hset user:1 age 12 |
| 获取值 | hget key field | hget user:1 name |
| 删除field | hdel key field [field ...] | |
| 计算field个数 | hlen key | |
| 批量设置或获取field-value | hmget key field [field]<br/>hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin<br/>hmget user:1 name city |
| 判断field是否存在 | hexists key field | |
| 获取所有field | hkeys key | |
| 获取所有value | hvals key | |
| 获取所有的filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。<br/>获取全部 可以使用hscan命令 该命令会渐进式遍历哈希类型 |
| 计数 | hincrby key field<br/>hincrbyfloat key field | |
### 1.4 列表
| 作用 | 格式 | 参数或示例 |
| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 增 | 左侧插入lpush key value [value ...] 右侧插入rpush key value [value ...] 某个指定元素前后插入linsert key before\|after pivot value | |
| 查 | 获取指定范围内的元素列表lrange key start end 获取列表指定索引下标的元素lindex key index 获取列表指定长度llen key | lrange listkey 0 -1 |
| 删 | 从列表左侧弹出元素lpop key 从列表右侧弹出元素rpop key 删除指定元素lrem key count value 截取列表ltrim key start end | count>0 从左到右, 删除最多count个元素。<br/>count<0 从右到左 删除最多count绝对值个元素<br/>count=0 删除所有 |
| 改 | 修改指定索引下标的元素lset key index newValue | |
| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout | key[key...] 多个列表的键。 timeout 阻塞时间\|等待时间(单位: 秒) |
### 1.5 集合
集合set 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素** 并且集合中的元素是无序的, **不能通过索引下标获取元素**
**集合内操作**
| 作用 | 格式 | 参数或示例 |
| -------------------- | ------------------------------ | ----------------------------------------- |
| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 |
| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 |
| 计算元素个数 | scard key | |
| 判断元素是否在集合中 | sismember key element | |
| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素count 默认为1 |
| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素spop 会 |
| 获取集合中所有元素 | smembers key | 可用sscan 代替 |
**集合间操作**
| 作用 | 格式 |
| ---------------------------- | ------------------------------------------------------------ |
| 求多个集合的交集 | sinter key [key ...] |
| 求多个集合的并集 | suinon key [key ...] |
| 求多个集合的差集 | sdiff key [key ...] |
| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...] <br/>suionstore destination key [key ...]<br/>sdiffstore destination key [key ...] |
### 1.6 有序集合
有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数score 作为排序的依据。
**集合内操作**
| 作用 | 格式 | 参数或示例 |
| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 添加成员 | zadd key score member [score member ...] | nx member必须不存在 才可设置成功, 用于添加。<br> xx member必须存在 才可以设置成功, 用于更新。<br/>ch 返回此次操作后, 有序集合元素和分数发生变化的个数<br/>incr 对score做增加 相当于后面介绍的zincrby。 |
| 计算成员个数 | zcard key | |
| 计算某个成员的分数 | zscore key member | |
| 计算某个成员的排名 | zrank key member zrevrank key member | zrank是从分数从低到高返回排名 zrevrank反之。 |
| 删除成员 | zrem key member [member ...] | |
| 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom |
| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回 zrevrange反之。 |
| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回 zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间小括号 和闭区间(中括号) -inf和+inf分别代表无限小和无限大 |
| 删除指定排名内的升序元素 | zremrangerank key start end | |
| 删除指定分数范围的成员 | zremrangebyscore key min max | |
**集合间操作**
| 作用 | 格式 |
| ---- | ------------------------------------------------------------ |
| 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
- destination 交集计算结果保存到这个键。
- numkeys 需要做交集计算键的个数。
- key[key...] 需要做交集计算的键。
- weights weight[weight...] 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重 每个键的权重默认是1。
- aggregate sum|min|max 计算成员交集后, 分值可以按照sum 、min最小值 、 max最大值 做汇总, 默认值是sum。
### 1.7 键管理
#### 1.7.1 单个键管理
##### 1.键重命名
**rename key newkey**
为了防止被强行rename Redis提供了renamenx命令 确保只有newKey不存在时候才被覆盖。
##### 2. 随机返回键
**random key**
##### 3.键过期
- expire key seconds 键在seconds秒后过期。
- expireat key timestamp 键在秒级时间戳timestamp后过期。
- pexpire key milliseconds 键在milliseconds毫秒后过期。
- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期
注意:
1. 如果expire key的键不存在 返回结果为0
2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样
3. persist key t命令可以将键的过期时间清除
4. 对于字符串类型键, 执行set命令会去掉过期时间 这个问题很容易在开发中被忽视
5. Redis不支持二级数据结构例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
6. setex命令作为set+expire的组合 不但是原子执行, 同时减少了一次网络通讯的时间
#### 1.7.2 键遍历
##### 1. 全量键遍历
**keys pattern**
##### 2. 渐进式遍历
scan cursor \[match pattern] \[count number]
- cursor是必需参数 实际上cursor是一个游标 第一次遍历从0开始 每次scan遍历完都会返回当前游标的值 直到游标值为0 表示遍历结束。
- match pattern是可选参数 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。
- count number是可选参数 它的作用是表明每次要遍历的键个数, 默认值是10 此参数可以适当增大。
#### 1.7.3 数据库管理
##### 1.切换数据库
**select dbIndex**
##### 2.flushdb/flushall
flushdb/flushall命令用于清除数据库 两者的区别的是flushdb只清除当前数据库 flushall会清除所有数据库。

View File

@ -1,230 +1,242 @@
# spring boot 整合 servlet
## 一、说明
#### 1.1 项目结构说明
1. 项目提供与servlet整合的两种方式一种是servlet3.0 原生的注解方式一种是采用spring 注册的方式;
2. servlet、过滤器、监听器分别位于servlet、filter、listen 下其中以Annotation命名结尾的代表是servlet注解方式实现采用spring注册方式则在ServletConfig中进行注册
3. 为了说明外置容器对servlet注解的自动发现机制项目采用外置容器构建关于spring boot 整合外置容器的详细说明可以参考[spring-boot-tomcat](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-tomcat)
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-servlet.png"/> </div>
#### 1.2 项目依赖
```xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除依赖 使用外部tomcat容器启动-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!--servlet api 注解依赖包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
```
## 二、采用spring 注册方式整合 servlet
#### 2.1 新建过滤器、监听器和servlet
```java
/**
* @author : heibaiying
* @description : 自定义过滤器
*/
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setAttribute("filterParam","我是filter传递的参数");
chain.doFilter(request,response);
response.getWriter().append(" CustomFilter ");
}
@Override
public void destroy() {
}
}
```
```java
/**
* @author : heibaiying
* @description : 自定义监听器
*/
public class CustomListen implements ServletContextListener {
//Web应用程序初始化过程正在启动的通知
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("容器初始化启动");
}
# spring boot 整合 servlet
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目依赖">1.2 项目依赖</a><br/>
<a href="#二采用spring-注册方式整合-servlet">二、采用spring 注册方式整合 servlet</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-新建过滤器监听器和servlet">2.1 新建过滤器、监听器和servlet</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-注册过滤器监听器和servlet">2.2 注册过滤器、监听器和servlet</a><br/>
<a href="#三采用注解方式整合-servlet">三、采用注解方式整合 servlet</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-新建过滤器监听器和servlet分别使用@WebFilter@WebListener@WebServlet注解标注">3.1 新建过滤器、监听器和servlet,分别使用@WebFilter@WebListener@WebServlet注解标注</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-使注解生效">3.2 使注解生效</a><br/>
## 正文<br/>
/* 通知servlet上下文即将关闭
* 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
*/
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("容器即将销毁");
}
}
```
```java
/**
* @author : heibaiying
* @description : 自定义servlet
*/
public class CustomServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet 执行:" + req.getAttribute("filterParam"));
resp.getWriter().append("CustomServlet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
```
#### 2.2 注册过滤器、监听器和servlet
```java
/**
* @author : heibaiying
*/
@Configuration
public class ServletConfig {
@Bean
public ServletRegistrationBean registrationBean() {
return new ServletRegistrationBean<HttpServlet>(new CustomServlet(), "/servlet");
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean bean = new FilterRegistrationBean<Filter>();
bean.setFilter(new CustomFilter());
bean.addUrlPatterns("/servlet");
return bean;
}
@Bean
public ServletListenerRegistrationBean listenerRegistrationBean() {
return new ServletListenerRegistrationBean<ServletContextListener>(new CustomListen());
}
}
```
## 三、采用注解方式整合 servlet
#### 3.1 新建过滤器、监听器和servlet,分别使用@WebFilter@WebListener@WebServlet注解标注
```java
/**
* @author : heibaiying
* @description : 自定义过滤器
*/
@WebFilter(urlPatterns = "/servletAnn")
public class CustomFilterAnnotation implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request,response);
response.getWriter().append(" CustomFilter Annotation");
}
@Override
public void destroy() {
}
}
```
```java
/**
* @author : heibaiying
* @description :自定义监听器
*/
@WebListener
public class CustomListenAnnotation implements ServletContextListener {
//Web应用程序初始化过程正在启动的通知
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("容器初始化启动 Annotation");
}
/* 通知servlet上下文即将关闭
* 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
*/
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("容器即将销毁 Annotation");
}
}
```
```java
/**
* @author : heibaiying
* @description : 自定义servlet
*/
@WebServlet(urlPatterns = "/servletAnn")
public class CustomServletAnnotation extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().append("CustomServlet Annotation");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
```
#### 3.2 使注解生效
1. 如果是内置容器,需要在启动类上添加@ServletComponentScan("com.heibaiying.springbootservlet") ,指定扫描的包目录;
## 一、说明
#### 1.1 项目结构说明
1. 项目提供与servlet整合的两种方式一种是servlet3.0 原生的注解方式一种是采用spring 注册的方式;
2. servlet、过滤器、监听器分别位于servlet、filter、listen 下其中以Annotation命名结尾的代表是servlet注解方式实现采用spring注册方式则在ServletConfig中进行注册
3. 为了说明外置容器对servlet注解的自动发现机制项目采用外置容器构建关于spring boot 整合外置容器的详细说明可以参考[spring-boot-tomcat](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-tomcat)
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-servlet.png"/> </div>
#### 1.2 项目依赖
```xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除依赖 使用外部tomcat容器启动-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!--servlet api 注解依赖包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
```
## 二、采用spring 注册方式整合 servlet
#### 2.1 新建过滤器、监听器和servlet
```java
/**
* @author : heibaiying
* @description : 自定义过滤器
*/
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setAttribute("filterParam","我是filter传递的参数");
chain.doFilter(request,response);
response.getWriter().append(" CustomFilter ");
}
@Override
public void destroy() {
}
}
```
```java
/**
* @author : heibaiying
* @description : 自定义监听器
*/
public class CustomListen implements ServletContextListener {
//Web应用程序初始化过程正在启动的通知
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("容器初始化启动");
}
/* 通知servlet上下文即将关闭
* 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
*/
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("容器即将销毁");
}
}
```
```java
/**
* @author : heibaiying
* @description : 自定义servlet
*/
public class CustomServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet 执行:" + req.getAttribute("filterParam"));
resp.getWriter().append("CustomServlet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
```
#### 2.2 注册过滤器、监听器和servlet
```java
/**
* @author : heibaiying
*/
@Configuration
public class ServletConfig {
@Bean
public ServletRegistrationBean registrationBean() {
return new ServletRegistrationBean<HttpServlet>(new CustomServlet(), "/servlet");
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean bean = new FilterRegistrationBean<Filter>();
bean.setFilter(new CustomFilter());
bean.addUrlPatterns("/servlet");
return bean;
}
@Bean
public ServletListenerRegistrationBean listenerRegistrationBean() {
return new ServletListenerRegistrationBean<ServletContextListener>(new CustomListen());
}
}
```
## 三、采用注解方式整合 servlet
#### 3.1 新建过滤器、监听器和servlet,分别使用@WebFilter@WebListener@WebServlet注解标注
```java
/**
* @author : heibaiying
* @description : 自定义过滤器
*/
@WebFilter(urlPatterns = "/servletAnn")
public class CustomFilterAnnotation implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request,response);
response.getWriter().append(" CustomFilter Annotation");
}
@Override
public void destroy() {
}
}
```
```java
/**
* @author : heibaiying
* @description :自定义监听器
*/
@WebListener
public class CustomListenAnnotation implements ServletContextListener {
//Web应用程序初始化过程正在启动的通知
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("容器初始化启动 Annotation");
}
/* 通知servlet上下文即将关闭
* 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
*/
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("容器即将销毁 Annotation");
}
}
```
```java
/**
* @author : heibaiying
* @description : 自定义servlet
*/
@WebServlet(urlPatterns = "/servletAnn")
public class CustomServletAnnotation extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().append("CustomServlet Annotation");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
```
#### 3.2 使注解生效
1. 如果是内置容器,需要在启动类上添加@ServletComponentScan("com.heibaiying.springbootservlet") ,指定扫描的包目录;
2. 如果是外置容器不需要进行任何配置依靠容器内建的discovery机制自动发现需要说明的是这里的容器必须支持servlet3.0tomcat从7.0版本开始支持Servlet3.0)。

View File

@ -1,101 +1,110 @@
# spring boot 整合 tomcat
## 一、说明
#### 1.1 项目结构说明
spring boot 整合 tomcat 后支持jsp 的使用内置容器默认是不支持jsp所以项目整合后采用jspController 跳转到show.jsp测试整合是否成功。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-tomcat.png"/> </div>
#### 1.2 项目主要依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除内置容器依赖 使用外部tomcat容器启动-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
```
## 二、整合 tomcat
#### 2.1 修改启动类继承自SpringBootServletInitializer并覆盖重写其中configure方法
```java
/**
* 如果用外置tomcat,启动报错java.lang.NoClassDefFoundError: javax/el/ELManager
* 是因为tomcat 7.0 el-api包中没有ELManager类 , 切换tomcat 为8.0 以上版本即可
*/
@SpringBootApplication
public class SpringBootTomcatApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(SpringBootTomcatApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringBootTomcatApplication.class, args);
}
}
```
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀
```yml
spring:
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
```
#### 2.3 新建controller和show.jsp 测试整合是否成功
```java
@Controller
@RequestMapping("index")
public class JspController {
@RequestMapping
public String jsp(Model model){
Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
model.addAttribute("programmer",programmer);
return "show";
}
}
```
```jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>programmer</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
</head>
<body>
<ul>
<li>姓名: ${programmer.name}</li>
<li>年龄: ${programmer.age}</li>
</ul>
</body>
</html>
```
# spring boot 整合 tomcat
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
<a href="#二整合-tomcat">二、整合 tomcat</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-修改启动类继承自SpringBootServletInitializer并覆盖重写其中configure方法">2.1 修改启动类继承自SpringBootServletInitializer并覆盖重写其中configure方法</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-在applicationyml-中指定访问视图文件的前缀和后缀">2.2 在application.yml 中指定访问视图文件的前缀和后缀 </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-新建controller和showjsp-测试整合是否成功">2.3 新建controller和show.jsp 测试整合是否成功</a><br/>
## 正文<br/>
## 一、说明
#### 1.1 项目结构说明
spring boot 整合 tomcat 后支持jsp 的使用内置容器默认是不支持jsp所以项目整合后采用jspController 跳转到show.jsp测试整合是否成功。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-tomcat.png"/> </div>
#### 1.2 项目主要依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除内置容器依赖 使用外部tomcat容器启动-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
```
## 二、整合 tomcat
#### 2.1 修改启动类继承自SpringBootServletInitializer并覆盖重写其中configure方法
```java
/**
* 如果用外置tomcat,启动报错java.lang.NoClassDefFoundError: javax/el/ELManager
* 是因为tomcat 7.0 el-api包中没有ELManager类 , 切换tomcat 为8.0 以上版本即可
*/
@SpringBootApplication
public class SpringBootTomcatApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(SpringBootTomcatApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringBootTomcatApplication.class, args);
}
}
```
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀
```yml
spring:
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
```
#### 2.3 新建controller和show.jsp 测试整合是否成功
```java
@Controller
@RequestMapping("index")
public class JspController {
@RequestMapping
public String jsp(Model model){
Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
model.addAttribute("programmer",programmer);
return "show";
}
}
```
```jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>programmer</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
</head>
<body>
<ul>
<li>姓名: ${programmer.name}</li>
<li>年龄: ${programmer.age}</li>
</ul>
</body>
</html>
```

View File

@ -1,183 +1,193 @@
# spring boot websocket
## 一、说明
### 1.1 项目结构说明
1. 项目模拟一个简单的群聊功能为区分不同的聊天客户端登录时候将临时用户名存储在session当中
2. 关于websocket的主要配置在websocket文件夹下
3. 模板引擎采用freemaker
4. 项目以web的方式构建。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-websocket.png"/> </div>
### 1.2 主要依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring boot webSocket 的依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
```
## 二、spring boot websocket
#### 2.1 创建消息处理类ChatSocket使用@ServerEndpoint声明websocket服务
```java
@ServerEndpoint(value = "/socket/{username}")
@Component
public class ChatSocket {
/**
* 建立连接时候触发
*/
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
// 这个方法是线程不安全的
Constant.nameAndSession.putIfAbsent(username, session);
}
/**
* 关闭连接时候触发
*/
@OnClose
public void onClose(Session session, @PathParam("username") String username) {
Constant.nameAndSession.remove(username);
}
/**
* 处理消息
*/
@OnMessage
public void onMessage(Session session, String message, @PathParam("username") String username) throws UnsupportedEncodingException {
// 防止中文乱码
String msg = URLDecoder.decode(message, "utf-8");
// 简单模拟群发消息
Constant.nameAndSession.forEach((s, webSocketSession)
-> {
try {
webSocketSession.getBasicRemote().sendText(username + " : " + msg);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
```
#### 2.2 配置ServerEndpointExporterServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务
```java
@Configuration
public class WebSocketConfig {
/***
* 检测{@link javax.websocket.server.ServerEndpointConfig}和{@link ServerEndpoint} 类型的bean
* 并在运行时使用标准Java WebSocket时注册。
* 我们在{@link com.heibaiying.springboot.websocket.WebSocketConfig}中就是使用@ServerEndpoint去声明websocket服务
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
```
#### 2.3 前端websocket的实现
```jsp
<!doctype html>
<html lang="en">
<head>
<title>${Session["username"]}您好!欢迎进入群聊大厅!</title>
</head>
<body>
<h5>${Session["username"]}您好!欢迎进入群聊大厅!</h5>
<input id="message" type="text">
<button id="btn">发送消息</button>
<div id="show">
</div>
<script>
let btn = document.getElementById("btn");
let message = document.getElementById("message");
let show = document.getElementById("show");
let ws = new WebSocket("ws://localhost:8080/socket/${Session["username"]}");
ws.onmessage = function (evt) {
let node = document.createElement("div");
node.innerHTML = "<h5>" + evt.data + "</h5>";
show.appendChild(node);
};
btn.addEventListener("click", function () {
let data = message.value;
console.log(data);
if (data) {
ws.send(encodeURI(data));
} else {
alert("请输入消息后发送");
}
message.value = "";
});
// 关闭页面时候关闭ws
window.addEventListener("beforeunload", function (event) {
ws.close();
});
</script>
</body>
</html>
```
#### 2.4 简单登录的实现
```java
<!doctype html>
<html lang="en">
<head>
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
<input name="username" type="text">
<button id="btn">输入临时用户名后登录!</button>
</form>
</body>
</html>
```
```java
/**
* @description : 简单登录
*/
@Controller
public class LoginController {
@PostMapping("login")
public String login(String username, HttpSession session) {
session.setAttribute(Constant.USER_NAME, username);
return "chat";
}
@GetMapping
public String index() {
return "index";
}
}
```
# spring boot websocket
## 目录<br/>
<a href="#一说明">一、说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-主要依赖">1.2 主要依赖</a><br/>
<a href="#二spring-boot-websocket">二、spring boot websocket</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-创建消息处理类ChatSocket使用@ServerEndpoint声明websocket服务">2.1 创建消息处理类ChatSocket使用@ServerEndpoint声明websocket服务</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-配置ServerEndpointExporterServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务。">2.2 配置ServerEndpointExporterServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-前端websocket的实现">2.3 前端websocket的实现</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#24-简单登录的实现">2.4 简单登录的实现</a><br/>
## 正文<br/>
## 一、说明
### 1.1 项目结构说明
1. 项目模拟一个简单的群聊功能为区分不同的聊天客户端登录时候将临时用户名存储在session当中
2. 关于websocket的主要配置在websocket文件夹下
3. 模板引擎采用freemaker
4. 项目以web的方式构建。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-websocket.png"/> </div>
### 1.2 主要依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring boot webSocket 的依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
```
## 二、spring boot websocket
#### 2.1 创建消息处理类ChatSocket使用@ServerEndpoint声明websocket服务
```java
@ServerEndpoint(value = "/socket/{username}")
@Component
public class ChatSocket {
/**
* 建立连接时候触发
*/
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
// 这个方法是线程不安全的
Constant.nameAndSession.putIfAbsent(username, session);
}
/**
* 关闭连接时候触发
*/
@OnClose
public void onClose(Session session, @PathParam("username") String username) {
Constant.nameAndSession.remove(username);
}
/**
* 处理消息
*/
@OnMessage
public void onMessage(Session session, String message, @PathParam("username") String username) throws UnsupportedEncodingException {
// 防止中文乱码
String msg = URLDecoder.decode(message, "utf-8");
// 简单模拟群发消息
Constant.nameAndSession.forEach((s, webSocketSession)
-> {
try {
webSocketSession.getBasicRemote().sendText(username + " : " + msg);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
```
#### 2.2 配置ServerEndpointExporterServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务
```java
@Configuration
public class WebSocketConfig {
/***
* 检测{@link javax.websocket.server.ServerEndpointConfig}和{@link ServerEndpoint} 类型的bean
* 并在运行时使用标准Java WebSocket时注册。
* 我们在{@link com.heibaiying.springboot.websocket.WebSocketConfig}中就是使用@ServerEndpoint去声明websocket服务
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
```
#### 2.3 前端websocket的实现
```jsp
<!doctype html>
<html lang="en">
<head>
<title>${Session["username"]}您好!欢迎进入群聊大厅!</title>
</head>
<body>
<h5>${Session["username"]}您好!欢迎进入群聊大厅!</h5>
<input id="message" type="text">
<button id="btn">发送消息</button>
<div id="show">
</div>
<script>
let btn = document.getElementById("btn");
let message = document.getElementById("message");
let show = document.getElementById("show");
let ws = new WebSocket("ws://localhost:8080/socket/${Session["username"]}");
ws.onmessage = function (evt) {
let node = document.createElement("div");
node.innerHTML = "<h5>" + evt.data + "</h5>";
show.appendChild(node);
};
btn.addEventListener("click", function () {
let data = message.value;
console.log(data);
if (data) {
ws.send(encodeURI(data));
} else {
alert("请输入消息后发送");
}
message.value = "";
});
// 关闭页面时候关闭ws
window.addEventListener("beforeunload", function (event) {
ws.close();
});
</script>
</body>
</html>
```
#### 2.4 简单登录的实现
```java
<!doctype html>
<html lang="en">
<head>
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
<input name="username" type="text">
<button id="btn">输入临时用户名后登录!</button>
</form>
</body>
</html>
```
```java
/**
* @description : 简单登录
*/
@Controller
public class LoginController {
@PostMapping("login")
public String login(String username, HttpSession session) {
session.setAttribute(Constant.USER_NAME, username);
return "chat";
}
@GetMapping
public String index() {
return "index";
}
}
```

View File

@ -1,177 +1,193 @@
# spring-boot-yml-profile
## 一、项目结构
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-yml-profile.png"/> </div>
## 二、常用 yaml 语法讲解
项目中的yml配置文件如下
```yaml
programmer:
name: xiaoming-DEV
married: false
hireDate: 2018/12/23
salary: 66666.88
random: ${random.int[1024,65536]}
skill: {java: master, jquery: proficiency}
company: [baidu,tengxun,alibaba]
school:
name: unviersity
location: shanghai
```
#### 2.1 基本规则
1. 大小写敏感
2. 使用缩进表示层级关系
3. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。
4. 使用#表示注释
5. 字符串默认不用加单双引号,但单引号和双引号都可以使用,双引号不会对特殊字符转义。
6. YAML中提供了多种常量结构包括整数浮点数字符串NULL日期布尔时间。
#### 2.2 对象的写法
```yaml
key: value
```
#### 2.3 map的写法
```yaml
# 写法一 同一缩进的所有键值对属于一个map
key:
key1: value1
key2: value2
# 写法二
{key1: value1, key2: value2}
```
#### 2.3 数组的写法
```yaml
# 写法一 使用一个短横线加一个空格代表一个数组项
- a
- b
- c
# 写法二
[a,b,c]
```
#### 2.5 单双引号
单引号和双引号都可以使用,双引号不会对特殊字符转义。
```yaml
s1: '内容\n字符串'
s2: "内容\n字符串"
转换后:
{ s1: '内容\\n字符串', s2: '内容\n字符串' }
```
#### 2.6 特殊符号
--- YAML可以在同一个文件中使用---表示一个文档的开始。
## 三、spring boot 与 yaml
#### 3.1 spring boot 支持使用 ${app.name} 引用预先定义的值
```properties
appName: MyApp
appDescription: ${app.name} is a Spring Boot application
```
#### 3.2 spring boot 支持使用 ${random.xxx} 配置随机值
```properties
my.secret: ${random.value}
my.number: ${random.int}
my.bignumber: ${random.long}
my.number.less.than.ten: ${random.int(10)}
my.number.in.range: ${random.int[1024,65536]}
```
## 四、@ConfigurationProperties实现属性绑定
```java
@Component
@ConfigurationProperties(prefix = "programmer")
@Data
@ToString
public class Programmer {
private String name;
private int age;
private boolean married;
private Date hireDate;
private float salary;
private int random;
private Map<String, String> skill;
private List company;
private School school;
}
```
Spring Boot将环境属性绑定到@ConfigurationProperties beans时会使用一些宽松的规则称之为松散绑定。所以Environment属性名和bean属性名不需要精确匹配。常见的示例中有用的包括虚线分割比如context-path绑定到contextPath将环境属性转为大写字母比如PORT绑定port
需要注意的是`@Value`是不支持松散绑定的,所以建议除非有特殊的需求,否则在`ConfigurationProperties``value` 配置属性的时候最好都保持属性和变量的一致,以免造成不必要的勿扰。
## 五、多配置文件
多配置文件可以在同一个yml中使用 --- 分割为多个配置或者遵循application-xxx.yml 的方式命名拆分为多个文件并在主配置文件application.yml 中确定激活哪个配置文件,当然也可在命令行中确定,命令行的优先级大于配置文件。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/profile.png"/> </div>
```yaml
# 配置文件中激活配置
spring:
profiles:
active: dev
```
```shell
# 命令行参数激活配置
--spring.profiles.active=dev
```
## 六、优先级的说明
Spring Boot设计了一个非常特别的PropertySource顺序以允许对属性值进行合理的覆盖属性会以如下的顺序进行设值
1. home目录下的devtools全局设置属性~/.spring-boot-devtools.properties如果devtools激活
2. 测试用例上的@TestPropertySource注解
3. 测试用例上的@SpringBootTest#properties注解
4. 命令行参数
5. 来自SPRING_APPLICATION_JSON的属性环境变量或系统属性中内嵌的内联JSON
6. ServletConfig初始化参数。
7. ServletContext初始化参数。
8. 来自于java:comp/env的JNDI属性。
9. Java系统属性System.getProperties())。
10. 操作系统环境变量。
11. RandomValuePropertySource只包含random.*中的属性。
12. 没有打进jar包的Profile-specific应用属性application-{profile}.properties和YAML变量
13. 打进jar包中的Profile-specific应用属性application-{profile}.properties和YAML变量
14. 没有打进jar包的应用配置application.properties和YAML变量
15. 打进jar包中的应用配置application.properties和YAML变量
16. @Configuration类上的@PropertySource注解
17. 默认属性使用SpringApplication.setDefaultProperties指定
这里做一下说明上文第12,14 点没有打进jar包的文件指的是在启动时候通过`spring.config.location`参数指定的外部配置文件外部配置文件的优先级应该是大于jar中的配置文件。
对上面的配置中常用的规则可以精简如下:
# spring-boot-yml-profile
## 目录<br/>
<a href="#一项目结构">一、项目结构</a><br/>
<a href="#二常用-yaml-语法讲解">二、常用 yaml 语法讲解</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-基本规则">2.1 基本规则</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-对象的写法">2.2 对象的写法</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-map的写法">2.3 map的写法</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-数组的写法">2.3 数组的写法</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#25-单双引号">2.5 单双引号</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#26-特殊符号">2.6 特殊符号</a><br/>
<a href="#三spring-boot-与-yaml">三、spring boot 与 yaml</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31--spring-boot-支持使用-${appname}-引用预先定义的值">3.1 spring boot 支持使用 ${app.name} 引用预先定义的值</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-spring-boot-支持使用-${randomxxx}-配置随机值">3.2 spring boot 支持使用 ${random.xxx} 配置随机值</a><br/>
<a href="#四@ConfigurationProperties实现属性绑定">四、@ConfigurationProperties实现属性绑定</a><br/>
<a href="#五多配置文件">五、多配置文件</a><br/>
<a href="#六优先级的说明">六、优先级的说明</a><br/>
## 正文<br/>
## 一、项目结构
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-yml-profile.png"/> </div>
## 二、常用 yaml 语法讲解
项目中的yml配置文件如下
```yaml
programmer:
name: xiaoming-DEV
married: false
hireDate: 2018/12/23
salary: 66666.88
random: ${random.int[1024,65536]}
skill: {java: master, jquery: proficiency}
company: [baidu,tengxun,alibaba]
school:
name: unviersity
location: shanghai
```
#### 2.1 基本规则
1. 大小写敏感
2. 使用缩进表示层级关系
3. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。
4. 使用#表示注释
5. 字符串默认不用加单双引号,但单引号和双引号都可以使用,双引号不会对特殊字符转义。
6. YAML中提供了多种常量结构包括整数浮点数字符串NULL日期布尔时间。
#### 2.2 对象的写法
```yaml
key: value
```
#### 2.3 map的写法
```yaml
# 写法一 同一缩进的所有键值对属于一个map
key:
key1: value1
key2: value2
# 写法二
{key1: value1, key2: value2}
```
#### 2.3 数组的写法
```yaml
# 写法一 使用一个短横线加一个空格代表一个数组项
- a
- b
- c
# 写法二
[a,b,c]
```
#### 2.5 单双引号
单引号和双引号都可以使用,双引号不会对特殊字符转义。
```yaml
s1: '内容\n字符串'
s2: "内容\n字符串"
转换后:
{ s1: '内容\\n字符串', s2: '内容\n字符串' }
```
#### 2.6 特殊符号
--- YAML可以在同一个文件中使用---表示一个文档的开始。
## 三、spring boot 与 yaml
#### 3.1 spring boot 支持使用 ${app.name} 引用预先定义的值
```properties
appName: MyApp
appDescription: ${app.name} is a Spring Boot application
```
#### 3.2 spring boot 支持使用 ${random.xxx} 配置随机值
```properties
my.secret: ${random.value}
my.number: ${random.int}
my.bignumber: ${random.long}
my.number.less.than.ten: ${random.int(10)}
my.number.in.range: ${random.int[1024,65536]}
```
## 四、@ConfigurationProperties实现属性绑定
```java
@Component
@ConfigurationProperties(prefix = "programmer")
@Data
@ToString
public class Programmer {
private String name;
private int age;
private boolean married;
private Date hireDate;
private float salary;
private int random;
private Map<String, String> skill;
private List company;
private School school;
}
```
Spring Boot将环境属性绑定到@ConfigurationProperties beans时会使用一些宽松的规则称之为松散绑定。所以Environment属性名和bean属性名不需要精确匹配。常见的示例中有用的包括虚线分割比如context-path绑定到contextPath将环境属性转为大写字母比如PORT绑定port
需要注意的是`@Value`是不支持松散绑定的,所以建议除非有特殊的需求,否则在`ConfigurationProperties``value` 配置属性的时候最好都保持属性和变量的一致,以免造成不必要的勿扰。
## 五、多配置文件
多配置文件可以在同一个yml中使用 --- 分割为多个配置或者遵循application-xxx.yml 的方式命名拆分为多个文件并在主配置文件application.yml 中确定激活哪个配置文件,当然也可在命令行中确定,命令行的优先级大于配置文件。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/profile.png"/> </div>
```yaml
# 配置文件中激活配置
spring:
profiles:
active: dev
```
```shell
# 命令行参数激活配置
--spring.profiles.active=dev
```
## 六、优先级的说明
Spring Boot设计了一个非常特别的PropertySource顺序以允许对属性值进行合理的覆盖属性会以如下的顺序进行设值
1. home目录下的devtools全局设置属性~/.spring-boot-devtools.properties如果devtools激活
2. 测试用例上的@TestPropertySource注解
3. 测试用例上的@SpringBootTest#properties注解
4. 命令行参数
5. 来自SPRING_APPLICATION_JSON的属性环境变量或系统属性中内嵌的内联JSON
6. ServletConfig初始化参数。
7. ServletContext初始化参数。
8. 来自于java:comp/env的JNDI属性。
9. Java系统属性System.getProperties())。
10. 操作系统环境变量。
11. RandomValuePropertySource只包含random.*中的属性。
12. 没有打进jar包的Profile-specific应用属性application-{profile}.properties和YAML变量
13. 打进jar包中的Profile-specific应用属性application-{profile}.properties和YAML变量
14. 没有打进jar包的应用配置application.properties和YAML变量
15. 打进jar包中的应用配置application.properties和YAML变量
16. @Configuration类上的@PropertySource注解
17. 默认属性使用SpringApplication.setDefaultProperties指定
这里做一下说明上文第12,14 点没有打进jar包的文件指的是在启动时候通过`spring.config.location`参数指定的外部配置文件外部配置文件的优先级应该是大于jar中的配置文件。
对上面的配置中常用的规则可以精简如下:
**命令行 > application-{profile}.yml > application.yml > 默认属性**

View File

@ -1,433 +1,455 @@
# spring-cloud-config
## 一、config 简介
spring cloud config 分为服务端和客户端服务端称为分布式配置中心集中管理配置文件客户端为各个业务单元它们从配置中心获取相关配置同时config 还实现了配置热更新,在服务不停机的情况下刷新配置。
## 二、项目结构
+ config-server: 配置中心;
+ config-client: 服务单元,可以从配置中心获取相关配置;
+ eureka: 注册中心。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-config.png"/> </div>
## 三、config-server 配置中心的实现
#### 3.1 导入依赖
```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>com.heibaiying.config</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>config-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
#### 3.2 在启动类上添加@EnableDiscoveryClient和@EnableConfigServer 注解
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
```
#### 3.3 指定注册中心地址,并配置git仓库地址的配置文件路径
```yaml
server:
port: 8020
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
# 指定服务命名
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/heibaiying/spring-samples-for-all/
search-paths: spring-cloud/spring-cloud-test-config/
# 如果代码仓库是公开的 则 不需要设置用户名和密码
username:
password:
# 指定拉取的配置文件的存放位置,配置文件最后存储的目录为 basedir + search-paths
# 这个地方还需要注意的是,配置文件的仓库最好只放配置文件
# 因为配置中心不仅会拉取search-paths下的文件还会把uri指定仓库中的全部文件拉取到basedir下
basedir: D:\git-config
# 指定分支
label: master
```
这里的git 仓库就是本用例的仓库,是公开的仓库,所以不用配置用户名和密码,配置文件如下
- application.yml 为主配置;
- application-dev.yml 为开发环境配置。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-git.png"/> </div>
#### 3.4 启动eureka和config-server服务访问 http://localhost:8020/application-dev.yml
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-application-dev.png"/> </div>
这里需要注意的拉取配置的时候我们此时指定拉取的是dev配置application.yml实际 配置如下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-dev.png"/> </div>
这说明在用配置中心拉取配置的时候和我们在本地开发的时候是一致的配置是互补的即dev中的实际配置应该是主配置和dev配置的结合且遵循同名属性精确优先的原则。
#### 3.5 http请求地址和资源文件映射
在本用例中如果我们想要直接访问主配置,用以下路径 http://localhost:8020/application.yml 是不行的,会得到错误页面。如果想要访问主配置,可以用http://localhost:8020/application-X.yml其中可以是任意字符原因是
请求地址和实际的配置文件应该遵循以下规则application为配置文件名profile 为环境label为分支如果不指定默认就是master分支
- /{application}/{profile}[/{label}]
- /{application}-{profile}.yml
- /{label}/{application}-{profile}.yml
- /{application}-{profile}.properties
- /{label}/{application}-{profile}.properties
访问主配置:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-a.png"/> </div>
## 四、config-client 搭建
#### 4.1 导入依赖
```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>com.heibaiying.config</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>config-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--config client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
#### 4.2 新建 `bootstrap.yml`配置文件,指定注册中心地址和配置中心服务名,并在启动类上开启自动注册@EnableDiscoveryClient
这里需要特别说明的是在之前的所有项目中我们采用的配置文件都是application.yml,但是这里**一定要采用bootstrap.yml**。
假设我们的数据库配置是放在远程配置中心的那么我们应该先去远程配置中心拉取配置然后再去进行数据库的自动化配置反之如果我们先进行了数据库的自动化配置那么就会因为找不到url或驱动而抛出异常。
- bootstrap.ymlbootstrap.properties用来程序引导时执行应用于更加早期配置信息读取bootstrap.yml 先于 application.yml 加载。
- application.ymlapplication.properties) 应用程序各个模块的配置信息。
```yaml
server:
port: 8030
spring:
application:
name: config-client
cloud:
config:
discovery:
enabled: true
# 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用的
serviceId: config-server
# 指定分支
label: master
# 指定环境
profile: dev
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
# 因为我们必须要先从注册中心去获取可用的配置中心, 从配置中心去拉取配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}
```
#### 4.3 创建配置映射类用于测试
```java
@Component
@ConfigurationProperties(prefix = "programmer")
@Data
@ToString
public class Programmer{
private String name;
private int age;
private boolean married;
private Date hireDate;
private float salary;
private int random;
private Map<String, String> skill;
private List company;
private School school;
}
```
```java
@RestController
public class ConfigController {
@Autowired
private Programmer programmer;
@RequestMapping("programmer")
public String getProgrammer() {
return programmer.toString();
}
}
```
#### 4.4 依次启动eureka,config-server,config-client ,访问 http://localhost:8030/programmer
这里需要注意是在启动eureka和config-server要稍等一会在启动config-client这里是为了确保config-server已经将服务注册到eureka然后我们的config-client才能从eureka中获取配置中心的服务。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-client-programmer.png"/> </div>
启动的时候可以从控制台看到如下拉取服务的信息:
```shell
Fetching config from server at : http://localhost:8020/
Located environment: name=config-client, profiles=[dev], label=master, version=50dcfb85cd751e4f28761cd6bad84c1f73034002, state=null
```
## 五、集成 spring-cloud-bus 实现配置热更新
#### 5.1 消息总线简介
在微服务的架构中我们通常想要构建一个共同的消息主题被所有微服务实例所监听以便对所有微服务实例的管理和通知这就是消息总线spring cloud bus 就是消息总线的一种实现。
目前spring cloud bus 支持的消息中间件有 RabbitMQ和kafka, 我们下面的整合采用的是RrabbitMQ。
关于热更新只需要对配置客户端config-client做更改不需要对config-server做改动。
#### 5.1 导入bus依赖
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!--因为要用到端点功能(主要是刷新端点),所以需要导入actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
#### 5.2 修改bootstrap.yml 配置开启总线配置配置rabbitmq 和 开启热刷新[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)
```yml
server:
port: 8030
spring:
application:
name: config-client
cloud:
config:
discovery:
enabled: true
# 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用
serviceId: config-server
# 指定分支
label: master
# 指定环境
profile: dev
bus:
#开启总线
enabled: true
# 打开ack跟踪的标志默认关闭
trace:
enabled: true
# 使用bus实现热更新
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
# 因为我们必须要先从注册中心去获取可用的配置中心, 然后从配置中心去拉取配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
# 暴露热刷新的端点
management:
endpoints:
web:
exposure:
include: bus-refresh
```
#### 5.3 用@RefreshScope指定需要热刷新的配置
```java
@Component
@ConfigurationProperties(prefix = "programmer")
@Data
@ToString
@RefreshScope // 定义下面配置热刷新范围
public class Programmer{
private String name;
private int age;
private boolean married;
private Date hireDate;
private float salary;
private int random;
private Map<String, String> skill;
private List company;
private School school;
}
```
#### 5.4 依次启动eurekaconfig-server, config-client 服务
在client服务端启动时候可以在控制台 看到bus 自动创建了交换机、队列等
```
Created new connection: rabbitConnectionFactory#496c6d94:22/SimpleConnection@185d85d2 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 63713]
o.s.amqp.rabbit.core.RabbitAdmin : Auto-declaring a non-durable, auto-delete, or exclusive Queue (springCloudBus.anonymous.iY4TIIi9TSe0bL-TWAMhWg) durable:false, auto-delete:true, exclusive:true. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.
```
也可以在 rabbitmq 管控台查看
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-exchange.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-queue.png"/> </div>
#### 5.6 直接在 git 上修改配置文件,然后用 `post` 触发热刷新端点 http://localhost:8030/actuator/bus-refresh ,即可看到配置已经热刷新
注意: 这里的只能用 post 方式请求 ,可以用 postman 等测试软件
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/bus-refresh.png"/> </div>
热刷新的过程在控制台有详细的打印,部分日志如下:
```shell
# 消息传播
Attempting to connect to: [127.0.0.1:5672]
Created new connection: rabbitConnectionFactory.publisher#b00f2d6:0/SimpleConnection@403c0406 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 62748]
# 从配置中心拉取配置文件
Fetching config from server at : http://DESKTOP-8JGSFLJ:8020/
# 刷新应用上下文 AnnotationConfigApplicationContext
Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@62e12f66
```
# spring-cloud-config
## 目录<br/>
<a href="#一config-简介">一、config 简介</a><br/>
<a href="#二项目结构">二、项目结构</a><br/>
<a href="#三config-server-配置中心的实现">三、config-server 配置中心的实现</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-导入依赖">3.1 导入依赖 </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-在启动类上添加@EnableDiscoveryClient和@EnableConfigServer-注解">3.2 在启动类上添加@EnableDiscoveryClient和@EnableConfigServer 注解</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33--指定注册中心地址并配置git仓库地址的配置文件路径">3.3 指定注册中心地址,并配置git仓库地址的配置文件路径</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#34--启动eureka和config-server服务访问-http//localhost8020/application-devyml">3.4 启动eureka和config-server服务访问 http://localhost:8020/application-dev.yml</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#35-http请求地址和资源文件映射">3.5 http请求地址和资源文件映射</a><br/>
<a href="#四config-client-搭建">四、config-client 搭建</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-导入依赖">4.1 导入依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-新建-`bootstrapyml`配置文件指定注册中心地址和配置中心服务名并在启动类上开启自动注册@EnableDiscoveryClient">4.2 新建 `bootstrap.yml`配置文件,指定注册中心地址和配置中心服务名,并在启动类上开启自动注册@EnableDiscoveryClient</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-创建配置映射类用于测试">4.3 创建配置映射类用于测试</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#44-依次启动eurekaconfig-serverconfig-client-访问-http//localhost8030/programmer">4.4 依次启动eureka,config-server,config-client ,访问 http://localhost:8030/programmer</a><br/>
<a href="#五集成-spring-cloud-bus-实现配置热更新">五、集成 spring-cloud-bus 实现配置热更新</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#51-消息总线简介">5.1 消息总线简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#51-导入bus依赖">5.1 导入bus依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#52-修改bootstrapyml-配置开启总线配置配置rabbitmq--和-开启热刷新[端点]https//githubcom/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator">5.2 修改bootstrap.yml 配置开启总线配置配置rabbitmq 和 开启热刷新[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#53-用@RefreshScope指定需要热刷新的配置">5.3 用@RefreshScope指定需要热刷新的配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#54-依次启动eurekaconfig-server-config-client-服务">5.4 依次启动eurekaconfig-server, config-client 服务</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#56--直接在-git-上修改配置文件然后用-`post`-触发热刷新端点-http//localhost8030/actuator/bus-refresh-即可看到配置已经热刷新">5.6 直接在 git 上修改配置文件,然后用 `post` 触发热刷新端点 http://localhost:8030/actuator/bus-refresh ,即可看到配置已经热刷新</a><br/>
## 正文<br/>
## 一、config 简介
spring cloud config 分为服务端和客户端服务端称为分布式配置中心集中管理配置文件客户端为各个业务单元它们从配置中心获取相关配置同时config 还实现了配置热更新,在服务不停机的情况下刷新配置。
## 二、项目结构
+ config-server: 配置中心;
+ config-client: 服务单元,可以从配置中心获取相关配置;
+ eureka: 注册中心。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-config.png"/> </div>
## 三、config-server 配置中心的实现
#### 3.1 导入依赖
```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>com.heibaiying.config</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>config-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
#### 3.2 在启动类上添加@EnableDiscoveryClient和@EnableConfigServer 注解
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
```
#### 3.3 指定注册中心地址,并配置git仓库地址的配置文件路径
```yaml
server:
port: 8020
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
# 指定服务命名
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/heibaiying/spring-samples-for-all/
search-paths: spring-cloud/spring-cloud-test-config/
# 如果代码仓库是公开的 则 不需要设置用户名和密码
username:
password:
# 指定拉取的配置文件的存放位置,配置文件最后存储的目录为 basedir + search-paths
# 这个地方还需要注意的是,配置文件的仓库最好只放配置文件
# 因为配置中心不仅会拉取search-paths下的文件还会把uri指定仓库中的全部文件拉取到basedir下
basedir: D:\git-config
# 指定分支
label: master
```
这里的git 仓库就是本用例的仓库,是公开的仓库,所以不用配置用户名和密码,配置文件如下
- application.yml 为主配置;
- application-dev.yml 为开发环境配置。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-git.png"/> </div>
#### 3.4 启动eureka和config-server服务访问 http://localhost:8020/application-dev.yml
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-application-dev.png"/> </div>
这里需要注意的拉取配置的时候我们此时指定拉取的是dev配置application.yml实际 配置如下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-dev.png"/> </div>
这说明在用配置中心拉取配置的时候和我们在本地开发的时候是一致的配置是互补的即dev中的实际配置应该是主配置和dev配置的结合且遵循同名属性精确优先的原则。
#### 3.5 http请求地址和资源文件映射
在本用例中如果我们想要直接访问主配置,用以下路径 http://localhost:8020/application.yml 是不行的,会得到错误页面。如果想要访问主配置,可以用http://localhost:8020/application-X.yml其中可以是任意字符原因是
请求地址和实际的配置文件应该遵循以下规则application为配置文件名profile 为环境label为分支如果不指定默认就是master分支
- /{application}/{profile}[/{label}]
- /{application}-{profile}.yml
- /{label}/{application}-{profile}.yml
- /{application}-{profile}.properties
- /{label}/{application}-{profile}.properties
访问主配置:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-a.png"/> </div>
## 四、config-client 搭建
#### 4.1 导入依赖
```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>com.heibaiying.config</groupId>
<artifactId>spring-cloud-config</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>config-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--config client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
#### 4.2 新建 `bootstrap.yml`配置文件,指定注册中心地址和配置中心服务名,并在启动类上开启自动注册@EnableDiscoveryClient
这里需要特别说明的是在之前的所有项目中我们采用的配置文件都是application.yml,但是这里**一定要采用bootstrap.yml**。
假设我们的数据库配置是放在远程配置中心的那么我们应该先去远程配置中心拉取配置然后再去进行数据库的自动化配置反之如果我们先进行了数据库的自动化配置那么就会因为找不到url或驱动而抛出异常。
- bootstrap.ymlbootstrap.properties用来程序引导时执行应用于更加早期配置信息读取bootstrap.yml 先于 application.yml 加载。
- application.ymlapplication.properties) 应用程序各个模块的配置信息。
```yaml
server:
port: 8030
spring:
application:
name: config-client
cloud:
config:
discovery:
enabled: true
# 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用的
serviceId: config-server
# 指定分支
label: master
# 指定环境
profile: dev
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
# 因为我们必须要先从注册中心去获取可用的配置中心, 从配置中心去拉取配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}
```
#### 4.3 创建配置映射类用于测试
```java
@Component
@ConfigurationProperties(prefix = "programmer")
@Data
@ToString
public class Programmer{
private String name;
private int age;
private boolean married;
private Date hireDate;
private float salary;
private int random;
private Map<String, String> skill;
private List company;
private School school;
}
```
```java
@RestController
public class ConfigController {
@Autowired
private Programmer programmer;
@RequestMapping("programmer")
public String getProgrammer() {
return programmer.toString();
}
}
```
#### 4.4 依次启动eureka,config-server,config-client ,访问 http://localhost:8030/programmer
这里需要注意是在启动eureka和config-server要稍等一会在启动config-client这里是为了确保config-server已经将服务注册到eureka然后我们的config-client才能从eureka中获取配置中心的服务。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-client-programmer.png"/> </div>
启动的时候可以从控制台看到如下拉取服务的信息:
```shell
Fetching config from server at : http://localhost:8020/
Located environment: name=config-client, profiles=[dev], label=master, version=50dcfb85cd751e4f28761cd6bad84c1f73034002, state=null
```
## 五、集成 spring-cloud-bus 实现配置热更新
#### 5.1 消息总线简介
在微服务的架构中我们通常想要构建一个共同的消息主题被所有微服务实例所监听以便对所有微服务实例的管理和通知这就是消息总线spring cloud bus 就是消息总线的一种实现。
目前spring cloud bus 支持的消息中间件有 RabbitMQ和kafka, 我们下面的整合采用的是RrabbitMQ。
关于热更新只需要对配置客户端config-client做更改不需要对config-server做改动。
#### 5.1 导入bus依赖
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!--因为要用到端点功能(主要是刷新端点),所以需要导入actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
#### 5.2 修改bootstrap.yml 配置开启总线配置配置rabbitmq 和 开启热刷新[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)
```yml
server:
port: 8030
spring:
application:
name: config-client
cloud:
config:
discovery:
enabled: true
# 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用
serviceId: config-server
# 指定分支
label: master
# 指定环境
profile: dev
bus:
#开启总线
enabled: true
# 打开ack跟踪的标志默认关闭
trace:
enabled: true
# 使用bus实现热更新
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
# 因为我们必须要先从注册中心去获取可用的配置中心, 然后从配置中心去拉取配置
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
# 暴露热刷新的端点
management:
endpoints:
web:
exposure:
include: bus-refresh
```
#### 5.3 用@RefreshScope指定需要热刷新的配置
```java
@Component
@ConfigurationProperties(prefix = "programmer")
@Data
@ToString
@RefreshScope // 定义下面配置热刷新范围
public class Programmer{
private String name;
private int age;
private boolean married;
private Date hireDate;
private float salary;
private int random;
private Map<String, String> skill;
private List company;
private School school;
}
```
#### 5.4 依次启动eurekaconfig-server, config-client 服务
在client服务端启动时候可以在控制台 看到bus 自动创建了交换机、队列等
```
Created new connection: rabbitConnectionFactory#496c6d94:22/SimpleConnection@185d85d2 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 63713]
o.s.amqp.rabbit.core.RabbitAdmin : Auto-declaring a non-durable, auto-delete, or exclusive Queue (springCloudBus.anonymous.iY4TIIi9TSe0bL-TWAMhWg) durable:false, auto-delete:true, exclusive:true. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.
```
也可以在 rabbitmq 管控台查看
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-exchange.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-queue.png"/> </div>
#### 5.6 直接在 git 上修改配置文件,然后用 `post` 触发热刷新端点 http://localhost:8030/actuator/bus-refresh ,即可看到配置已经热刷新
注意: 这里的只能用 post 方式请求 ,可以用 postman 等测试软件
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/bus-refresh.png"/> </div>
热刷新的过程在控制台有详细的打印,部分日志如下:
```shell
# 消息传播
Attempting to connect to: [127.0.0.1:5672]
Created new connection: rabbitConnectionFactory.publisher#b00f2d6:0/SimpleConnection@403c0406 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 62748]
# 从配置中心拉取配置文件
Fetching config from server at : http://DESKTOP-8JGSFLJ:8020/
# 刷新应用上下文 AnnotationConfigApplicationContext
Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@62e12f66
```

View File

@ -1,5 +1,22 @@
# eureka 高可用注册中心的搭建
## 目录<br/>
<a href="#一项目结构">一、项目结构</a><br/>
<a href="#二三步搭建eureka-高可用注册中心">二、三步搭建eureka 高可用注册中心</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-引入eureka服务端依赖">2.1 引入eureka服务端依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22--创建三份配置文件分别代表不同注册中心的配置">2.2 创建三份配置文件,分别代表不同注册中心的配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置">2.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置</a><br/>
<a href="#三三步搭建eureka-客户端">三、三步搭建eureka 客户端</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-引入eureka客户端依赖">3.1 引入eureka客户端依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-eureka-客户端配置指定注册中心地址">3.2 eureka 客户端配置,指定注册中心地址</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置">3.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置</a><br/>
<a href="#4启动项目">4.启动项目 </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-这里我们可以采用命令行方式指定配置分别启动三个注册中心">4.1 这里我们可以采用命令行方式指定配置,分别启动三个注册中心</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42--高可用集群搭建成功的判定">4.2 高可用集群搭建成功的判定</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#**421--点击下面注册中心的可用实例列表中的地址访问链接分以下几个情况**">4.2.1 点击下面注册中心的可用实例列表中的地址,访问链接分以下几个情况:</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43--prefer-ip-address-参数说明">4.3 prefer-ip-address 参数说明</a><br/>
## 正文<br/>
## 一、项目结构
eureka-server为服务注册中心负责服务的管理
@ -155,7 +172,7 @@ public class EurekaClientApplication {
这里需要主要的是仅仅status中出现其他注册中心时并不一定是搭建成功的**一定是当注册中心的DS Replicas 和 available replicas中显示其余的注册中心时候**,才代表搭建成功。
#### **4.2.1 点击下面注册中心的可用实例列表中的地址,访问链接分以下几个情况:**
#### 4.2.1 点击下面注册中心的可用实例列表中的地址,访问链接分以下几个情况:
1. hostname和prefer-ip-address都没有配置则访问 主机名:服务名:端口号,

View File

@ -1,112 +1,126 @@
# eureka 服务的注册与发现
# eureka 服务的注册与发现
## 目录<br/>
<a href="#一eureka-简介">一、eureka 简介</a><br/>
<a href="#二项目结构">二、项目结构</a><br/>
<a href="#三三步搭建eureka-服务注册中心">三、三步搭建eureka 服务注册中心</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-引入eureka服务端依赖">3.1 引入eureka服务端依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-eureka-服务端配置">3.2 eureka 服务端配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置">3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置</a><br/>
<a href="#四三步搭建eureka-客户端">四、三步搭建eureka 客户端</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-引入eureka客户端依赖">4.1 引入eureka客户端依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-eureka-客户端配置">4.2 eureka 客户端配置</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置">4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置</a><br/>
<a href="#五启动项目">五、启动项目 </a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#51-进入注册中心控制台查看服务注册情况">5.1 进入注册中心控制台,查看服务注册情况</a><br/>
## 正文<br/>
## 一、eureka 简介
Spring Cloud Eureka使用Netflix Eureka来实现服务注册与发现它既包含了服务端组件也包含了客户端组件。
**Eureka服务端**:服务的注册中心,负责维护注册的服务列表。
**Eureka客户端** 在应用程序运行时Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。同时它也能把从服务端查询到服务信息缓存到本地并周期性地刷新服务状态。
## 二、项目结构
eureka-server为服务注册中心负责服务的管理
eureka-client 为eureka客户端
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-eureka.png"/> </div>
## 三、三步搭建eureka 服务注册中心
#### 3.1 引入eureka服务端依赖
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
```
#### 3.2 eureka 服务端配置
```yaml
server:
port: 8010
eureka:
instance:
hostname: localhost
client:
# 设置为false,代表不向注册中心注册自己
register-with-eureka: false
# 注册中心主要用于维护服务并不需要检索服务所以设置为false
fetch-registry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
```
#### 3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置
```java
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
```
## 四、三步搭建eureka 客户端
#### 4.1 引入eureka客户端依赖
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
```
#### 4.2 eureka 客户端配置
```yaml
server:
port: 8020
# 指定服务命名
spring:
application:
name: eureka-client
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
#### 4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置
```java
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
```
## 五、启动项目
#### 5.1 进入注册中心控制台,查看服务注册情况
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/eureka.png"/> </div>
## 一、eureka 简介
Spring Cloud Eureka使用Netflix Eureka来实现服务注册与发现它既包含了服务端组件也包含了客户端组件。
**Eureka服务端**:服务的注册中心,负责维护注册的服务列表。
**Eureka客户端** 在应用程序运行时Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。同时它也能把从服务端查询到服务信息缓存到本地并周期性地刷新服务状态。
## 二、项目结构
eureka-server为服务注册中心负责服务的管理
eureka-client 为eureka客户端
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-eureka.png"/> </div>
## 三、三步搭建eureka 服务注册中心
#### 3.1 引入eureka服务端依赖
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
```
#### 3.2 eureka 服务端配置
```yaml
server:
port: 8010
eureka:
instance:
hostname: localhost
client:
# 设置为false,代表不向注册中心注册自己
register-with-eureka: false
# 注册中心主要用于维护服务并不需要检索服务所以设置为false
fetch-registry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
```
#### 3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置
```java
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
```
## 四、三步搭建eureka 客户端
#### 4.1 引入eureka客户端依赖
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
```
#### 4.2 eureka 客户端配置
```yaml
server:
port: 8020
# 指定服务命名
spring:
application:
name: eureka-client
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
#### 4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置
```java
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
```
## 五、启动项目
#### 5.1 进入注册中心控制台,查看服务注册情况
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/eureka.png"/> </div>

View File

@ -1,378 +1,400 @@
# spring-cloud-feign
## 一、feign 简介
在上一个用例中我们使用ribbon+restTemplate 实现服务之间的远程调用实际上每一个调用都是模板化的内容所以spring cloud Feign 在此基础上进行了进一步的封装。我们只需要定义一个接口并使用feign注解的方式来进行配置同时采用springMvc 注解进行参数绑定就可以完成服务的调用。feign同时还内置实现了负载均衡、服务容错等功能。
## 二、项目结构
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用feign调用产品服务
+ producer服务的提供者
+ eureka: 注册中心。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-feign.png"/> </div>
## 三、服务提供者的实现
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-producer.png"/> </div>
#### 3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。
ProductService.java
```java
/**
* @author : heibaiying
* @description : 产品提供接口实现类
*/
@Service
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {
private static List<Product> productList = new ArrayList<>();
public Product queryProductById(int id) {
return productList.stream().filter(p->p.getId()==id).collect(Collectors.toList()).get(0);
}
public List<Product> queryAllProducts() {
return productList;
}
@Override
public void saveProduct(Product product) {
productList.add(product);
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
int port = event.getWebServer().getPort();
for (long i = 0; i < 20; i++) {
productList.add(new Product(i, port + "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
}
}
}
```
ProducerController.java
```java
@RestController
public class ProducerController implements ProductFeign {
@Autowired
private IProductService productService;
@GetMapping("products")
public List<Product> productList() {
return productService.queryAllProducts();
}
@GetMapping("product/{id}")
public Product productDetail(@PathVariable int id) {
return productService.queryProductById(id);
}
@PostMapping("product")
public void save(@RequestBody Product product) {
productService.saveProduct(product);
}
}
```
#### 3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
```java
server:
port: 8020
# 指定服务命名
spring:
application:
name: producer
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
```
## 四、服务消费者的实现
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-consumer.png"/> </div>
#### 4.1 导入openfeign依赖
```xml
<!-- feign 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
```
#### 4.2 指定注册中心地址,并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients
@EnableFeignClients 会去扫描工程中所有用 @FeignClient 声明的 feign 客户端。
```java
server:
port: 8080
# 指定服务命名
spring:
application:
name: consumer
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
```
#### 4.3 创建服务调用公共接口
```java
/**
* @author : heibaiying
* @description : 声明式服务调用
*/
public interface ProductFeign {
@GetMapping("products")
List<Product> productList();
/**
* 这是需要强调的是使用feign时候@PathVariable一定要用value指明参数
* 不然会抛出.IllegalStateException: PathVariable annotation was empty on param 异常
*/
@GetMapping("product/{id}")
Product productDetail(@PathVariable(value = "id") int id);
@PostMapping("product")
void save(@RequestBody Product product);
}
```
按照官方对于服务最佳化的推荐,这里我们的服务调用接口放在公共模块中,因为在实际的开发中,同一个服务调用接口可能被多个模块所使用。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/common-feign.png"/> </div>
#### 4.4 继承公共接口创建CProductFeign@FeignClient声明为feign客户端
```java
/**
* @author : heibaiying
* @description : 声明式接口调用
*/
@FeignClient(value = "producer",configuration = FeignConfig.class)
public interface CProductFeign extends ProductFeign {
}
```
#### 4.5 注入使用 feign 服务调用接口
```java
@Controller
@RequestMapping("sell")
public class SellController {
@Autowired
private CProductFeign cproductFeign;
@GetMapping("products")
public String productList(Model model) {
List<Product> products = cproductFeign.productList();
model.addAttribute("products", products);
return "products";
}
@GetMapping("product/{id}")
public String productDetail(@PathVariable int id, Model model) {
Product product = cproductFeign.productDetail(id);
model.addAttribute("product", product);
return "product";
}
@PostMapping("product")
public String save(@RequestParam String productName) {
long id = Math.round(Math.random() * 100);
Product product = new Product(id, productName, false, new Date(), 88);
cproductFeign.save(product);
return "redirect:products";
}
}
```
## 五、启动测试
#### 5.1 启动一个Eureka服务、三个producer服务注意区分端口、和一个消费者服务
feign 的依赖中导入了spring-cloud-starter-netflix-ribbon依赖并且在内部实现了基于ribbon的客户端负载均衡所以我们这里启动三个producer实例来观察负载均衡的情况。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div>
**服务注册中心:**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-eureka.png"/> </div>
#### 5.2 访问http://localhost:8080/sell/products 查看负载均衡的调用结果
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8020.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8030.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-8040.png"/> </div>
## 六、 feign 的服务容错
#### 6.1 feign 的依赖中默认导入了hystrix 的相关依赖,我们不需要额外导入,只需要开启相关配置即可
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-hystrix-maven.png"/> </div>
#### 6.2 在application.yml 中开启hystrix
```yml
feign:
hystrix:
# 如果为true则OpenFign客户端将使用Hystrix断路器进行封装 默认为false
enabled: true
```
#### 6.3 创建`CProductFeignImpl`,继承feign接口CProductFeign定义熔断时候的回退处理
```java
/**
* @author : heibaiying
* @description : 定义发生熔断时候的回退处理。除了继承自CProductFeign,还需要用@Component声明为spring的组件
*/
@Component
public class CProductFeignImpl implements CProductFeign {
// 发生熔断时候,返回空集合,前端页面会做容错显示
@Override
public List<Product> productList() {
return new ArrayList<>();
}
@Override
public Product productDetail(int id) {
return null;
}
@Override
public void save(Product product) {
}
}
```
页面的简单容错处理:
```html
<!doctype html>
<html lang="en">
<head>
<title>产品列表</title>
</head>
<body>
<h3>产品列表:点击查看详情</h3>
<form action="/sell/product" method="post">
<input type="text" name="productName">
<input type="submit" value="新增产品">
</form>
<ul>
<#if (products?size>0) >
<#list products as product>
<li>
<a href="/sell/product/${product.id}">${product.name}</a>
</li>
</#list>
<#else>
<h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
</#if>
</ul>
</body>
</html>
```
#### 6.4 在 @FeignClient 注解中用fallback参数指定熔断时候的回退处理
```java
/**
* @author : heibaiying
* @description : 声明式接口调用
*/
@FeignClient(value = "producer",configuration = FeignConfig.class,fallback = CProductFeignImpl.class)
public interface CProductFeign extends ProductFeign {
}
```
#### 6.5 测试熔断处理
hystrix 默认调用超时时间为2s ,这里我们使用线程休眠的方式来模拟超时熔断。
```java
public List<Product> queryAllProducts() {
/*用于测试 hystrix 超时熔断
try {
int i = new Random().nextInt(2500);
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
return productList;
}
```
测试结果:
# spring-cloud-feign
## 目录<br/>
<a href="#一feign-简介">一、feign 简介</a><br/>
<a href="#二项目结构">二、项目结构</a><br/>
<a href="#三服务提供者的实现">三、服务提供者的实现</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-产品服务由`ProductService`提供并通过`ProducerController`将服务暴露给外部调用。">3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-指定注册中心地址并在启动类上开启自动注册@EnableDiscoveryClient">3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient</a><br/>
<a href="#四服务消费者的实现">四、服务消费者的实现</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-导入openfeign依赖">4.1 导入openfeign依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-指定注册中心地址并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients">4.2 指定注册中心地址,并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-创建服务调用公共接口">4.3 创建服务调用公共接口</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#44-继承公共接口创建CProductFeign-用@FeignClient声明为feign客户端">4.4 继承公共接口创建CProductFeign@FeignClient声明为feign客户端</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#45--注入使用-feign-服务调用接口">4.5 注入使用 feign 服务调用接口</a><br/>
<a href="#五启动测试">五、启动测试</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#51-启动一个Eureka服务三个producer服务注意区分端口和一个消费者服务">5.1 启动一个Eureka服务、三个producer服务注意区分端口、和一个消费者服务</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#52--访问http//localhost8080/sell/products-查看负载均衡的调用结果">5.2 访问http://localhost:8080/sell/products 查看负载均衡的调用结果</a><br/>
<a href="#六-feign-的服务容错">六、 feign 的服务容错</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#61-feign-的依赖中默认导入了hystrix-的相关依赖我们不需要额外导入只需要开启相关配置即可">6.1 feign 的依赖中默认导入了hystrix 的相关依赖,我们不需要额外导入,只需要开启相关配置即可</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#62-在applicationyml-中开启hystrix">6.2 在application.yml 中开启hystrix</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#63-创建`CProductFeignImpl`继承feign接口CProductFeign定义熔断时候的回退处理">6.3 创建`CProductFeignImpl`,继承feign接口CProductFeign定义熔断时候的回退处理</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#64-在-@FeignClient-注解中用fallback参数指定熔断时候的回退处理">6.4 在 @FeignClient 注解中用fallback参数指定熔断时候的回退处理</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#65-测试熔断处理">6.5 测试熔断处理</a><br/>
## 正文<br/>
## 一、feign 简介
在上一个用例中我们使用ribbon+restTemplate 实现服务之间的远程调用实际上每一个调用都是模板化的内容所以spring cloud Feign 在此基础上进行了进一步的封装。我们只需要定义一个接口并使用feign注解的方式来进行配置同时采用springMvc 注解进行参数绑定就可以完成服务的调用。feign同时还内置实现了负载均衡、服务容错等功能。
## 二、项目结构
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用feign调用产品服务
+ producer服务的提供者
+ eureka: 注册中心。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-feign.png"/> </div>
## 三、服务提供者的实现
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-producer.png"/> </div>
#### 3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。
ProductService.java
```java
/**
* @author : heibaiying
* @description : 产品提供接口实现类
*/
@Service
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {
private static List<Product> productList = new ArrayList<>();
public Product queryProductById(int id) {
return productList.stream().filter(p->p.getId()==id).collect(Collectors.toList()).get(0);
}
public List<Product> queryAllProducts() {
return productList;
}
@Override
public void saveProduct(Product product) {
productList.add(product);
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
int port = event.getWebServer().getPort();
for (long i = 0; i < 20; i++) {
productList.add(new Product(i, port + "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
}
}
}
```
ProducerController.java
```java
@RestController
public class ProducerController implements ProductFeign {
@Autowired
private IProductService productService;
@GetMapping("products")
public List<Product> productList() {
return productService.queryAllProducts();
}
@GetMapping("product/{id}")
public Product productDetail(@PathVariable int id) {
return productService.queryProductById(id);
}
@PostMapping("product")
public void save(@RequestBody Product product) {
productService.saveProduct(product);
}
}
```
#### 3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
```java
server:
port: 8020
# 指定服务命名
spring:
application:
name: producer
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
```
## 四、服务消费者的实现
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-consumer.png"/> </div>
#### 4.1 导入openfeign依赖
```xml
<!-- feign 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
```
#### 4.2 指定注册中心地址,并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients
@EnableFeignClients 会去扫描工程中所有用 @FeignClient 声明的 feign 客户端。
```java
server:
port: 8080
# 指定服务命名
spring:
application:
name: consumer
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
```
#### 4.3 创建服务调用公共接口
```java
/**
* @author : heibaiying
* @description : 声明式服务调用
*/
public interface ProductFeign {
@GetMapping("products")
List<Product> productList();
/**
* 这是需要强调的是使用feign时候@PathVariable一定要用value指明参数
* 不然会抛出.IllegalStateException: PathVariable annotation was empty on param 异常
*/
@GetMapping("product/{id}")
Product productDetail(@PathVariable(value = "id") int id);
@PostMapping("product")
void save(@RequestBody Product product);
}
```
按照官方对于服务最佳化的推荐,这里我们的服务调用接口放在公共模块中,因为在实际的开发中,同一个服务调用接口可能被多个模块所使用。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/common-feign.png"/> </div>
#### 4.4 继承公共接口创建CProductFeign@FeignClient声明为feign客户端
```java
/**
* @author : heibaiying
* @description : 声明式接口调用
*/
@FeignClient(value = "producer",configuration = FeignConfig.class)
public interface CProductFeign extends ProductFeign {
}
```
#### 4.5 注入使用 feign 服务调用接口
```java
@Controller
@RequestMapping("sell")
public class SellController {
@Autowired
private CProductFeign cproductFeign;
@GetMapping("products")
public String productList(Model model) {
List<Product> products = cproductFeign.productList();
model.addAttribute("products", products);
return "products";
}
@GetMapping("product/{id}")
public String productDetail(@PathVariable int id, Model model) {
Product product = cproductFeign.productDetail(id);
model.addAttribute("product", product);
return "product";
}
@PostMapping("product")
public String save(@RequestParam String productName) {
long id = Math.round(Math.random() * 100);
Product product = new Product(id, productName, false, new Date(), 88);
cproductFeign.save(product);
return "redirect:products";
}
}
```
## 五、启动测试
#### 5.1 启动一个Eureka服务、三个producer服务注意区分端口、和一个消费者服务
feign 的依赖中导入了spring-cloud-starter-netflix-ribbon依赖并且在内部实现了基于ribbon的客户端负载均衡所以我们这里启动三个producer实例来观察负载均衡的情况。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div>
**服务注册中心:**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-eureka.png"/> </div>
#### 5.2 访问http://localhost:8080/sell/products 查看负载均衡的调用结果
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8020.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8030.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-8040.png"/> </div>
## 六、 feign 的服务容错
#### 6.1 feign 的依赖中默认导入了hystrix 的相关依赖,我们不需要额外导入,只需要开启相关配置即可
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-hystrix-maven.png"/> </div>
#### 6.2 在application.yml 中开启hystrix
```yml
feign:
hystrix:
# 如果为true则OpenFign客户端将使用Hystrix断路器进行封装 默认为false
enabled: true
```
#### 6.3 创建`CProductFeignImpl`,继承feign接口CProductFeign定义熔断时候的回退处理
```java
/**
* @author : heibaiying
* @description : 定义发生熔断时候的回退处理。除了继承自CProductFeign,还需要用@Component声明为spring的组件
*/
@Component
public class CProductFeignImpl implements CProductFeign {
// 发生熔断时候,返回空集合,前端页面会做容错显示
@Override
public List<Product> productList() {
return new ArrayList<>();
}
@Override
public Product productDetail(int id) {
return null;
}
@Override
public void save(Product product) {
}
}
```
页面的简单容错处理:
```html
<!doctype html>
<html lang="en">
<head>
<title>产品列表</title>
</head>
<body>
<h3>产品列表:点击查看详情</h3>
<form action="/sell/product" method="post">
<input type="text" name="productName">
<input type="submit" value="新增产品">
</form>
<ul>
<#if (products?size>0) >
<#list products as product>
<li>
<a href="/sell/product/${product.id}">${product.name}</a>
</li>
</#list>
<#else>
<h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
</#if>
</ul>
</body>
</html>
```
#### 6.4 在 @FeignClient 注解中用fallback参数指定熔断时候的回退处理
```java
/**
* @author : heibaiying
* @description : 声明式接口调用
*/
@FeignClient(value = "producer",configuration = FeignConfig.class,fallback = CProductFeignImpl.class)
public interface CProductFeign extends ProductFeign {
}
```
#### 6.5 测试熔断处理
hystrix 默认调用超时时间为2s ,这里我们使用线程休眠的方式来模拟超时熔断。
```java
public List<Product> queryAllProducts() {
/*用于测试 hystrix 超时熔断
try {
int i = new Random().nextInt(2500);
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
return productList;
}
```
测试结果:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-hystrix.png"/> </div>

View File

@ -1,325 +1,346 @@
# spring-cloud-hystrix-turbine
## 一、hystrix 简介
#### 1.1 熔断器
在分布式系统中,由于服务之间相互的依赖调用,如果一个服务单元发生了故障就有可能导致故障蔓延至整个系统,从而衍生出一系列的保护机制,断路器就是其中之一。
断路器可以在服务单元发生故障的时候及时切断与服务单元的连接避免资源被长时间占用。spring cloud hystrix组件实现了断路器、线程隔离等一系列基本功能并具有服务降级、服务熔断、请求缓存、请求合并以及服务监控等配套功能。
#### 1.2 hystrix 工作机制
- 当一个服务的处理请求的失败次数低于阈值时候,熔断器处于关闭状态,服务正常;
- 当一个服务的处理请求的失败次数大于阈值时候,熔断器开启,这时候所有的请求都会执行快速失败,是不会去调用实际的服务的;
- 当熔断器处于打开状态的一段时间后,熔断器处于半打开状态,这时候一定数量的请求回去调用实际的服务,如果调用成功,则代表服务可用了,熔断器关闭;如果还是失败,则代表服务还是不可用,熔断器继续关闭。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/circuitbreaker.png"/> </div>
## 二、项目结构
[spring-cloud-ribbon](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-ribbon)用例已经实现通过ribbon+restTemplate实现服务间的调用本用例在其基础上进行hystrix 的整合。
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用RestTemplate调用产品服务
+ producer服务的提供者
+ eureka: 注册中心;
+ turbine:多个熔断器的聚合监控。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-hystrix.png"/> </div>
## 三、整合 hystrix 以consumer模块为例
#### 3.1 引入依赖
hystrix的仪表盘功能实际上是从[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)获取数据所以需要actuator starter开启端点的相关功能。
```xml
<!--hystrix 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--hystrix 监控仪表盘依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--健康检查依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
#### 3.2 暴露端点
```java
management:
endpoints:
web:
exposure:
# 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
include: "*"
```
#### 3.3 在启动类上添加注解@EnableHystrix和@EnableHystrixDashboard
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableHystrixDashboard
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
```
#### 3.4 使用 @HystrixCommand 定义失败回退的方法
```java
@HystrixCommand(fallbackMethod = "queryProductsFail")
public List<Product> queryAllProducts() {
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://producer/products", List.class);
List<Product> productList = responseEntity.getBody();
return productList;
}
// 如果发送熔断返回空集合,在前端判断处理
public List<Product> queryProductsFail() {
return new ArrayList<>();
}
```
```html
<!doctype html>
<html lang="en">
<head>
<title>产品列表</title>
</head>
<body>
<h3>产品列表:点击查看详情</h3>
<form action="/sell/product" method="post">
<input type="text" name="productName">
<input type="submit" value="新增产品">
</form>
<ul>
<#if (products?size>0) >
<#list products as product>
<li>
<a href="/sell/product/${product.id}">${product.name}</a>
</li>
</#list>
<#else>
<h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
</#if>
</ul>
</body>
</html>
```
#### 3.5 模拟熔断
这里被调用方采用线程休眠的方式模拟服务超时Hystrix默认超时时间为2s,调用远程服务时候超过这个时间,会触发熔断。
```java
public List<Product> queryAllProducts() {
// hystrix 默认超时是2秒
int i = new Random().nextInt(2500);
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
return productList;
}
```
3.5 启动服务访问http://localhost:8030/sell/products ,多次刷新查看熔断情况
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030.png"/> </div>
#### 3.5 启动服务,访问 localhost:8030/hystrix
依次输出http://localhost:8030/actuator/hystrix.stream监控地址 2000延迟时间title可以任意填写进入监控台。
需要注意的是在spring cloud Finchley.SR2监控地址中都是有/actuator的因为在spring boot 2.x 的所有端点(包括自定义端点)都是暴露在这个路径下,在启动时候的控制台输出的日志可以查看到所有暴露端点的映射。
**登录页面**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-single-login.png"/> </div>
**监控页面**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030-login.png"/> </div>
**关于各个参数的说明参见[官方wiki](https://github.com/Netflix-Skunkworks/hystrix-dashboard/wiki)提供的图**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dashboard.png"/> </div>
## 四、使用turbine 实现聚合监控
单体监控和聚合监控:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dashboard-direct-vs-turbine-640.png"/> </div>
#### 4.1 创建turbine模块导入依赖
```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>com.heibaiying.hystrix</groupId>
<artifactId>spring-cloud-hystrx</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>turbine</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
#### 4.2 指定注册中心地址和聚合的项目,这里我们监控 consumer,producer 两个项目
```java
server:
port: 8040
# 指定服务命名
spring:
application:
name: turbine
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
# 指定聚合的项目
turbine:
aggregator:
cluster-config: default
combine-host-port: true
app-config: consumer,producer
clusterNameExpression: "'default'"
```
#### 4.3 在启动类上添加注解
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableHystrixDashboard
@EnableTurbine
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
```
#### 4.4 依次启动eureka、producer、consumer、turbine四个项目
在 localhost:8030/hystrix或者localhost:8030/hystrixconsumer和producer都集成了hystrix 页面输入http://localhost:8040/turbine.stream查看断路器聚合信息
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-cluster-login.png"/> </div>
**显示了不同服务单元consumer,producer的多个断路器信息**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-cluster.png"/> </div>
## 五、整合过程中可能出现的问题
#### 5.1 无法访问监控页面
1. 一般是端点链接输入不对在F版本的spring cloud 中,输入监控的端点链接是 http://localhost:8030/actuator/hystrix.stream ,中间是有/actuator/(之前版本的没有/actuator/
2. 没有暴露端点链接,暴露端点链接有两种方式,一种是我们在上文中提到的基于配置的方式
```yaml
management:
endpoints:
web:
exposure:
# 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
include: "*"
```
第二种方式是基于代码的方式,如下:
```java
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
```
这两种方式二选一即可,就算是采用代码的方式,还是建议将地址设置为/actuator/hystrix.stream而不是原来的hystrix.stream因为turbine默认也是从/actuator/hystrix.stream去获取信息。
#### 5.2 页面一直loading 或者访问端点页面一直出现ping
这种情况是熔断器所在的方法没有被调用,所以没有产生监控数据,不是整合问题,这时候调用一下熔断器所在方法即可。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-loading.png"/> </div>
# spring-cloud-hystrix-turbine
## 目录<br/>
<a href="#一hystrix-简介">一、hystrix 简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-熔断器">1.1 熔断器</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-hystrix-工作机制">1.2 hystrix 工作机制</a><br/>
<a href="#二项目结构">二、项目结构</a><br/>
<a href="#三整合-hystrix-以consumer模块为例">三、整合 hystrix 以consumer模块为例</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-引入依赖">3.1 引入依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-暴露端点">3.2 暴露端点</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-在启动类上添加注解@EnableHystrix和@EnableHystrixDashboard">3.3 在启动类上添加注解@EnableHystrix和@EnableHystrixDashboard</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#34--使用-@HystrixCommand-定义失败回退的方法">3.4 使用 @HystrixCommand 定义失败回退的方法</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#35-模拟熔断">3.5 模拟熔断</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#35-启动服务访问-localhost8030/hystrix">3.5 启动服务,访问 localhost:8030/hystrix</a><br/>
<a href="#四使用turbine-实现聚合监控">四、使用turbine 实现聚合监控</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-创建turbine模块导入依赖">4.1 创建turbine模块导入依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-指定注册中心地址和聚合的项目这里我们监控-consumerproducer-两个项目">4.2 指定注册中心地址和聚合的项目,这里我们监控 consumer,producer 两个项目</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-在启动类上添加注解">4.3 在启动类上添加注解</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#44-依次启动eurekaproducerconsumerturbine四个项目">4.4 依次启动eureka、producer、consumer、turbine四个项目</a><br/>
<a href="#五整合过程中可能出现的问题">五、整合过程中可能出现的问题</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#51-无法访问监控页面">5.1 无法访问监控页面</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#52-页面一直loading-或者访问端点页面一直出现ping">5.2 页面一直loading 或者访问端点页面一直出现ping</a><br/>
## 正文<br/>
## 一、hystrix 简介
#### 1.1 熔断器
在分布式系统中,由于服务之间相互的依赖调用,如果一个服务单元发生了故障就有可能导致故障蔓延至整个系统,从而衍生出一系列的保护机制,断路器就是其中之一。
断路器可以在服务单元发生故障的时候及时切断与服务单元的连接避免资源被长时间占用。spring cloud hystrix组件实现了断路器、线程隔离等一系列基本功能并具有服务降级、服务熔断、请求缓存、请求合并以及服务监控等配套功能。
#### 1.2 hystrix 工作机制
- 当一个服务的处理请求的失败次数低于阈值时候,熔断器处于关闭状态,服务正常;
- 当一个服务的处理请求的失败次数大于阈值时候,熔断器开启,这时候所有的请求都会执行快速失败,是不会去调用实际的服务的;
- 当熔断器处于打开状态的一段时间后,熔断器处于半打开状态,这时候一定数量的请求回去调用实际的服务,如果调用成功,则代表服务可用了,熔断器关闭;如果还是失败,则代表服务还是不可用,熔断器继续关闭。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/circuitbreaker.png"/> </div>
## 二、项目结构
[spring-cloud-ribbon](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-ribbon)用例已经实现通过ribbon+restTemplate实现服务间的调用本用例在其基础上进行hystrix 的整合。
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用RestTemplate调用产品服务
+ producer服务的提供者
+ eureka: 注册中心;
+ turbine:多个熔断器的聚合监控。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-hystrix.png"/> </div>
## 三、整合 hystrix 以consumer模块为例
#### 3.1 引入依赖
hystrix的仪表盘功能实际上是从[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)获取数据所以需要actuator starter开启端点的相关功能。
```xml
<!--hystrix 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--hystrix 监控仪表盘依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--健康检查依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
#### 3.2 暴露端点
```java
management:
endpoints:
web:
exposure:
# 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
include: "*"
```
#### 3.3 在启动类上添加注解@EnableHystrix和@EnableHystrixDashboard
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableHystrixDashboard
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
```
#### 3.4 使用 @HystrixCommand 定义失败回退的方法
```java
@HystrixCommand(fallbackMethod = "queryProductsFail")
public List<Product> queryAllProducts() {
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://producer/products", List.class);
List<Product> productList = responseEntity.getBody();
return productList;
}
// 如果发送熔断返回空集合,在前端判断处理
public List<Product> queryProductsFail() {
return new ArrayList<>();
}
```
```html
<!doctype html>
<html lang="en">
<head>
<title>产品列表</title>
</head>
<body>
<h3>产品列表:点击查看详情</h3>
<form action="/sell/product" method="post">
<input type="text" name="productName">
<input type="submit" value="新增产品">
</form>
<ul>
<#if (products?size>0) >
<#list products as product>
<li>
<a href="/sell/product/${product.id}">${product.name}</a>
</li>
</#list>
<#else>
<h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
</#if>
</ul>
</body>
</html>
```
#### 3.5 模拟熔断
这里被调用方采用线程休眠的方式模拟服务超时Hystrix默认超时时间为2s,调用远程服务时候超过这个时间,会触发熔断。
```java
public List<Product> queryAllProducts() {
// hystrix 默认超时是2秒
int i = new Random().nextInt(2500);
try {
Thread.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
return productList;
}
```
3.5 启动服务访问http://localhost:8030/sell/products ,多次刷新查看熔断情况
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030.png"/> </div>
#### 3.5 启动服务,访问 localhost:8030/hystrix
依次输出http://localhost:8030/actuator/hystrix.stream监控地址 2000延迟时间title可以任意填写进入监控台。
需要注意的是在spring cloud Finchley.SR2监控地址中都是有/actuator的因为在spring boot 2.x 的所有端点(包括自定义端点)都是暴露在这个路径下,在启动时候的控制台输出的日志可以查看到所有暴露端点的映射。
**登录页面**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-single-login.png"/> </div>
**监控页面**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030-login.png"/> </div>
**关于各个参数的说明参见[官方wiki](https://github.com/Netflix-Skunkworks/hystrix-dashboard/wiki)提供的图**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dashboard.png"/> </div>
## 四、使用turbine 实现聚合监控
单体监控和聚合监控:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dashboard-direct-vs-turbine-640.png"/> </div>
#### 4.1 创建turbine模块导入依赖
```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>com.heibaiying.hystrix</groupId>
<artifactId>spring-cloud-hystrx</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>turbine</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
#### 4.2 指定注册中心地址和聚合的项目,这里我们监控 consumer,producer 两个项目
```java
server:
port: 8040
# 指定服务命名
spring:
application:
name: turbine
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
# 指定聚合的项目
turbine:
aggregator:
cluster-config: default
combine-host-port: true
app-config: consumer,producer
clusterNameExpression: "'default'"
```
#### 4.3 在启动类上添加注解
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableHystrixDashboard
@EnableTurbine
public class TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}
}
```
#### 4.4 依次启动eureka、producer、consumer、turbine四个项目
在 localhost:8030/hystrix或者localhost:8030/hystrixconsumer和producer都集成了hystrix 页面输入http://localhost:8040/turbine.stream查看断路器聚合信息
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-cluster-login.png"/> </div>
**显示了不同服务单元consumer,producer的多个断路器信息**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-cluster.png"/> </div>
## 五、整合过程中可能出现的问题
#### 5.1 无法访问监控页面
1. 一般是端点链接输入不对在F版本的spring cloud 中,输入监控的端点链接是 http://localhost:8030/actuator/hystrix.stream ,中间是有/actuator/(之前版本的没有/actuator/
2. 没有暴露端点链接,暴露端点链接有两种方式,一种是我们在上文中提到的基于配置的方式
```yaml
management:
endpoints:
web:
exposure:
# 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
include: "*"
```
第二种方式是基于代码的方式,如下:
```java
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/actuator/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
```
这两种方式二选一即可,就算是采用代码的方式,还是建议将地址设置为/actuator/hystrix.stream而不是原来的hystrix.stream因为turbine默认也是从/actuator/hystrix.stream去获取信息。
#### 5.2 页面一直loading 或者访问端点页面一直出现ping
这种情况是熔断器所在的方法没有被调用,所以没有产生监控数据,不是整合问题,这时候调用一下熔断器所在方法即可。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-loading.png"/> </div>

View File

@ -1,309 +1,331 @@
# spring-cloud-ribbon
## 一、ribbon 简介
ribbon是Netfix公司开源的负载均衡组件采用服务端负载均衡的方式即消费者客户端维护可用的服务列表并通过负载均衡的方式将请求按照指定的策略分摊给消费者从而达到负载均衡的方式。
## 二、项目结构
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用RestTemplate调用产品服务
+ producer服务的提供者
+ eureka: 注册中心ribbon 从注册中心获取可用的服务列表,是实现负载均衡的基础。这里使用我们在[服务的注册与发现](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-eureka)这个用例中搭建的简单注册中心作为测试即可。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon.png"/> </div>
## 三、服务提供者的实现
#### 3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/ribbon-producer.png"/> </div>
ProductService.java
```java
/**
* @author : heibaiying
* @description : 产品提供接口实现类
* 这里为了之后直观的看到负载均衡的结果,我们继承了 ApplicationListener,从 WebServerInitializedEvent 获取服务的端口号,并拼接在产品名称上
*/
@Service
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {
private static List<Product> productList = new ArrayList<>();
public Product queryProductById(int id) {
for (Product product : productList) {
if (product.getId() == id) {
return product;
}
}
return null;
}
public List<Product> queryAllProducts() {
return productList;
}
@Override
public void saveProduct(Product product) {
productList.add(product);
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
int port = event.getWebServer().getPort();
for (long i = 0; i < 20; i++) {
productList.add(new Product(i, port + "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
}
}
}
```
ProducerController.java
```java
@RestController
public class ProducerController {
@Autowired
private IProductService productService;
@GetMapping("products")
public List<Product> productList() {
return productService.queryAllProducts();
}
@GetMapping("product/{id}")
public Product productDetail(@PathVariable int id) {
return productService.queryProductById(id);
}
@PostMapping("product")
public void save(@RequestBody Product product) {
productService.saveProduct(product);
}
}
```
#### 3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
```java
server:
port: 8020
# 指定服务命名
spring:
application:
name: producer
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
```
## 四、服务消费者的实现
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/ribbon-consumer.png"/> </div>
#### 4.1 导入负载均衡需要的依赖
```xml
<!--ribbon 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
```
#### 4.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
```java
server:
port: 8080
# 指定服务命名
spring:
application:
name: consumer
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
```
#### 4.3 使用@LoadBalanced配置RestTemplate即可实现客户端负载均衡
```java
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RibbonConfig {
@LoadBalanced // 配置客户端负载均衡
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
```
#### 4.4 使用RestTemplate调用远程服务
这里我们在调用远程服务的时候url填写的是服务名+具体接口地址 ,由于我们的同一个服务会存在多个实例,在使用@LoadBalanced配置RestTemplate调用服务时客户端就会从按照指定的负载均衡的方式将请求分摊到多个实例上。默认的负载均衡采用的是RoundRobinRule轮询的策略有特殊需求时候可以采用其他内置的策略规则或者实现IRule来定义自己的负载均衡策略
```java
@Service
public class ProductService implements IProductService {
@Autowired
private RestTemplate restTemplate;
public Product queryProductById(int id) {
ResponseEntity<Product> responseEntity = restTemplate.getForEntity("http://producer/product/{1}", Product.class, id);
return responseEntity.getBody();
}
public List<Product> queryAllProducts() {
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://producer/products", List.class);
List<Product> productList = responseEntity.getBody();
return productList;
}
public void saveProduct(Product product) {
restTemplate.postForObject("http://producer/product", product, Void.class);
}
}
```
## 五、启动测试
#### 5.1 启动一个Eureka服务、三个producer服务注意区分端口、和一个消费者服务
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div>
**服务注册中心:**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-eureka.png"/> </div>
#### 5.2 访问http://localhost:8080/sell/products 查看负载均衡的调用结果
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8020.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8030.png"/> </div>
## 六、 附1 关于RestTemplate的说明
#### 6.1 restTemplate 规范
restTemplate 调用对应resultful接口时候使用的方法应该与接口声明方式@GetMapping@PostMapping@PutMapping@DeleteMapping)保持一致。请求类型与对应的调用方法如下。
- GET请求(getForObject 、getForEntity)
- POST请求postForObject 、postForEntity
- PUT请求put
- DELETE请求 delete
#### 6.2 ForEntity()和ForObject的区别
- `ForEntity()`发送一个请求返回的ResponseEntity包含了响应体所映射成的对象
- `ForObject()`发送一个请求,返回的请求体将映射为一个对象
例如:
```java
ResponseEntity<Product> responseEntity = restTemplate.getForEntity("http://producer/product/{1}", Product.class, id);
Product product = restTemplate.getForObject("http://producer/product/{1}", Product.class, id);
```
## 七、 附2 关于ribbon更多负载均衡的策略
Ribbon内置了多种负载均衡策略如果有更复杂的需求可以自己实现IRule。
#### 7.1 内置的负载均衡的策略如下图
![Ribbon负载均衡策略.png](http://upload-images.jianshu.io/upload_images/6944619-0355d316f5df9b3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
图片来源于博客:[Ribbon负载均衡策略与自定义配置](https://blog.csdn.net/jrn1012/article/details/77837680)
#### 7.2 配置文件指定负载均衡的方式
要设置`IRule`名为的服务名称`users`,您可以设置以下属性:
```yaml
users:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
```
#### 7.3 代码指定负载均衡的方式
```java
@Configuration
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
public class TestConfiguration {
}
```
```java
@Configuration
public class CustomConfiguration {
@Bean
public IRule ribbonRule() {
return new BestAvailableRule();
}
}
```
在使用代码方式的时候需要注意 [官方文档](http://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html#_customizing_the_ribbon_client)中关于注解方式有以下强调:
The CustomConfiguration clas must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).
`CustomConfiguration`类必须是`@Configuration`标注的,但需要注意的它不是在`@ComponentScan`主应用程序上下文。否则,它将由所有`@RibbonClients`共享。如果你使用`@ComponentScan`(或`@SpringBootApplication`),你需要采取一些措施来避免它被扫描到(例如,你可以把它放在一个独立的,非重叠的包,或用`@ComponentScan`时显示扫描指定的包)。
# spring-cloud-ribbon
## 目录<br/>
<a href="#一ribbon-简介">一、ribbon 简介</a><br/>
<a href="#二项目结构">二、项目结构</a><br/>
<a href="#三服务提供者的实现">三、服务提供者的实现</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-产品服务由`ProductService`提供并通过`ProducerController`将服务暴露给外部调用。">3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-指定注册中心地址并在启动类上开启自动注册@EnableDiscoveryClient">3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient</a><br/>
<a href="#四服务消费者的实现">四、服务消费者的实现</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-导入负载均衡需要的依赖">4.1 导入负载均衡需要的依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-指定注册中心地址并在启动类上开启自动注册@EnableDiscoveryClient">4.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-使用@LoadBalanced配置RestTemplate即可实现客户端负载均衡">4.3 使用@LoadBalanced配置RestTemplate即可实现客户端负载均衡</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#44-使用RestTemplate调用远程服务">4.4 使用RestTemplate调用远程服务</a><br/>
<a href="#五启动测试">五、启动测试</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#51-启动一个Eureka服务三个producer服务注意区分端口和一个消费者服务">5.1 启动一个Eureka服务、三个producer服务注意区分端口、和一个消费者服务</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#52--访问http//localhost8080/sell/products-查看负载均衡的调用结果">5.2 访问http://localhost:8080/sell/products 查看负载均衡的调用结果</a><br/>
<a href="#六-附1-关于RestTemplate的说明">六、 附1 关于RestTemplate的说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#61--restTemplate-规范">6.1 restTemplate 规范</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#62-ForEntity和ForObject的区别">6.2 ForEntity()和ForObject的区别</a><br/>
<a href="#七-附2-关于ribbon更多负载均衡的策略">七、 附2 关于ribbon更多负载均衡的策略</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#71-内置的负载均衡的策略如下图">7.1 内置的负载均衡的策略如下图</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#72-配置文件指定负载均衡的方式">7.2 配置文件指定负载均衡的方式</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#73-代码指定负载均衡的方式">7.3 代码指定负载均衡的方式</a><br/>
## 正文<br/>
## 一、ribbon 简介
ribbon是Netfix公司开源的负载均衡组件采用服务端负载均衡的方式即消费者客户端维护可用的服务列表并通过负载均衡的方式将请求按照指定的策略分摊给消费者从而达到负载均衡的方式。
## 二、项目结构
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用RestTemplate调用产品服务
+ producer服务的提供者
+ eureka: 注册中心ribbon 从注册中心获取可用的服务列表,是实现负载均衡的基础。这里使用我们在[服务的注册与发现](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-eureka)这个用例中搭建的简单注册中心作为测试即可。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon.png"/> </div>
## 三、服务提供者的实现
#### 3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/ribbon-producer.png"/> </div>
ProductService.java
```java
/**
* @author : heibaiying
* @description : 产品提供接口实现类
* 这里为了之后直观的看到负载均衡的结果,我们继承了 ApplicationListener,从 WebServerInitializedEvent 获取服务的端口号,并拼接在产品名称上
*/
@Service
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {
private static List<Product> productList = new ArrayList<>();
public Product queryProductById(int id) {
for (Product product : productList) {
if (product.getId() == id) {
return product;
}
}
return null;
}
public List<Product> queryAllProducts() {
return productList;
}
@Override
public void saveProduct(Product product) {
productList.add(product);
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
int port = event.getWebServer().getPort();
for (long i = 0; i < 20; i++) {
productList.add(new Product(i, port + "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
}
}
}
```
ProducerController.java
```java
@RestController
public class ProducerController {
@Autowired
private IProductService productService;
@GetMapping("products")
public List<Product> productList() {
return productService.queryAllProducts();
}
@GetMapping("product/{id}")
public Product productDetail(@PathVariable int id) {
return productService.queryProductById(id);
}
@PostMapping("product")
public void save(@RequestBody Product product) {
productService.saveProduct(product);
}
}
```
#### 3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
```java
server:
port: 8020
# 指定服务命名
spring:
application:
name: producer
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
```
## 四、服务消费者的实现
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/ribbon-consumer.png"/> </div>
#### 4.1 导入负载均衡需要的依赖
```xml
<!--ribbon 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
```
#### 4.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
```java
server:
port: 8080
# 指定服务命名
spring:
application:
name: consumer
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
```
```java
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
```
#### 4.3 使用@LoadBalanced配置RestTemplate即可实现客户端负载均衡
```java
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RibbonConfig {
@LoadBalanced // 配置客户端负载均衡
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
```
#### 4.4 使用RestTemplate调用远程服务
这里我们在调用远程服务的时候url填写的是服务名+具体接口地址 ,由于我们的同一个服务会存在多个实例,在使用@LoadBalanced配置RestTemplate调用服务时客户端就会从按照指定的负载均衡的方式将请求分摊到多个实例上。默认的负载均衡采用的是RoundRobinRule轮询的策略有特殊需求时候可以采用其他内置的策略规则或者实现IRule来定义自己的负载均衡策略
```java
@Service
public class ProductService implements IProductService {
@Autowired
private RestTemplate restTemplate;
public Product queryProductById(int id) {
ResponseEntity<Product> responseEntity = restTemplate.getForEntity("http://producer/product/{1}", Product.class, id);
return responseEntity.getBody();
}
public List<Product> queryAllProducts() {
ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://producer/products", List.class);
List<Product> productList = responseEntity.getBody();
return productList;
}
public void saveProduct(Product product) {
restTemplate.postForObject("http://producer/product", product, Void.class);
}
}
```
## 五、启动测试
#### 5.1 启动一个Eureka服务、三个producer服务注意区分端口、和一个消费者服务
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div>
**服务注册中心:**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-eureka.png"/> </div>
#### 5.2 访问http://localhost:8080/sell/products 查看负载均衡的调用结果
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8020.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8030.png"/> </div>
## 六、 附1 关于RestTemplate的说明
#### 6.1 restTemplate 规范
restTemplate 调用对应resultful接口时候使用的方法应该与接口声明方式@GetMapping@PostMapping@PutMapping@DeleteMapping)保持一致。请求类型与对应的调用方法如下。
- GET请求(getForObject 、getForEntity)
- POST请求postForObject 、postForEntity
- PUT请求put
- DELETE请求 delete
#### 6.2 ForEntity()和ForObject的区别
- `ForEntity()`发送一个请求返回的ResponseEntity包含了响应体所映射成的对象
- `ForObject()`发送一个请求,返回的请求体将映射为一个对象
例如:
```java
ResponseEntity<Product> responseEntity = restTemplate.getForEntity("http://producer/product/{1}", Product.class, id);
Product product = restTemplate.getForObject("http://producer/product/{1}", Product.class, id);
```
## 七、 附2 关于ribbon更多负载均衡的策略
Ribbon内置了多种负载均衡策略如果有更复杂的需求可以自己实现IRule。
#### 7.1 内置的负载均衡的策略如下图
![Ribbon负载均衡策略.png](http://upload-images.jianshu.io/upload_images/6944619-0355d316f5df9b3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
图片来源于博客:[Ribbon负载均衡策略与自定义配置](https://blog.csdn.net/jrn1012/article/details/77837680)
#### 7.2 配置文件指定负载均衡的方式
要设置`IRule`名为的服务名称`users`,您可以设置以下属性:
```yaml
users:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
```
#### 7.3 代码指定负载均衡的方式
```java
@Configuration
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
public class TestConfiguration {
}
```
```java
@Configuration
public class CustomConfiguration {
@Bean
public IRule ribbonRule() {
return new BestAvailableRule();
}
}
```
在使用代码方式的时候需要注意 [官方文档](http://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html#_customizing_the_ribbon_client)中关于注解方式有以下强调:
The CustomConfiguration clas must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).
`CustomConfiguration`类必须是`@Configuration`标注的,但需要注意的它不是在`@ComponentScan`主应用程序上下文。否则,它将由所有`@RibbonClients`共享。如果你使用`@ComponentScan`(或`@SpringBootApplication`),你需要采取一些措施来避免它被扫描到(例如,你可以把它放在一个独立的,非重叠的包,或用`@ComponentScan`时显示扫描指定的包)。

View File

@ -1,93 +1,102 @@
# spring-sleuth-zipkin
## 一、简介
在微服务架构中,几乎每一个前端的请求都会经过多个服务单元协调来提供服务,形成复杂的服务调用链路。当服务发生问题时候,很难知道问题来源于链路的哪一个环节,这时候就需要进行链路追踪。
zipkin 是一个开源的分布式跟踪系统可以使用spring cloud sleuth 来轻松的集成 zipkin。
## 二、项目结构
这里的项目是在之前的 [spring-cloud-zuul](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-zuul) 进行集成zuul 项目的产品接口调用链路从 网关 -> consumer -> producer,历经三个环节的调用链路可以直观展示zipkin对链路追踪可视化的好处。
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用feign调用产品服务
+ producer服务的提供者
+ eureka: 注册中心;
+ zuul: api网关。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-sleuth-zipkin.png"/> </div>
## 三、构建 zipkin 服务端
zipkin 客户端可以不用自己构建,直接从[官网](https://zipkin.io/pages/quickstart)上下载对应的jar 包启动即可,默认端口 9411
```shell
java -jar zipkin.jar
```
可以直接从docker仓库拉取然后启动容器
```shell
docker run -d -p 9411:9411 openzipkin/zipkin
```
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-download.png"/> </div>
## 四、集成zipkin
这里我们对zuul、consumer、producer 三个模块都进行集成
#### 4.1 对三个模块(zuul、consumer、producer )添加依赖
```xml
<!--zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
```
#### 4.2 分别在三个模块的application.yml 配置文件中指定zipkin的地址
```yaml
spring:
zipkin:
base-url: http://localhost:9411/
# 可以指定监控数据的采样率
sleuth:
sampler:
probability: 1
```
## 五、启动项目
分别启动eurekazuulconsumerproducerzuul ,访问 http://localhost:9411/ ,查看我们的服务调用链路
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin.png"/> </div>
点击链路,则可以查看具体的调用情况
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-detail.png"/> </div>
展示信息说明:
Span 基本工作单元,发送一个远程调度任务就会产生一个 Span。
Trace由一系列 Span 组成的,呈树状结构。 所有由这个请求产生的 Span 组成了这个 Trace 。
SpanId ; 工作单元 (Span) 的唯一标识。
TraceId : 一条请求链路 (Trace) 的唯 一 标识。
除了TraceID外还需要SpanID用于记录调用父子关系。每个服务会记录下parent id和span id通过他们可以组织一次完整调用链的父子关系。
# spring-sleuth-zipkin
## 目录<br/>
<a href="#一简介">一、简介</a><br/>
<a href="#二项目结构">二、项目结构</a><br/>
<a href="#三构建-zipkin-服务端">三、构建 zipkin 服务端</a><br/>
<a href="#四集成zipkin">四、集成zipkin</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-对三个模块zuulconsumerproducer-添加依赖">4.1 对三个模块(zuul、consumer、producer )添加依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-分别在三个模块的applicationyml-配置文件中指定zipkin的地址">4.2 分别在三个模块的application.yml 配置文件中指定zipkin的地址</a><br/>
<a href="#五启动项目">五、启动项目</a><br/>
## 正文<br/>
## 一、简介
在微服务架构中,几乎每一个前端的请求都会经过多个服务单元协调来提供服务,形成复杂的服务调用链路。当服务发生问题时候,很难知道问题来源于链路的哪一个环节,这时候就需要进行链路追踪。
zipkin 是一个开源的分布式跟踪系统可以使用spring cloud sleuth 来轻松的集成 zipkin。
## 二、项目结构
这里的项目是在之前的 [spring-cloud-zuul](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-zuul) 进行集成zuul 项目的产品接口调用链路从 网关 -> consumer -> producer,历经三个环节的调用链路可以直观展示zipkin对链路追踪可视化的好处。
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用feign调用产品服务
+ producer服务的提供者
+ eureka: 注册中心;
+ zuul: api网关。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-sleuth-zipkin.png"/> </div>
## 三、构建 zipkin 服务端
zipkin 客户端可以不用自己构建,直接从[官网](https://zipkin.io/pages/quickstart)上下载对应的jar 包启动即可,默认端口 9411
```shell
java -jar zipkin.jar
```
可以直接从docker仓库拉取然后启动容器
```shell
docker run -d -p 9411:9411 openzipkin/zipkin
```
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-download.png"/> </div>
## 四、集成zipkin
这里我们对zuul、consumer、producer 三个模块都进行集成
#### 4.1 对三个模块(zuul、consumer、producer )添加依赖
```xml
<!--zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
```
#### 4.2 分别在三个模块的application.yml 配置文件中指定zipkin的地址
```yaml
spring:
zipkin:
base-url: http://localhost:9411/
# 可以指定监控数据的采样率
sleuth:
sampler:
probability: 1
```
## 五、启动项目
分别启动eurekazuulconsumerproducerzuul ,访问 http://localhost:9411/ ,查看我们的服务调用链路
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin.png"/> </div>
点击链路,则可以查看具体的调用情况
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-detail.png"/> </div>
展示信息说明:
Span 基本工作单元,发送一个远程调度任务就会产生一个 Span。
Trace由一系列 Span 组成的,呈树状结构。 所有由这个请求产生的 Span 组成了这个 Trace 。
SpanId ; 工作单元 (Span) 的唯一标识。
TraceId : 一条请求链路 (Trace) 的唯 一 标识。
除了TraceID外还需要SpanID用于记录调用父子关系。每个服务会记录下parent id和span id通过他们可以组织一次完整调用链的父子关系。
注:关于以上概念可以比对链表的实现原理来理解。

View File

@ -1,388 +1,408 @@
# spring-cloud-zuul
## 一、zuul简介
### 1.1 API 网关
api 网关是整个微服务系统的门面,所有的外部访问需要通过网关进行调度和过滤。它实现了请求转发、负载均衡、校验过滤、错误熔断、服务聚合等功能。
下图是直观的显示api Gateway 在微服务网关中的作用图片引用自spring boot 官网)。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/apiGateway.png"/> </div>
### 1.2 zuul
spring cloud 中提供了基础Net flix Zuul 实现的网关组件这就是Zuul,它除了实现负载均衡、错误熔断、路由转发等功能还能与spring 其他组件无缝配合使用。
## 二、项目结构
[spring-cloud-feign](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-feign)用例已经实现通过feign实现服务间的调用且提供了两个业务服务单元(consumer、producer)可以方便直观的测试zuul的路由、负载均衡、和错误熔断等功能所以本用例在其基础上进行zuul的整合。
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用feign调用产品服务
+ producer服务的提供者
+ eureka: 注册中心;
+ zuul: api网关。
聚合项目目录如下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-zuul.png"/> </div>
zuul 项目目录如下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul.png"/> </div>
## 三、构建api 网关 zuul
#### 3.1 引入依赖
主要的依赖是 spring-cloud-starter-netflix-zuul
```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-starter-parent</artifactId>
<version>2.0.8.RELEASE</version>
<relativePath/>
</parent>
<artifactId>zuul</artifactId>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.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-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--zuul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
```
#### 3.2 在启动类上添加注解@EnableZuulProxy和@EnableDiscoveryClient
@EnableZuulProxy会自动设置Zuul服务器端点并在其中开启一些反向代理过滤器,以便将请求转发到后端服务器。
```java
@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
```
#### 3.3 指定注册中心、配置网关的路由规则
zuul 需要指定注册中心的地址zuul 会从eureka获取其他微服务的实例信息然后按照指定的路由规则进行请求转发。
```yaml
server:
port: 8090
# 指定服务命名
spring:
application:
name: zuul
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
# 网关的路由
zuul:
routes:
xxxx: #这个地方的值是可以任意的字符串
path: /producer/**
serviceId: producer
consumer:
path: /consumer/**
serviceId: consumer
```
#### 3.4 启动eureka、producer、consumer、zuul服务访问 localhost:8090/consumer/sell/product
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer.png"/> </div>
## 四、错误熔断
#### 4.1 zuul 默认整合了 hystrix ,不用导入其他额外依赖
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-hystrix.png"/> </div>
#### 4.2 创建 CustomZuulFallbackProvider并实现FallbackProvider 接口,同时用@Component声明为spring 组件,即可实现熔断时候的回退服务
```java
/**
* @author : heibaiying
* @description : zuul 的熔断器
*/
@Component
public class CustomZuulFallbackProvider implements FallbackProvider {
/*
* 定义熔断将用于哪些路由的服务
*/
@Override
public String getRoute() {
return "consumer";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
/**
* 返回响应的HTTP状态代码
*/
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.SERVICE_UNAVAILABLE;
}
/**
* 返回HTTP状态代码
*/
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.SERVICE_UNAVAILABLE.value();
}
/**
* 返回响应的HTTP状态文本
*/
@Override
public String getStatusText() throws IOException {
return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase();
}
@Override
public void close() {
}
/**
* 将消息正文作为输入流返回
*/
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("商城崩溃了,请稍后重试!".getBytes());
}
/**
* 将消息正文作为输入流返回
*/
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
return httpHeaders;
}
};
}
}
```
正确返回了内容、同时返回的http状态码也和我们设置的一样。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-broker.png"/> </div>
## 五、zuul 过滤器
创建自定义过滤器继承自CustomZuulFilter当我们访问网关的时候如果判断session 中没有对应的 code,则跳转到我们自定义的登录页面。
```java
/**
* @author : heibaiying
* @description : 自定义filter过滤器
*/
@Component
public class CustomZuulFilter extends ZuulFilter {
/**
* 返回过滤器的类型
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 返回过滤器的优先级顺序
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 从此方法返回“true”意味着应该调用下面的 run方法
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* ZuulFilter的核心校验方法
*/
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String code = (String)request.getSession().getAttribute("code");
if (StringUtils.isEmpty(code)){
// 设置值为false 不将请求转发到对应的服务上
currentContext.setSendZuulResponse(false);
// 设置返回的状态码
currentContext.setResponseStatusCode(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
HttpServletResponse response = currentContext.getResponse();
try {
// 跳转到登录页面
response.sendRedirect("/index");
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
```
index.ftl:
```html
<!doctype html>
<html lang="en">
<head>
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
<input name="username" type="text">
<button id="btn">输入临时用户名后登录!</button>
</form>
</body>
</html>
```
## 六、负载均衡
#### zuul 默认集成了ribbon 实现了负载均衡。只要启动多个实例即可查看到负载均衡的效果。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-ribbon.png"/> </div>
#### 这里我们直接在idea 中启动多个实例来测试:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-config.png"/> </div>
#### 负载均衡测试结果:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer-8040.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer-8030.png"/> </div>
## 七、附:关于版本问题可能导致的 zuul 启动失败
如果出现以下错误导致启动失败,是 spring boot 版本不兼容导致的错误Finchley SR2版本 spring cloud 中的 zuul 和 spring boot 2.1.x 版本存在不兼容。如果出现这个问题,则将 spring boot 将至 2.0.x 的版本即可,用例中采用的是 2.0.8 版本。在实际的开发中应该严格遵循spring 官方的版本依赖说明。
```java
APPLICATION FAILED TO START
---
Description:
The bean 'counterFactory', defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulCounterFactoryConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulMetricsConfiguration.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
```
**spring cloud 版本说明**
| Release Train | Boot Version |
| ------------- | ------------ |
| Greenwich | 2.1.x |
| Finchley | 2.0.x |
| Edgware | 1.5.x |
| Dalston | 1.5.x |
更多组件的版本说明可以在[spring cloud overview](https://spring.io/projects/spring-cloud#overview) 页面查看。
# spring-cloud-zuul
## 目录<br/>
<a href="#一zuul简介">一、zuul简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-API-网关">1.1 API 网关</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-zuul">1.2 zuul</a><br/>
<a href="#二项目结构">二、项目结构</a><br/>
<a href="#三构建api-网关-zuul">三、构建api 网关 zuul</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-引入依赖">3.1 引入依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-在启动类上添加注解@EnableZuulProxy和@EnableDiscoveryClient">3.2 在启动类上添加注解@EnableZuulProxy和@EnableDiscoveryClient</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33--指定注册中心配置网关的路由规则">3.3 指定注册中心、配置网关的路由规则</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#34--启动eurekaproducerconsumerzuul服务访问-localhost8090/consumer/sell/product">3.4 启动eureka、producer、consumer、zuul服务访问 localhost:8090/consumer/sell/product </a><br/>
<a href="#四错误熔断">四、错误熔断</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41--zuul-默认整合了-hystrix-不用导入其他额外依赖">4.1 zuul 默认整合了 hystrix ,不用导入其他额外依赖</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-创建-CustomZuulFallbackProvider并实现FallbackProvider-接口同时用@Component声明为spring-组件即可实现熔断时候的回退服务">4.2 创建 CustomZuulFallbackProvider并实现FallbackProvider 接口,同时用@Component声明为spring 组件,即可实现熔断时候的回退服务</a><br/>
<a href="#五zuul--过滤器">五、zuul 过滤器</a><br/>
<a href="#六负载均衡">六、负载均衡</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#zuul-默认集成了ribbon-实现了负载均衡。只要启动多个实例即可查看到负载均衡的效果。">zuul 默认集成了ribbon 实现了负载均衡。只要启动多个实例即可查看到负载均衡的效果。</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#这里我们直接在idea-中启动多个实例来测试">这里我们直接在idea 中启动多个实例来测试:</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#负载均衡测试结果">负载均衡测试结果:</a><br/>
<a href="#七附关于版本问题可能导致的-zuul-启动失败">七、附:关于版本问题可能导致的 zuul 启动失败</a><br/>
## 正文<br/>
## 一、zuul简介
### 1.1 API 网关
api 网关是整个微服务系统的门面,所有的外部访问需要通过网关进行调度和过滤。它实现了请求转发、负载均衡、校验过滤、错误熔断、服务聚合等功能。
下图是直观的显示api Gateway 在微服务网关中的作用图片引用自spring boot 官网)。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/apiGateway.png"/> </div>
### 1.2 zuul
spring cloud 中提供了基础Net flix Zuul 实现的网关组件这就是Zuul,它除了实现负载均衡、错误熔断、路由转发等功能还能与spring 其他组件无缝配合使用。
## 二、项目结构
[spring-cloud-feign](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-feign)用例已经实现通过feign实现服务间的调用且提供了两个业务服务单元(consumer、producer)可以方便直观的测试zuul的路由、负载均衡、和错误熔断等功能所以本用例在其基础上进行zuul的整合。
+ common: 公共的接口和实体类;
+ consumer: 服务的消费者采用feign调用产品服务
+ producer服务的提供者
+ eureka: 注册中心;
+ zuul: api网关。
聚合项目目录如下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-zuul.png"/> </div>
zuul 项目目录如下:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul.png"/> </div>
## 三、构建api 网关 zuul
#### 3.1 引入依赖
主要的依赖是 spring-cloud-starter-netflix-zuul
```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-starter-parent</artifactId>
<version>2.0.8.RELEASE</version>
<relativePath/>
</parent>
<artifactId>zuul</artifactId>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.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-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--zuul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
```
#### 3.2 在启动类上添加注解@EnableZuulProxy和@EnableDiscoveryClient
@EnableZuulProxy会自动设置Zuul服务器端点并在其中开启一些反向代理过滤器,以便将请求转发到后端服务器。
```java
@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
```
#### 3.3 指定注册中心、配置网关的路由规则
zuul 需要指定注册中心的地址zuul 会从eureka获取其他微服务的实例信息然后按照指定的路由规则进行请求转发。
```yaml
server:
port: 8090
# 指定服务命名
spring:
application:
name: zuul
# 指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8010/eureka/
# 网关的路由
zuul:
routes:
xxxx: #这个地方的值是可以任意的字符串
path: /producer/**
serviceId: producer
consumer:
path: /consumer/**
serviceId: consumer
```
#### 3.4 启动eureka、producer、consumer、zuul服务访问 localhost:8090/consumer/sell/product
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer.png"/> </div>
## 四、错误熔断
#### 4.1 zuul 默认整合了 hystrix ,不用导入其他额外依赖
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-hystrix.png"/> </div>
#### 4.2 创建 CustomZuulFallbackProvider并实现FallbackProvider 接口,同时用@Component声明为spring 组件,即可实现熔断时候的回退服务
```java
/**
* @author : heibaiying
* @description : zuul 的熔断器
*/
@Component
public class CustomZuulFallbackProvider implements FallbackProvider {
/*
* 定义熔断将用于哪些路由的服务
*/
@Override
public String getRoute() {
return "consumer";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
/**
* 返回响应的HTTP状态代码
*/
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.SERVICE_UNAVAILABLE;
}
/**
* 返回HTTP状态代码
*/
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.SERVICE_UNAVAILABLE.value();
}
/**
* 返回响应的HTTP状态文本
*/
@Override
public String getStatusText() throws IOException {
return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase();
}
@Override
public void close() {
}
/**
* 将消息正文作为输入流返回
*/
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("商城崩溃了,请稍后重试!".getBytes());
}
/**
* 将消息正文作为输入流返回
*/
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
return httpHeaders;
}
};
}
}
```
正确返回了内容、同时返回的http状态码也和我们设置的一样。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-broker.png"/> </div>
## 五、zuul 过滤器
创建自定义过滤器继承自CustomZuulFilter当我们访问网关的时候如果判断session 中没有对应的 code,则跳转到我们自定义的登录页面。
```java
/**
* @author : heibaiying
* @description : 自定义filter过滤器
*/
@Component
public class CustomZuulFilter extends ZuulFilter {
/**
* 返回过滤器的类型
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 返回过滤器的优先级顺序
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 从此方法返回“true”意味着应该调用下面的 run方法
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* ZuulFilter的核心校验方法
*/
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String code = (String)request.getSession().getAttribute("code");
if (StringUtils.isEmpty(code)){
// 设置值为false 不将请求转发到对应的服务上
currentContext.setSendZuulResponse(false);
// 设置返回的状态码
currentContext.setResponseStatusCode(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
HttpServletResponse response = currentContext.getResponse();
try {
// 跳转到登录页面
response.sendRedirect("/index");
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
```
index.ftl:
```html
<!doctype html>
<html lang="en">
<head>
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
<input name="username" type="text">
<button id="btn">输入临时用户名后登录!</button>
</form>
</body>
</html>
```
## 六、负载均衡
#### zuul 默认集成了ribbon 实现了负载均衡。只要启动多个实例即可查看到负载均衡的效果。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-ribbon.png"/> </div>
#### 这里我们直接在idea 中启动多个实例来测试:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-config.png"/> </div>
#### 负载均衡测试结果:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer-8040.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer-8030.png"/> </div>
## 七、附:关于版本问题可能导致的 zuul 启动失败
如果出现以下错误导致启动失败,是 spring boot 版本不兼容导致的错误Finchley SR2版本 spring cloud 中的 zuul 和 spring boot 2.1.x 版本存在不兼容。如果出现这个问题,则将 spring boot 将至 2.0.x 的版本即可,用例中采用的是 2.0.8 版本。在实际的开发中应该严格遵循spring 官方的版本依赖说明。
```java
APPLICATION FAILED TO START
---
Description:
The bean 'counterFactory', defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulCounterFactoryConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulMetricsConfiguration.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
```
**spring cloud 版本说明**
| Release Train | Boot Version |
| ------------- | ------------ |
| Greenwich | 2.1.x |
| Finchley | 2.0.x |
| Edgware | 1.5.x |
| Dalston | 1.5.x |
更多组件的版本说明可以在[spring cloud overview](https://spring.io/projects/spring-cloud#overview) 页面查看。

View File

@ -8,7 +8,7 @@
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#1在resources文件夹下新建数据库配置文件jdbcproperties">1、在resources文件夹下新建数据库配置文件jdbc.properties</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2配置Jdbc数据源并定义事务管理器">2、配置Jdbc数据源并定义事务管理器</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#3新建查询接口及其实现类这里我查询的表是mysql和oracle中的字典表">3、新建查询接口及其实现类</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#4新建测试类进行测试">4.新建测试类进行测试</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#4新建测试类进行测试">4新建测试类进行测试</a><br/>
## 正文<br/>
@ -166,7 +166,7 @@ public class OracleDaoImpl implements OracleDao {
}
```
#### 4.新建测试类进行测试
#### 4新建测试类进行测试
```java
@RunWith(SpringRunner.class)