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

View File

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

View File

@ -1,193 +1,203 @@
# spring boot data jpa # spring boot data jpa
## 一、说明 ## 目录<br/>
<a href="#一说明">一、说明</a><br/>
#### 1.1 项目结构 &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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-data-jpa.png"/> </div> <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/>
#### 1.2 项目主要依赖 &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/>
```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);
## 正文<br/> ## 正文<br/>
/*
* 占位符查询 ## 一、说明
*/
@Query(value = "select u from Programmer u where u.name = ?1 or u.salary = ?2") #### 1.1 项目结构
List<Programmer> findByConditionAndOrder(String name, float salary, Sort.Order order);
<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
@Query("select u from Programmer u where u.name = :name or u.age = :age") <dependencies>
Programmer findByParam(@Param("name") String name, <!-- data-jpa -->
@Param("age") int age); <dependency>
} <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
``` </dependency>
<!--引入mysql驱动-->
关于查询方法遵循的命名规范和关键词见下表: <dependency>
<groupId>mysql</groupId>
| Keyword | Sample | JPQL snippet | <artifactId>mysql-connector-java</artifactId>
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | <version>8.0.13</version>
| `And` | `findByLastnameAndFirstname` | `… where x.lastname = ?1 and x.firstname = ?2` | </dependency>
| `Or` | `findByLastnameOrFirstname` | `… where x.lastname = ?1 or x.firstname = ?2` | <!--单元测试包-->
| `Is,Equals` | `findByFirstname`,`findByFirstnameIs`,<br>`findByFirstnameEquals` | `… where x.firstname = ?1` | <dependency>
| `Between` | `findByStartDateBetween` | `… where x.startDate between ?1 and ?2` | <groupId>org.springframework.boot</groupId>
| `LessThan` | `findByAgeLessThan` | `… where x.age < ?1` | <artifactId>spring-boot-starter-test</artifactId>
| `LessThanEqual` | `findByAgeLessThanEqual` | `… where x.age <= ?1` | <scope>test</scope>
| `GreaterThan` | `findByAgeGreaterThan` | `… where x.age > ?1` | </dependency>
| `GreaterThanEqual` | `findByAgeGreaterThanEqual` | `… where x.age >= ?1` | </dependencies>
| `After` | `findByStartDateAfter` | `… where x.startDate > ?1` | ```
| `Before` | `findByStartDateBefore` | `… where x.startDate < ?1` |
| `IsNull` | `findByAgeIsNull` | `… where x.age is null` | ## 二、data jpa 的使用
| `IsNotNull,NotNull` | `findByAge(Is)NotNull` | `… where x.age not null` |
| `Like` | `findByFirstnameLike` | `… where x.firstname like ?1` | #### 2.1 在application.yml 中配置数据源
| `NotLike` | `findByFirstnameNotLike` | `… where x.firstname not like ?1` |
| `StartingWith` | `findByFirstnameStartingWith` | `… where x.firstname like ?1`(parameter bound with appended `%`) | ```yaml
| `EndingWith` | `findByFirstnameEndingWith` | `… where x.firstname like ?1`(parameter bound with prepended `%`) | spring:
| `Containing` | `findByFirstnameContaining` | `… where x.firstname like ?1`(parameter bound wrapped in `%`) | datasource:
| `OrderBy` | `findByAgeOrderByLastnameDesc` | `… where x.age = ?1 order by x.lastname desc` | url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
| `Not` | `findByLastnameNot` | `… where x.lastname <> ?1` | username: root
| `In` | `findByAgeIn(Collection<Age> ages)` | `… where x.age in ?1` | password: root
| `NotIn` | `findByAgeNotIn(Collection<Age> ages)` | `… where x.age not in ?1` | driver-class-name: com.mysql.cj.jdbc.Driver
| `True` | `findByActiveTrue()` | `… where x.active = true` | jpa:
| `False` | `findByActiveFalse()` | `… where x.active = false` | hibernate:
| `IgnoreCase` | `findByFirstnameIgnoreCase` | `… where UPPER(x.firstame) = UPPER(?1)` | ddl-auto: update
#Hibernate默认创建的表是myisam引擎可以用以下方式指定为使用innodb创建表
#### 2.3 测试类 database-platform: org.hibernate.dialect.MySQL57Dialect
show-sql: true
```java ```
@RunWith(SpringRunner.class)
@SpringBootTest #### 2.2 新建查询接口
public class DataJPATests {
```java
@Autowired /**
private ProgRepository repository; * @author : heibaiying
* @description : 查询接口继承自CrudRepository,CrudRepository 默认定义了部分增删改查方法
/** */
* 保存数据测试 public interface ProgRepository extends CrudRepository<Programmer, Integer> {
*/
@Test /*
public void save() { * 方法名遵循命名规范的查询 更多命名规范可以参考官方文档所列出的这张表格
// 保存单条数据 */
repository.save(new Programmer("pro01", 12, 2121.34f, new Date())); List<Programmer> findAllByName(String name);
// 保存多条数据
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())); Page<Programmer> findAll(Pageable pageable);
programmers.add(new Programmer("pro01", 44, 4561.34f, new Date()));
repository.saveAll(programmers);
} /*
* 占位符查询
*/
/** @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);
*/
@Test
public void get() { /*
* 传入参数名称
// 遵循命名规范的查询 */
List<Programmer> programmers = repository.findAllByName("pro01"); @Query("select u from Programmer u where u.name = :name or u.age = :age")
programmers.forEach(System.out::println); Programmer findByParam(@Param("name") String name,
@Param("age") int age);
// 传入参数名称 }
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); | Keyword | Sample | JPQL snippet |
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
//条件与分页查询 需要注意的是这里的页数是从第0页开始计算的 | `And` | `findByLastnameAndFirstname` | `… where x.lastname = ?1 and x.firstname = ?2` |
Page<Programmer> page = repository.findAll(PageRequest.of(0, 10, Sort.Direction.DESC, "salary")); | `Or` | `findByLastnameOrFirstname` | `… where x.lastname = ?1 or x.firstname = ?2` |
page.get().forEach(System.out::println); | `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` |
@Test | `Before` | `findByStartDateBefore` | `… where x.startDate < ?1` |
public void update() { | `IsNull` | `findByAgeIsNull` | `… where x.age is null` |
// 保存主键相同的数据就认为是更新操作 | `IsNotNull,NotNull` | `findByAge(Is)NotNull` | `… where x.age not null` |
repository.save(new Programmer(1, "updatePro01", 12, 2121.34f, new Date())); | `Like` | `findByFirstnameLike` | `… where x.firstname like ?1` |
Optional<Programmer> programmer = repository.findById(1); | `NotLike` | `findByFirstnameNotLike` | `… where x.firstname not like ?1` |
Assert.assertEquals(programmer.get().getName(), "updatePro01"); | `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` |
@Test | `NotIn` | `findByAgeNotIn(Collection<Age> ages)` | `… where x.age not in ?1` |
public void delete() { | `True` | `findByActiveTrue()` | `… where x.active = true` |
Optional<Programmer> programmer = repository.findById(2); | `False` | `findByActiveFalse()` | `… where x.active = false` |
if (programmer.isPresent()) { | `IgnoreCase` | `findByFirstnameIgnoreCase` | `… where UPPER(x.firstame) = UPPER(?1)` |
repository.deleteById(2);
} #### 2.3 测试类
Assert.assertFalse(programmer.isPresent());
} ```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 # spring boot 整合 druid+mybatis
## 一、说明 ## 目录<br/>
<a href="#一说明">一、说明</a><br/>
#### 1.1 项目结构 &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/>
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下 <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/>
2. 为了使用druid控制台的功能项目以web的方式构建。 &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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid-mybatis.png"/> </div> &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/>
#### 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 |
## 正文<br/> ## 正文<br/>
## 二、整合 druid + mybatis ## 一、说明
#### 2.1 在application.yml 中配置数据源 #### 1.1 项目结构
本用例采用druid作为数据库连接池虽然druid性能略逊于Hikari但是提供了更为全面的监控管理可以按照实际需求选用druid或者Hikari。关于Hikari数据源的配置可以参考[spring-boot-mybatis项目](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mybatis) 1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下
```yaml 2. 为了使用druid控制台的功能项目以web的方式构建。
spring:
datasource: <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid-mybatis.png"/> </div>
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
username: root #### 1.2 项目主要依赖
password: root
driver-class-name: com.mysql.cj.jdbc.Driver 需要说明的是按照spring 官方对于自定义的starter 命名规范的推荐:
# 使用 druid 作为连接池 更多配置的说明可以参见 druid starter 中文文档 https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter - 官方的starter命名spring-boot-starter-XXXX
type: com.alibaba.druid.pool.DruidDataSource - 其他第三方starter命名XXXX-spring-boot-starte
druid:
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法或者第一次getConnection时 所以mybatis的starter命名为mybatis-spring-boot-starter如果有自定义starter需求也需要按照此命名规则进行命名。
initialSize: 5
# 最小连接池数量 ```xml
minIdle: 5 <dependency>
# 最大连接池数量 <groupId>org.mybatis.spring.boot</groupId>
maxActive: 10 <artifactId>mybatis-spring-boot-starter</artifactId>
# 获取连接时最大等待时间单位毫秒。配置了maxWait之后缺省启用公平锁并发效率会有所下降如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 <version>1.3.2</version>
maxWait: 60000 </dependency>
# Destroy线程会检测连接的间隔时间如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 <!--引入mysql驱动-->
timeBetweenEvictionRunsMillis: 60000 <dependency>
# 连接保持空闲而不被驱逐的最小时间 <groupId>mysql</groupId>
minEvictableIdleTimeMillis: 300000 <artifactId>mysql-connector-java</artifactId>
# 用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL <version>8.0.13</version>
validationQuery: SELECT 1 </dependency>
# 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。 <!--druid 依赖-->
testWhileIdle: true <dependency>
# 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。 <groupId>com.alibaba</groupId>
testOnBorrow: false <artifactId>druid-spring-boot-starter</artifactId>
# 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。 <version>1.1.10</version>
testOnReturn: false </dependency>
# 是否自动回收超时连接 ```
removeAbandoned: true
# 超时时间(以秒数为单位) spring boot 与 mybatis 版本的对应关系:
remove-abandoned-timeout: 180
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
# druid 监控的配置 如果不使用 druid 的监控功能的话 以下配置就不是必须的 | --------------------------- | ------------------------------------------------------------ | ------------- |
# 本项目监控台访问地址: http://localhost:8080/druid/login.html | **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 |
# WebStatFilter用于采集web-jdbc关联监控的数据。 | **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
# 更多配置可参见: https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter | **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
web-stat-filter:
# 是否开启 WebStatFilter 默认是true
enabled: true
# 需要拦截的url ## 二、整合 druid + mybatis
url-pattern: /*
# 排除静态资源的请求 #### 2.1 在application.yml 中配置数据源
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
本用例采用druid作为数据库连接池虽然druid性能略逊于Hikari但是提供了更为全面的监控管理可以按照实际需求选用druid或者Hikari。关于Hikari数据源的配置可以参考[spring-boot-mybatis项目](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mybatis)
# Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。
# 更多配置可参见:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE ```yaml
stat-view-servlet: spring:
#是否启用StatViewServlet 默认值true datasource:
enabled: true url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
# 需要拦截的url username: root
url-pattern: /druid/* password: root
# 允许清空统计数据 driver-class-name: com.mysql.cj.jdbc.Driver
reset-enable: true
login-username: druid # 使用 druid 作为连接池 更多配置的说明可以参见 druid starter 中文文档 https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
login-password: druid type: com.alibaba.druid.pool.DruidDataSource
druid:
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法或者第一次getConnection时
initialSize: 5
# mybatis 相关配置 # 最小连接池数量
mybatis: minIdle: 5
configuration: # 最大连接池数量
# 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 maxActive: 10
# oracle数据库建议配置为JdbcType.NULL, 默认是Other # 获取连接时最大等待时间单位毫秒。配置了maxWait之后缺省启用公平锁并发效率会有所下降如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
jdbc-type-for-null: 'null' maxWait: 60000
# 是否打印sql语句 调试的时候可以开启 # Destroy线程会检测连接的间隔时间如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl timeBetweenEvictionRunsMillis: 60000
``` # 连接保持空闲而不被驱逐的最小时间
minEvictableIdleTimeMillis: 300000
#### 2.2 新建查询接口和controller # 用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL
validationQuery: SELECT 1
```java # 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
@Mapper testWhileIdle: true
public interface ProgrammerDao { # 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
testOnBorrow: false
# 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
@Select("select * from programmer") testOnReturn: false
List<Programmer> selectAll(); # 是否自动回收超时连接
removeAbandoned: true
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})") # 超时时间(以秒数为单位)
void save(Programmer programmer); remove-abandoned-timeout: 180
@Select("select * from programmer where name = #{id}") # druid 监控的配置 如果不使用 druid 的监控功能的话 以下配置就不是必须的
Programmer selectById(int id); # 本项目监控台访问地址: http://localhost:8080/druid/login.html
@Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}") # WebStatFilter用于采集web-jdbc关联监控的数据。
int modify(Programmer programmer); # 更多配置可参见: https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter
web-stat-filter:
@Delete(" delete from programmer where id = #{id}") # 是否开启 WebStatFilter 默认是true
void delete(int id); enabled: true
} # 需要拦截的url
``` url-pattern: /*
# 排除静态资源的请求
```xml exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
@RestController
public class ProgrammerController { # Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。
# 更多配置可参见:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
@Autowired stat-view-servlet:
private ProgrammerDao programmerDao; #是否启用StatViewServlet 默认值true
enabled: true
@GetMapping("/programmers") # 需要拦截的url
public List<Programmer> get() { url-pattern: /druid/*
return programmerDao.selectAll(); # 允许清空统计数据
} reset-enable: true
} login-username: druid
``` login-password: druid
#### 2.3 关于druid监控数据的外部化调用
```java # mybatis 相关配置
/** mybatis:
* @author : heibaiying configuration:
* @description :在 Spring Boot 中可以通过 HTTP 接口将 Druid 监控数据以JSON 的形式暴露出去, # 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
* 实际使用中你可以根据你的需要自由地对监控数据、暴露方式进行扩展。 # oracle数据库建议配置为JdbcType.NULL, 默认是Other
*/ jdbc-type-for-null: 'null'
# 是否打印sql语句 调试的时候可以开启
@RestController log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
public class DruidStatController { ```
@GetMapping("/stat") #### 2.2 新建查询接口和controller
public Object druidStat() {
// DruidStatManagerFacade#getDataSourceStatDataList 该方法可以获取所有数据源的监控数据 ```java
return DruidStatManagerFacade.getInstance().getDataSourceStatDataList(); @Mapper
} public interface ProgrammerDao {
}
```
@Select("select * from programmer")
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/druid-status.png"/> </div> List<Programmer> selectAll();
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
void save(Programmer programmer);
#### 2.4 druid 控制台的使用,默认访问地址 http://localhost:8080/druid/login.html
@Select("select * from programmer where name = #{id}")
<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> 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

View File

@ -1,348 +1,362 @@
# spring boot 整合 dubbo # spring boot 整合 dubbo
## 一、 项目结构说明 ## 目录<br/>
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中所以项目采用maven多模块的构建方式在spring-boot-dubbo下构建三个子模块 <a href="#二关键依赖">二、关键依赖</a><br/>
<a href="#三公共模块boot-dubbo-common">三、公共模块boot-dubbo-common</a><br/>
1. boot-dubbo-common 是公共模块用于存放公共的接口和bean,被boot-dubbo-provider和boot-dubbo-consumer在pom.xml中引用 <a href="#四-服务提供者boot-dubbo-provider">四、 服务提供者boot-dubbo-provider</a><br/>
2. boot-dubbo-provider 是服务的提供者,提供商品的查询服务; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-提供方配置">4.1 提供方配置</a><br/>
3. boot-dubbo-consumer是服务的消费者调用provider提供的查询服务。 &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/>
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) &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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dubbo.png"/> </div> <a href="#六项目构建的说明">六、项目构建的说明</a><br/>
<a href="#七关于dubbo新版本管理控制台的安装说明">七、关于dubbo新版本管理控制台的安装说明</a><br/>
## 正文<br/>
## 二、关键依赖
## 一、 项目结构说明
在父工程的项目中统一导入依赖dubbo的starter父工程的pom.xml如下
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中所以项目采用maven多模块的构建方式在spring-boot-dubbo下构建三个子模块
```xml
<?xml version="1.0" encoding="UTF-8"?> 1. boot-dubbo-common 是公共模块用于存放公共的接口和bean,被boot-dubbo-provider和boot-dubbo-consumer在pom.xml中引用
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2. boot-dubbo-provider 是服务的提供者,提供商品的查询服务;
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 3. boot-dubbo-consumer是服务的消费者调用provider提供的查询服务。
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging> 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)
<modules> <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dubbo.png"/> </div>
<module>boot-dubbo-common</module>
<module>boot-dubbo-consumer</module>
<module>boot-dubbo-provider</module>
</modules> ## 二、关键依赖
<parent> 在父工程的项目中统一导入依赖dubbo的starter父工程的pom.xml如下
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> ```xml
<version>2.1.1.RELEASE</version> <?xml version="1.0" encoding="UTF-8"?>
<relativePath/> <!-- lookup parent from repository --> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
</parent> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>com.heibaiying</groupId> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-dubbo</artifactId> <packaging>pom</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-dubbo</name> <modules>
<description>Demo project for Spring Boot</description> <module>boot-dubbo-common</module>
<module>boot-dubbo-consumer</module>
<properties> <module>boot-dubbo-provider</module>
<java.version>1.8</java.version> </modules>
</properties>
<parent>
<dependencies> <groupId>org.springframework.boot</groupId>
<dependency> <artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId> <version>2.1.1.RELEASE</version>
<artifactId>spring-boot-starter-freemarker</artifactId> <relativePath/> <!-- lookup parent from repository -->
</dependency> </parent>
<dependency> <groupId>com.heibaiying</groupId>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dubbo</artifactId>
<artifactId>spring-boot-starter-web</artifactId> <version>0.0.1-SNAPSHOT</version>
</dependency> <name>spring-boot-dubbo</name>
<description>Demo project for Spring Boot</description>
<dependency>
<groupId>org.springframework.boot</groupId> <properties>
<artifactId>spring-boot-devtools</artifactId> <java.version>1.8</java.version>
<scope>runtime</scope> </properties>
</dependency>
<dependency> <dependencies>
<groupId>org.projectlombok</groupId> <dependency>
<artifactId>lombok</artifactId> <groupId>org.springframework.boot</groupId>
<optional>true</optional> <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope> </dependency>
</dependency>
<!--引入dubbo start依赖--> <dependency>
<dependency> <groupId>org.springframework.boot</groupId>
<groupId>com.alibaba.boot</groupId> <artifactId>spring-boot-devtools</artifactId>
<artifactId>dubbo-spring-boot-starter</artifactId> <scope>runtime</scope>
<version>0.2.0</version> </dependency>
</dependency> <dependency>
</dependencies> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</project> <optional>true</optional>
``` </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
## 三、公共模块boot-dubbo-common <scope>test</scope>
</dependency>
- api 下为公共的调用接口; <!--引入dubbo start依赖-->
- bean 下为公共的实体类。 <dependency>
<groupId>com.alibaba.boot</groupId>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-common.png"/> </div> <artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
## 四、 服务提供者boot-dubbo-provider </dependency>
</dependencies>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-provider.png"/> </div>
</project>
#### 4.1 提供方配置 ```
```yaml
dubbo:
application: ## 三、公共模块boot-dubbo-common
name: boot-duboo-provider
# 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心并且在start依赖中引入了zookeeper的java客户端Curator - api 下为公共的调用接口;
registry: - bean 下为公共的实体类。
protocol: zookeeper
address: 127.0.0.1:2181 <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-common.png"/> </div>
protocol.name: dubbo
``` ## 四、 服务提供者boot-dubbo-provider
#### 4.2 使用注解@Service暴露服务 <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-provider.png"/> </div>
需要注意的是这里的@Service注解不是spring的注解而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service #### 4.1 提供方配置
```java ```yaml
package com.heibaiying.dubboprovider.service; dubbo:
application:
import com.alibaba.dubbo.config.annotation.Service; name: boot-duboo-provider
import com.heibaiying.api.IProductService; # 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心并且在start依赖中引入了zookeeper的java客户端Curator
import com.heibaiying.bean.Product; registry:
protocol: zookeeper
import java.util.ArrayList; address: 127.0.0.1:2181
import java.util.Date; protocol.name: dubbo
import java.util.List; ```
/** #### 4.2 使用注解@Service暴露服务
* @author : heibaiying
* @description : 产品提供接口实现类 需要注意的是这里的@Service注解不是spring的注解而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
*/
@Service(timeout = 5000) ```java
public class ProductService implements IProductService { package com.heibaiying.dubboprovider.service;
private static List<Product> productList = new ArrayList<>(); import com.alibaba.dubbo.config.annotation.Service;
import com.heibaiying.api.IProductService;
static { import com.heibaiying.bean.Product;
for (int i = 0; i < 20; i++) {
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i)); import java.util.ArrayList;
} import java.util.Date;
} import java.util.List;
public Product queryProductById(int id) { /**
for (Product product : productList) { * @author : heibaiying
if (product.getId() == id) { * @description : 产品提供接口实现类
return product; */
} @Service(timeout = 5000)
} public class ProductService implements IProductService {
return null;
} private static List<Product> productList = new ArrayList<>();
static {
public List<Product> queryAllProducts() { for (int i = 0; i < 20; i++) {
return productList; productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
} }
} }
``` public Product queryProductById(int id) {
for (Product product : productList) {
## 五、服务消费者boot-dubbo-consumer if (product.getId() == id) {
return product;
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-consumer1.png"/> </div> }
}
#### 1.消费方的配置 return null;
}
```yaml
dubbo:
application: public List<Product> queryAllProducts() {
name: boot-duboo-provider return productList;
# 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心并且在start依赖中引入了zookeeper的java客户端Curator }
registry: }
protocol: zookeeper
address: 127.0.0.1:2181 ```
protocol.name: dubbo
# 关闭所有服务的启动时检查 (没有提供者时报错)视实际情况设置 ## 五、服务消费者boot-dubbo-consumer
consumer:
check: false <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-consumer1.png"/> </div>
server:
port: 8090 #### 1.消费方的配置
```
```yaml
#### 2.使用注解@Reference引用远程服务 dubbo:
application:
```java name: boot-duboo-provider
package com.heibaiying.dubboconsumer.controller; # 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心并且在start依赖中引入了zookeeper的java客户端Curator
registry:
import com.alibaba.dubbo.config.annotation.Reference; protocol: zookeeper
import com.heibaiying.api.IProductService; address: 127.0.0.1:2181
import com.heibaiying.bean.Product; protocol.name: dubbo
import org.springframework.stereotype.Controller; # 关闭所有服务的启动时检查 (没有提供者时报错)视实际情况设置
import org.springframework.ui.Model; consumer:
import org.springframework.web.bind.annotation.PathVariable; check: false
import org.springframework.web.bind.annotation.RequestMapping; server:
port: 8090
import java.util.List; ```
@Controller #### 2.使用注解@Reference引用远程服务
@RequestMapping("sell")
public class SellController { ```java
package com.heibaiying.dubboconsumer.controller;
// dubbo远程引用注解
@Reference import com.alibaba.dubbo.config.annotation.Reference;
private IProductService productService; import com.heibaiying.api.IProductService;
import com.heibaiying.bean.Product;
@RequestMapping import org.springframework.stereotype.Controller;
public String productList(Model model) { import org.springframework.ui.Model;
List<Product> products = productService.queryAllProducts(); import org.springframework.web.bind.annotation.PathVariable;
model.addAttribute("products", products); import org.springframework.web.bind.annotation.RequestMapping;
return "products";
} import java.util.List;
@RequestMapping("product/{id}") @Controller
public String productDetail(@PathVariable int id, Model model) { @RequestMapping("sell")
Product product = productService.queryProductById(id); public class SellController {
model.addAttribute("product", product);
return "product"; // dubbo远程引用注解
} @Reference
} private IProductService productService;
```
@RequestMapping
## 六、项目构建的说明 public String productList(Model model) {
List<Product> products = productService.queryAllProducts();
因为在项目中consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行: model.addAttribute("products", products);
return "products";
```shell }
mvn install -Dmaven.test.skip = true
``` @RequestMapping("product/{id}")
public String productDetail(@PathVariable int id, Model model) {
consumer中 pom.xml如下 Product product = productService.queryProductById(id);
model.addAttribute("product", product);
```xml return "product";
<?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> 因为在项目中consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
<groupId>com.heibaiying</groupId>
<version>0.0.1-SNAPSHOT</version> ```shell
</parent> mvn install -Dmaven.test.skip = true
```
<artifactId>boot-dubbo-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version> consumer中 pom.xml如下
<name>boot-dubbo-consumer</name>
<description>dubbo project for Spring Boot</description> ```xml
<?xml version="1.0" encoding="UTF-8"?>
<properties> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<java.version>1.8</java.version> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
</properties> <modelVersion>4.0.0</modelVersion>
<!--引入对公共模块的依赖--> <parent>
<dependencies> <artifactId>spring-boot-dubbo</artifactId>
<dependency> <groupId>com.heibaiying</groupId>
<groupId>com.heibaiying</groupId> <version>0.0.1-SNAPSHOT</version>
<artifactId>boot-dubbo-common</artifactId> </parent>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope> <artifactId>boot-dubbo-consumer</artifactId>
</dependency> <version>0.0.1-SNAPSHOT</version>
</dependencies> <name>boot-dubbo-consumer</name>
<description>dubbo project for Spring Boot</description>
<build>
<plugins> <properties>
<plugin> <java.version>1.8</java.version>
<groupId>org.springframework.boot</groupId> </properties>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin> <!--引入对公共模块的依赖-->
</plugins> <dependencies>
</build> <dependency>
<groupId>com.heibaiying</groupId>
</project> <artifactId>boot-dubbo-common</artifactId>
``` <version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
provider中 pom.xml如下 </dependency>
</dependencies>
```xml
<?xml version="1.0" encoding="UTF-8"?> <build>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <plugins>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <plugin>
<modelVersion>4.0.0</modelVersion> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<parent> </plugin>
<artifactId>spring-boot-dubbo</artifactId> </plugins>
<groupId>com.heibaiying</groupId> </build>
<version>0.0.1-SNAPSHOT</version>
</parent> </project>
```
<artifactId>boot-dubbo-provider</artifactId> provider中 pom.xml如下
<version>0.0.1-SNAPSHOT</version>
<name>boot-dubbo-provider</name> ```xml
<description>dubbo project for Spring Boot</description> <?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"
<properties> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<java.version>1.8</java.version> <modelVersion>4.0.0</modelVersion>
</properties>
<parent>
<!--引入对公共模块的依赖--> <artifactId>spring-boot-dubbo</artifactId>
<dependencies> <groupId>com.heibaiying</groupId>
<dependency> <version>0.0.1-SNAPSHOT</version>
<groupId>com.heibaiying</groupId> </parent>
<artifactId>boot-dubbo-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope> <artifactId>boot-dubbo-provider</artifactId>
</dependency> <version>0.0.1-SNAPSHOT</version>
</dependencies> <name>boot-dubbo-provider</name>
<description>dubbo project for Spring Boot</description>
<build>
<plugins> <properties>
<plugin> <java.version>1.8</java.version>
<groupId>org.springframework.boot</groupId> </properties>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin> <!--引入对公共模块的依赖-->
</plugins> <dependencies>
</build> <dependency>
<groupId>com.heibaiying</groupId>
</project> <artifactId>boot-dubbo-common</artifactId>
``` <version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
## 七、关于dubbo新版本管理控制台的安装说明 </dependency>
</dependencies>
安装:
<build>
```sh <plugins>
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops <plugin>
cd /var/tmp/dubbo-ops <groupId>org.springframework.boot</groupId>
mvn clean package <artifactId>spring-boot-maven-plugin</artifactId>
``` </plugin>
</plugins>
配置: </build>
```sh </project>
配置文件为: ```
dubbo-admin-backend/src/main/resources/application.properties
主要的配置有 默认的配置就是127.0.0.1:2181 ## 七、关于dubbo新版本管理控制台的安装说明
dubbo.registry.address=zookeeper://127.0.0.1:2181
``` 安装:
启动: ```sh
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
```sh cd /var/tmp/dubbo-ops
mvn --projects dubbo-admin-backend spring-boot:run mvn clean package
``` ```
访问: 配置:
``` ```sh
http://127.0.0.1:8080 配置文件为:
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 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/> ## 正文<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 # spring boot 整合 kafka
## 一、kafka的相关概念 ## 目录<br/>
<a href="#一kafka的相关概念">一、kafka的相关概念</a><br/>
### 1.主题和分区 &nbsp;&nbsp;&nbsp;&nbsp;<a href="#1主题和分区">1.主题和分区</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2分区复制">2.分区复制</a><br/>
kafka 的消息通过主题进行分类。一个主题可以被分为若干个分区一个分区就是一个提交日志。消息以追加的方式写入分区然后以先入先出的顺序读取。kafka通过分区来实现数据的冗余和伸缩性分区可以分布在不同的服务器上也就是说一个主题可以横跨多个服务器以此来提供比单个服务器更强大的性能类比HDFS分布式文件系统 &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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka主题和分区.png"/> </div> &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/>
### 2.分区复制 <a href="#二-整合-kafka">二、 整合 kafka</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-kafka基本配置">2.1 kafka基本配置</a><br/>
每个主题被分为若干个分区,每个分区有多个副本。那些副本被保存在 broker 上,每个 broker 可以保存成百上千个属于不同主题和分区的副本。副本有以下两种类型 &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/>
### 3. 生产者 &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/>
- 也可以通过消息键和分区器来实现把消息直接写到指定的分区这个需要自定义分区器需要实现Partitioner 接口并重写其中的partition方法。 ## 正文<br/>
### 4. 消费者
## 一、kafka的相关概念
消费者是**消费者群组**的一部分。也就是说,会有一个或者多个消费者共同读取一个主题,群组保证每个分区只能被一个消费者使用。
### 1.主题和分区
**一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组里面的多个消费者读取。多个消费者群组可以共同读取同一个主题,彼此之间互不影响**
kafka 的消息通过主题进行分类。一个主题可以被分为若干个分区一个分区就是一个提交日志。消息以追加的方式写入分区然后以先入先出的顺序读取。kafka通过分区来实现数据的冗余和伸缩性分区可以分布在不同的服务器上也就是说一个主题可以横跨多个服务器以此来提供比单个服务器更强大的性能类比HDFS分布式文件系统
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka消费者.png"/> </div>
注意:由于一个主题包含多个分区,因此无法在整个主题范围内保证消息的顺序性,**但可以保证消息在单个分区内的顺序性**。
### 5.broker和集群
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka主题和分区.png"/> </div>
一个独立的kafka服务器被称为broker。broker 接收来自生产者的消息为消息设置偏移量并提交消息到磁盘保存。broker为消费者提供服务对读取分区的请求做出响应返回已经提交到磁盘的消息。
### 2.分区复制
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) - 跟随者副本 首领以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,它们唯一的任务就是从首领那里复制消息,保持与首领一致的状态。如果首领发生崩渍,其中的一个跟随者会被提升为新首领。
### 3. 生产者
## 二、项目说明 - 默认情况下生产者在把消息均衡地分布到在主题的所有分区上,而并不关心特定消息会被写到那个分区;
- 如果指定消息键,则通过对消息键的散列来实现分区;
#### 1.1 项目结构说明 - 也可以通过消息键和分区器来实现把消息直接写到指定的分区这个需要自定义分区器需要实现Partitioner 接口并重写其中的partition方法。
本项目提供kafka发送简单消息、对象消息、和多消费者组消费消息三种情况下的sample。 ### 4. 消费者
1. kafkaSimpleConsumer 用于普通消息的监听; 消费者是**消费者群组**的一部分。也就是说,会有一个或者多个消费者共同读取一个主题,群组保证每个分区只能被一个消费者使用。
2. kafkaBeanConsumer 用于对象消息监听;
3. kafkaGroupConsumer 用于多消费者组和多消费者对主题分区消息监听的情况 **一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组里面的多个消费者读取。多个消费者群组可以共同读取同一个主题,彼此之间互不影响**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka消费者.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-kafka.png"/> </div> ### 5.broker和集群
#### 1.2 主要依赖 一个独立的kafka服务器被称为broker。broker 接收来自生产者的消息为消息设置偏移量并提交消息到磁盘保存。broker为消费者提供服务对读取分区的请求做出响应返回已经提交到磁盘的消息。
```xml broker是集群的组成部分。每一个集群都有一个broker同时充当了集群控制器的角色自动从集群的活跃成员中选举出来。控制器负责管理工作包括将分区分配给broker和监控broker。**在集群中一个分区从属一个broker,该broker被称为分区的首领**。一个分区可以分配给多个broker,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余如果有一个broker失效其他broker可以接管领导权。
<dependency>
<groupId>org.springframework.kafka</groupId> <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka集群复制.png"/> </div>
<artifactId>spring-kafka</artifactId>
</dependency> 更多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)
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope> ## 二、项目说明
</dependency>
``` #### 1.1 项目结构说明
本项目提供kafka发送简单消息、对象消息、和多消费者组消费消息三种情况下的sample。
## 二、 整合 kafka 1. kafkaSimpleConsumer 用于普通消息的监听;
2. kafkaBeanConsumer 用于对象消息监听;
#### 2.1 kafka基本配置 3. kafkaGroupConsumer 用于多消费者组和多消费者对主题分区消息监听的情况。
```yaml
spring:
kafka: <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-kafka.png"/> </div>
# 以逗号分隔的地址列表用于建立与Kafka集群的初始连接(kafka 默认的端口号为9092)
bootstrap-servers: 127.0.0.1:9092 #### 1.2 主要依赖
producer:
# 发生错误后,消息重发的次数。 ```xml
retries: 0 <dependency>
#当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。 <groupId>org.springframework.kafka</groupId>
batch-size: 16384 <artifactId>spring-kafka</artifactId>
# 设置生产者内存缓冲区的大小。 </dependency>
buffer-memory: 33554432 <dependency>
# 键的序列化方式 <groupId>org.springframework.kafka</groupId>
key-serializer: org.apache.kafka.common.serialization.StringSerializer <artifactId>spring-kafka-test</artifactId>
# 值的序列化方式 <scope>test</scope>
value-serializer: org.apache.kafka.common.serialization.StringSerializer </dependency>
# acks=0 生产者在成功写入消息之前不会等待任何来自服务器的响应。 ```
# acks=1 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
acks: 1
consumer: ## 二、 整合 kafka
# 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式如1S,1M,2H,5D
auto-commit-interval: 1S #### 2.1 kafka基本配置
# 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
# latest默认值在偏移量无效的情况下消费者将从最新的记录开始读取数据在消费者启动之后生成的记录 ```yaml
# earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录 spring:
auto-offset-reset: earliest kafka:
# 是否自动提交偏移量默认值是true,为了避免出现重复数据和数据丢失可以把它设置为false,然后手动提交偏移量 # 以逗号分隔的地址列表用于建立与Kafka集群的初始连接(kafka 默认的端口号为9092)
enable-auto-commit: true bootstrap-servers: 127.0.0.1:9092
# 键的反序列化方式 producer:
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer # 发生错误后,消息重发的次数。
# 值的反序列化方式 retries: 0
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer #当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
listener: batch-size: 16384
# 在侦听器容器中运行的线程数 # 设置生产者内存缓冲区的大小
concurrency: 5 buffer-memory: 33554432
# 键的序列化方式
``` key-serializer: org.apache.kafka.common.serialization.StringSerializer
# 值的序列化方式
这里需要说明的是: value-serializer: org.apache.kafka.common.serialization.StringSerializer
# acks=0 生产者在成功写入消息之前不会等待任何来自服务器的响应。
在spring boot 2.X 版本 auto-commit-interval自动提交的时间间隔采用的是值的类型为Duration Duration 是 jdk 1.8 版本之后引入的类,在其源码中我们可以看到对于其字符串的表达需要符合一定的规范,即数字+单位如下的写法1s 1.5s 0s 0.001S 1h 2d 在yaml 中都是有效的。如果传入无效的字符串则spring boot 在启动阶段解析配置文件的时候就会抛出异常。 # acks=1 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
```java acks: 1
public final class Duration consumer:
implements TemporalAmount, Comparable<Duration>, Serializable { # 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式如1S,1M,2H,5D
auto-commit-interval: 1S
/** # 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
* The pattern for parsing. # latest默认值在偏移量无效的情况下消费者将从最新的记录开始读取数据在消费者启动之后生成的记录
*/ # earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
private static final Pattern PATTERN = auto-offset-reset: earliest
Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" + # 是否自动提交偏移量默认值是true,为了避免出现重复数据和数据丢失可以把它设置为false,然后手动提交偏移量
"(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?", Pattern.CASE_INSENSITIVE); enable-auto-commit: true
# 键的反序列化方式
........ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 值的反序列化方式
} value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
``` listener:
# 在侦听器容器中运行的线程数。
#### 2.2 KafkaTemplate实现消息发送 concurrency: 5
```java ```
@Component
@Slf4j 这里需要说明的是:
public class KafKaCustomrProducer {
在spring boot 2.X 版本 auto-commit-interval自动提交的时间间隔采用的是值的类型为Duration Duration 是 jdk 1.8 版本之后引入的类,在其源码中我们可以看到对于其字符串的表达需要符合一定的规范,即数字+单位如下的写法1s 1.5s 0s 0.001S 1h 2d 在yaml 中都是有效的。如果传入无效的字符串则spring boot 在启动阶段解析配置文件的时候就会抛出异常。
@Autowired
private KafkaTemplate kafkaTemplate; ```java
public final class Duration
public void sendMessage(String topic, Object object) { implements TemporalAmount, Comparable<Duration>, Serializable {
/* /**
* 这里的ListenableFuture类是spring对java原生Future的扩展增强,是一个泛型接口,用于监听异步方法的回调 * The pattern for parsing.
* 而对于kafka send 方法返回值而言,这里的泛型所代表的实际类型就是 SendResult<K, V>,而这里K,V的泛型实际上 */
* 被用于ProducerRecord<K, V> producerRecord,即生产者发送消息的key,value 类型 private static final Pattern PATTERN =
*/ Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" +
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, object); "(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?", Pattern.CASE_INSENSITIVE);
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() { ........
@Override
public void onFailure(Throwable throwable) { }
log.info("发送消息失败:" + throwable.getMessage()); ```
}
#### 2.2 KafkaTemplate实现消息发送
@Override
public void onSuccess(SendResult<String, Object> sendResult) { ```java
System.out.println("发送结果:" + sendResult.toString()); @Component
} @Slf4j
}); public class KafKaCustomrProducer {
}
} @Autowired
private KafkaTemplate kafkaTemplate;
```
public void sendMessage(String topic, Object object) {
#### 2.3 @KafkaListener注解实现消息的监听
/*
```java * 这里的ListenableFuture类是spring对java原生Future的扩展增强,是一个泛型接口,用于监听异步方法的回调
@Component * 而对于kafka send 方法返回值而言,这里的泛型所代表的实际类型就是 SendResult<K, V>,而这里K,V的泛型实际上
@Slf4j * 被用于ProducerRecord<K, V> producerRecord,即生产者发送消息的key,value 类型
public class KafkaSimpleConsumer { */
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, object);
// 简单消费者
@KafkaListener(groupId = "simpleGroup", topics = Topic.SIMPLE) future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
public void consumer1_1(ConsumerRecord<String, Object> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) { @Override
System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic); public void onFailure(Throwable throwable) {
/* log.info("发送消息失败:" + throwable.getMessage());
* 如果需要手工提交异步 consumer.commitSync(); }
* 手工同步提交 consumer.commitAsync()
*/ @Override
} public void onSuccess(SendResult<String, Object> sendResult) {
} System.out.println("发送结果:" + sendResult.toString());
``` }
});
#### 2.4 测试整合结果 }
}
```java
@Slf4j ```
@RestController
public class SendMsgController { #### 2.3 @KafkaListener注解实现消息的监听
@Autowired ```java
private KafKaCustomrProducer producer; @Component
@Autowired @Slf4j
private KafkaTemplate kafkaTemplate; 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) {
@GetMapping("sendSimple") System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic);
public void sendSimple() { /*
producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka"); * 如果需要手工提交异步 consumer.commitSync();
} * 手工同步提交 consumer.commitAsync()
} */
``` }
}
```
## 三、关于多消费者组的测试 #### 2.4 测试整合结果
#### 3.1 创建多分区主题 ```java
@Slf4j
```java @RestController
/** public class SendMsgController {
* @author : heibaiying
* @description : kafka配置类 @Autowired
*/ private KafKaCustomrProducer producer;
@Configuration @Autowired
public class KafkaConfig { private KafkaTemplate kafkaTemplate;
@Bean /***
public NewTopic groupTopic() { * 发送消息体为基本类型的消息
// 指定主题名称,分区数量,和复制因子 */
return new NewTopic(Topic.GROUP, 10, (short) 2); @GetMapping("sendSimple")
} public void sendSimple() {
producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka");
} }
``` }
```
#### 3.2 多消费者组对同一主题的监听
1. 消费者1-1 监听主题的 0、1 分区
2. 消费者1-2 监听主题的 2、3 分区 ## 三、关于多消费者组的测试
3. 消费者1-3 监听主题的 0、1 分区
4. 消费者2-1 监听主题的所有分区 #### 3.1 创建多分区主题
```java ```java
/** /**
* @author : heibaiying * @author : heibaiying
* @description : kafka 消费者组 * @description : kafka配置类
* <p> */
* 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。 @Configuration
*/ public class KafkaConfig {
@Component
@Slf4j @Bean
public class KafkaGroupConsumer { public NewTopic groupTopic() {
// 指定主题名称,分区数量,和复制因子
// 分组1 中的消费者1 return new NewTopic(Topic.GROUP, 10, (short) 2);
@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());
} #### 3.2 多消费者组对同一主题的监听
// 分组1 中的消费者2 1. 消费者1-1 监听主题的 0、1 分区
@KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions = 2. 消费者1-2 监听主题的 2、3 分区
{@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"}) 3. 消费者1-3 监听主题的 0、1 分区
}) 4. 消费者2-1 监听主题的所有分区
public void consumer1_2(ConsumerRecord<String, Object> record) {
System.out.println("consumer1-2 收到消息:" + record.value()); ```java
} /**
* @author : heibaiying
// 分组1 中的消费者3 * @description : kafka 消费者组
@KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions = * <p>
{@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"}) * 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。
}) */
public void consumer1_3(ConsumerRecord<String, Object> record) { @Component
System.out.println("consumer1-3 收到消息:" + record.value()); @Slf4j
} public class KafkaGroupConsumer {
// 分组2 中的消费者 // 分组1 中的消费者1
@KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP) @KafkaListener(id = "consumer1-1", groupId = "group1", topicPartitions =
public void consumer2_1(ConsumerRecord<String, Object> record) { {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
System.err.println("consumer2-1 收到消息:" + record.value()); })
} public void consumer1_1(ConsumerRecord<String, Object> record) {
} System.out.println("consumer1-1 收到消息:" + record.value());
}
```
// 分组1 中的消费者2
#### 3.2 发送消息时候指定主题的具体分区 @KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions =
{@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"})
```java })
/*** public void consumer1_2(ConsumerRecord<String, Object> record) {
* 多消费者组、组中多消费者对同一主题的消费情况 System.out.println("consumer1-2 收到消息:" + record.value());
*/ }
@GetMapping("sendGroup")
public void sendGroup() { // 分组1 中的消费者3
for (int i = 0; i < 4; i++) { @KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions =
// 第二个参数指定分区,第三个参数指定消息键 分区优先 {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i); })
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() { public void consumer1_3(ConsumerRecord<String, Object> record) {
@Override System.out.println("consumer1-3 收到消息:" + record.value());
public void onFailure(Throwable throwable) { }
log.info("发送消息失败:" + throwable.getMessage());
} // 分组2 中的消费者
@KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP)
@Override public void consumer2_1(ConsumerRecord<String, Object> record) {
public void onSuccess(SendResult<String, Object> sendResult) { System.err.println("consumer2-1 收到消息:" + record.value());
System.out.println("发送结果:" + sendResult.toString()); }
} }
});
} ```
}
``` #### 3.2 发送消息时候指定主题的具体分区
测试结果: ```java
/***
```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] @GetMapping("sendGroup")
发送结果: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] public void sendGroup() {
发送结果: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] for (int i = 0; i < 4; i++) {
发送结果: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 接收情况 ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i);
consumer2-1 收到消息:hello group 1 future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
consumer2-1 收到消息:hello group 0 @Override
consumer2-1 收到消息:hello group 2 public void onFailure(Throwable throwable) {
consumer2-1 收到消息:hello group 3 log.info("发送消息失败:" + throwable.getMessage());
# 消费者1-1接收情况 }
consumer1-1 收到消息:hello group 1
consumer1-1 收到消息:hello group 0 @Override
# 消费者1-3接收情况 public void onSuccess(SendResult<String, Object> sendResult) {
consumer1-3 收到消息:hello group 1 System.out.println("发送结果:" + sendResult.toString());
consumer1-3 收到消息:hello group 0 }
# 消费者1-2接收情况 });
consumer1-2 收到消息:hello group 3 }
consumer1-2 收到消息:hello group 2 }
``` ```
#### 3.4 测试结果 测试结果
1. 和kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响; ```yaml
2. 和kafka原本机制不一样的是这里我们消费者1-1和消费1-3共同属于同一个消费者组并且监听同样的分区按照原本kafka的机制群组保证每个分区只能被同一个消费者组的一个消费者使用但是按照spring的声明方式实现的消息监听这里被两个消费者都监听到了。 # 主要看每次发送结果中的 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 接收情况
用例采用的是第三方fastjson将实体类序列化为json后发送。实现如下 consumer2-1 收到消息:hello group 1
consumer2-1 收到消息:hello group 0
```java consumer2-1 收到消息:hello group 2
/*** consumer2-1 收到消息:hello group 3
* 发送消息体为bean的消息 # 消费者1-1接收情况
*/ consumer1-1 收到消息:hello group 1
@GetMapping("sendBean") consumer1-1 收到消息:hello group 0
public void sendBean() { # 消费者1-3接收情况
Programmer programmer = new Programmer("xiaoming", 12, 21212.33f, new Date()); consumer1-3 收到消息:hello group 1
producer.sendMessage(Topic.BEAN, JSON.toJSON(programmer).toString()); consumer1-3 收到消息:hello group 0
} # 消费者1-2接收情况
consumer1-2 收到消息:hello group 3
``` consumer1-2 收到消息:hello group 2
```
```java
@Component #### 3.4 测试结果
@Slf4j
public class KafkaBeanConsumer { 1. 和kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响;
2. 和kafka原本机制不一样的是这里我们消费者1-1和消费1-3共同属于同一个消费者组并且监听同样的分区按照原本kafka的机制群组保证每个分区只能被同一个消费者组的一个消费者使用但是按照spring的声明方式实现的消息监听这里被两个消费者都监听到了。
@KafkaListener(groupId = "beanGroup",topics = Topic.BEAN)
public void consumer(ConsumerRecord<String, Object> record) {
System.out.println("消费者收到消息:" + JSON.parseObject(record.value().toString(), Programmer.class));
} ## 四、序列化与反序列化
}
``` 用例采用的是第三方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 # spring boot 整合 mecached
## 一、说明 ## 目录<br/>
<a href="#一说明">一、说明</a><br/>
### 1.1 XMemcached客户端说明 &nbsp;&nbsp;&nbsp;&nbsp;<a href="#11--XMemcached客户端说明">1.1 XMemcached客户端说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
spring boot 官方并没有提供关于 memcached 的starter所以我们这里还是采用XMemcached作为客户端进行整合。 XMemcached是基于java nio的memcached高性能客户端支持完整的memcached协议支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。 &nbsp;&nbsp;&nbsp;&nbsp;<a href="#13-主要依赖">1.3 主要依赖</a><br/>
<a href="#二spring-boot-整合-memcached">二、spring boot 整合 memcached</a><br/>
### 1.2 项目结构说明 &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/>
memcached的整合配置位于config文件夹下。 &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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-memcached.png"/> </div> <a href="#附memcached-基本命令">memcached 基本命令</a><br/>
### 1.3 主要依赖
```xml
<!--memcached java 客户端-->
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.5</version>
</dependency>
```
## 正文<br/> ## 正文<br/>
## 二、spring boot 整合 memcached ## 一、说明
#### 2.1 单机配置 ### 1.1 XMemcached客户端说明
```java spring boot 官方并没有提供关于 memcached 的starter所以我们这里还是采用XMemcached作为客户端进行整合。 XMemcached是基于java nio的memcached高性能客户端支持完整的memcached协议支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
@Bean
public MemcachedClient memcachedClient() { ### 1.2 项目结构说明
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
MemcachedClient memcachedClient = null; memcached的整合配置位于config文件夹下。
try {
memcachedClient = builder.build(); <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-memcached.png"/> </div>
} catch (IOException e) {
e.printStackTrace(); ### 1.3 主要依赖
}
return memcachedClient; ```xml
} <!--memcached java 客户端-->
``` <dependency>
<groupId>com.googlecode.xmemcached</groupId>
#### 2.2 集群配置 <artifactId>xmemcached</artifactId>
<version>2.4.5</version>
```java </dependency>
@Bean ```
public MemcachedClient memcachedClientForCluster() {
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
addressList.add(new InetSocketAddress("192.168.200.201", 11211)); ## 二、spring boot 整合 memcached
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
// 赋予权重 #### 2.1 单机配置
int[] weights = {1, 2};
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights); ```java
// 设置连接池大小 @Bean
builder.setConnectionPoolSize(10); public MemcachedClient memcachedClient() {
// 协议工厂 XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
builder.setCommandFactory(new TextCommandFactory()); MemcachedClient memcachedClient = null;
// 分布策略一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认) try {
builder.setSessionLocator(new KetamaMemcachedSessionLocator()); memcachedClient = builder.build();
// 设置序列化器 } catch (IOException e) {
builder.setTranscoder(new SerializingTranscoder()); e.printStackTrace();
MemcachedClient memcachedClient = null; }
try { return memcachedClient;
memcachedClient = builder.build(); }
} catch (IOException e) { ```
e.printStackTrace();
} #### 2.2 集群配置
return memcachedClient;
} ```java
``` @Bean
public MemcachedClient memcachedClientForCluster() {
#### 2.3 存储基本类型测试用例
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
xmemcached单机版本和集群版本注入的实例是相同的。 addressList.add(new InetSocketAddress("192.168.200.201", 11211));
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
```java // 赋予权重
/** int[] weights = {1, 2};
* @author : heibaiying XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
* @description : Memcached 操作基本对象 // 设置连接池大小
*/ builder.setConnectionPoolSize(10);
@RunWith(SpringRunner.class) // 协议工厂
@SpringBootTest builder.setCommandFactory(new TextCommandFactory());
public class MemSamples { // 分布策略一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
@Autowired // 设置序列化器
private MemcachedClient memcachedClient; builder.setTranscoder(new SerializingTranscoder());
MemcachedClient memcachedClient = null;
@Test try {
public void operate() throws InterruptedException, MemcachedException, TimeoutException { memcachedClient = builder.build();
memcachedClient.set("hello", 0, "Hello,cluster xmemcached"); } catch (IOException e) {
String value = memcachedClient.get("hello"); e.printStackTrace();
System.out.println("hello=" + value); }
memcachedClient.delete("hello"); return memcachedClient;
value = memcachedClient.get("hello"); }
System.out.println("hello=" + value); ```
}
} #### 2.3 存储基本类型测试用例
```
xmemcached单机版本和集群版本注入的实例是相同的。
#### 2.5 存储实体对象测试用例
```java
```java /**
/** * @author : heibaiying
* @author : heibaiying * @description : Memcached 操作基本对象
* @description :Memcached 序列化与反序列化 */
*/ @RunWith(SpringRunner.class)
@RunWith(SpringRunner.class) @SpringBootTest
@SpringBootTest public class MemSamples {
public class MemObjectSamples {
@Autowired
@Autowired private MemcachedClient memcachedClient;
private MemcachedClient memcachedClient;
@Test
@Test public void operate() throws InterruptedException, MemcachedException, TimeoutException {
public void operate() throws InterruptedException, MemcachedException, TimeoutException { memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date())); String value = memcachedClient.get("hello");
Programmer programmer = memcachedClient.get("programmer"); System.out.println("hello=" + value);
System.out.println("hello ," + programmer.getName()); memcachedClient.delete("hello");
memcachedClient.delete("programmer"); value = memcachedClient.get("hello");
programmer = memcachedClient.get("programmer"); System.out.println("hello=" + value);
Assert.assertNull(programmer); }
} }
} ```
``` #### 2.5 存储实体对象测试用例
```java
/**
## 附memcached 基本命令 * @author : heibaiying
* @description :Memcached 序列化与反序列化
| 命令 | 格式 | 说明 | */
| --------------- | -------------------------------------------------- | ------------------------------------- | @RunWith(SpringRunner.class)
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 | @SpringBootTest
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下才可以插入 | public class MemObjectSamples {
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 | @Autowired
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 | private MemcachedClient memcachedClient;
| 查询操作 get | get key | |
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 | @Test
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 | public void operate() throws InterruptedException, MemcachedException, TimeoutException {
| 删除 delete | delete key | 将数据打一个删除标记 | memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value | Programmer programmer = memcachedClient.get("programmer");
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 | 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以下 | | 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |

View File

@ -1,182 +1,192 @@
# spring boot 整合 mongodb # spring boot 整合 mongodb
## 一、说明 ## 目录<br/>
<a href="#一说明">一、说明</a><br/>
#### 1.1 用例结构 &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/>
1. 本用例提供mongdb的简单整合用例; <a href="#二整合-mongodb">二、整合 mongodb</a><br/>
2. 提供用MongoTemplate的方式操作mongdb见测试用例MongoOriginalTests.java &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-在applicationyml-中配置mongodb数据源">2.1 在application.yml 中配置mongodb数据源</a><br/>
3. 提供基于spring data jpa 的方式操作mongodb(推荐)见测试用例MongoJPATests.java &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/>
<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>
```
## 正文<br/> ## 正文<br/>
## 二、整合 mongodb ## 一、说明
#### 2.1 在application.yml 中配置mongodb数据源 #### 1.1 用例结构
```yaml 1. 本用例提供mongdb的简单整合用例
spring: 2. 提供用MongoTemplate的方式操作mongdb见测试用例MongoOriginalTests.java
data: 3. 提供基于spring data jpa 的方式操作mongodb(推荐)见测试用例MongoJPATests.java
mongodb:
database: spring <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mongodb.png"/> </div>
uri: mongodb://192.168.0.108:27017
``` #### 1.2 项目主要依赖
#### 2.2 基于MongoTemplate实现对mongodb的操作 ```xml
<dependency>
```java <groupId>org.springframework.boot</groupId>
@RunWith(SpringRunner.class) <artifactId>spring-boot-starter-data-mongodb</artifactId>
@SpringBootTest </dependency>
public class MongoOriginalTests { ```
@Autowired
private MongoTemplate mongoTemplate;
## 二、整合 mongodb
@Test
public void insert() { #### 2.1 在application.yml 中配置mongodb数据源
// 单条插入
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date())); ```yaml
List<Programmer> programmers = new ArrayList<Programmer>(); spring:
// 批量插入 data:
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date())); mongodb:
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date())); database: spring
mongoTemplate.insert(programmers, Programmer.class); uri: mongodb://192.168.0.108:27017
} ```
// 条件查询 #### 2.2 基于MongoTemplate实现对mongodb的操作
@Test
public void select() { ```java
Criteria criteria = new Criteria(); @RunWith(SpringRunner.class)
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21)); @SpringBootTest
Query query = new Query(criteria); public class MongoOriginalTests {
Programmer one = mongoTemplate.findOne(query, Programmer.class);
System.out.println(one); @Autowired
} private MongoTemplate mongoTemplate;
@Test
// 更新数据 public void insert() {
@Test // 单条插入
public void MUpdate() { mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class); List<Programmer> programmers = new ArrayList<Programmer>();
System.out.println("更新记录数:" + updateResult.getModifiedCount()); // 批量插入
} 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 delete() {
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class); // 条件查询
System.out.println("影响记录数:" + result.getDeletedCount()); @Test
System.out.println("是否成功:" + result.wasAcknowledged()); 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);
#### 2.3 使用 data jpa 方式操作mongodb (推荐使用) }
1.新建查询结构,查询方法按照支持的关键字命名
// 更新数据
```java @Test
public interface ProgrammerRepository extends MongoRepository<Programmer, String> { public void MUpdate() {
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
void deleteAllByName(String name); System.out.println("更新记录数:" + updateResult.getModifiedCount());
}
Programmer findAllByName(String names);
// 删除指定数据
Programmer findByNameAndAge(String name, int age); @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.测试 }
```java }
@RunWith(SpringRunner.class) ```
@SpringBootTest
public class MongoJPATests { #### 2.3 使用 data jpa 方式操作mongodb (推荐使用)
@Autowired 1.新建查询结构,查询方法按照支持的关键字命名
private ProgrammerRepository repository;
```java
@Test public interface ProgrammerRepository extends MongoRepository<Programmer, String> {
public void insert() {
// 单条插入 void deleteAllByName(String name);
repository.save(new Programmer("python", 23, 21832.34f, new Date()));
// 批量插入 Programmer findAllByName(String names);
List<Programmer> programmers = new ArrayList<Programmer>();
programmers.add(new Programmer("java", 21, 52200.21f, new Date())); Programmer findByNameAndAge(String name, int age);
programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
repository.saveAll(programmers); }
} ```
// 条件查询 2.测试
@Test
public void select() { ```java
Programmer java = repository.findByNameAndAge("java", 21); @RunWith(SpringRunner.class)
Assert.assertEquals(java.getSalary(), 52200.21f, 0.01); @SpringBootTest
} public class MongoJPATests {
@Autowired
// 更新数据 private ProgrammerRepository repository;
@Test
public void MUpdate() { @Test
repository.save(new Programmer("Go", 8, 500.21f, new Date())); public void insert() {
Programmer go = repository.findAllByName("Go"); // 单条插入
Assert.assertEquals(go.getAge(), 8); 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()));
@Test programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
public void delete() { repository.saveAll(programmers);
repository.deleteAllByName("python"); }
Optional<Programmer> python = repository.findById("python");
Assert.assertFalse(python.isPresent()); // 条件查询
} @Test
public void select() {
} Programmer java = repository.findByNameAndAge("java", 21);
``` Assert.assertEquals(java.getSalary(), 52200.21f, 0.01);
}
查询方法支持的关键字如下,更多命名规范可以参见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 | // 更新数据
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | @Test
| `After` | `findByBirthdateAfter(Date date)` | `{"birthdate" : {"$gt" : date}}` | public void MUpdate() {
| `GreaterThan` | `findByAgeGreaterThan(int age)` | `{"age" : {"$gt" : age}}` | repository.save(new Programmer("Go", 8, 500.21f, new Date()));
| `GreaterThanEqual` | `findByAgeGreaterThanEqual(int age)` | `{"age" : {"$gte" : age}}` | Programmer go = repository.findAllByName("Go");
| `Before` | `findByBirthdateBefore(Date date)` | `{"birthdate" : {"$lt" : date}}` | Assert.assertEquals(go.getAge(), 8);
| `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…]}}` | @Test
| `NotIn` | `findByAgeNotIn(Collection ages)` | `{"age" : {"$nin" : [ages…]}}` | public void delete() {
| `IsNotNull`, `NotNull` | `findByFirstnameNotNull()` | `{"firstname" : {"$ne" : null}}` | repository.deleteAllByName("python");
| `IsNull`, `Null` | `findByFirstnameNull()` | `{"firstname" : null}` | Optional<Programmer> python = repository.findById("python");
| `Like`, `StartingWith`, `EndingWith` | `findByFirstnameLike(String name)` | `{"firstname" : name} (name as regex)` | Assert.assertFalse(python.isPresent());
| `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 }}` | 查询方法支持的关键字如下,更多命名规范可以参见Spring Data MongoDB官方文档[Query Methods](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongodb.repositories.queries)
| `(No keyword)` | `findByFirstname(String name)` | `{"firstname" : name}` |
| `Not` | `findByFirstnameNot(String name)` | `{"firstname" : {"$ne" : name}}` | | Keyword | Sample | Logical result |
| `Near` | `findByLocationNear(Point point)` | `{"location" : {"$near" : [x,y]}}` | | ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| `Near` | `findByLocationNear(Point point, Distance max)` | `{"location" : {"$near" : [x,y], "$maxDistance" : max}}` | | `After` | `findByBirthdateAfter(Date date)` | `{"birthdate" : {"$gt" : date}}` |
| `Near` | `findByLocationNear(Point point, Distance min, Distance max)` | `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}` | | `GreaterThan` | `findByAgeGreaterThan(int age)` | `{"age" : {"$gt" : age}}` |
| `Within` | `findByLocationWithin(Circle circle)` | `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}` | | `GreaterThanEqual` | `findByAgeGreaterThanEqual(int age)` | `{"age" : {"$gte" : age}}` |
| `Within` | `findByLocationWithin(Box box)` | `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}` | | `Before` | `findByBirthdateBefore(Date date)` | `{"birthdate" : {"$lt" : date}}` |
| `IsTrue`, `True` | `findByActiveIsTrue()` | `{"active" : true}` | | `LessThan` | `findByAgeLessThan(int age)` | `{"age" : {"$lt" : age}}` |
| `IsFalse`, `False` | `findByActiveIsFalse()` | `{"active" : false}` | | `LessThanEqual` | `findByAgeLessThanEqual(int age)` | `{"age" : {"$lte" : age}}` |
| `Exists` | `findByLocationExists(boolean exists)` | `{"location" : {"$exists" : exists }}` | | `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/> ## 正文<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 # spring boot 整合 rabbitmq
## 一、 项目结构说明 ## 目录<br/>
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
1.1 之前关于spring 整合 rabbitmq 我们采用的是单项目的方式为了使得用例更具有实际意义这里采用maven多模块的构建方式在spring-boot-rabbitmq下构建三个子模块 <a href="#二关键依赖">二、关键依赖</a><br/>
<a href="#三公共模块rabbitmq-common">三、公共模块rabbitmq-common</a><br/>
1. rabbitmq-common 是公共模块用于存放公共的接口、配置和bean,被rabbitmq-producer和rabbitmq-consumer在pom.xml中引用 <a href="#四服务消费者rabbitmq-consumer">四、服务消费者rabbitmq-consumer</a><br/>
2. rabbitmq-producer 是消息的生产者模块; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-消息消费者配置">4.1 消息消费者配置</a><br/>
3. rabbitmq-consumer是消息的消费者模块。 &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/>
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),里面有详细的配图说明。 &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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-rabbitmq.png"/> </div> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#53--以单元测试的方式发送消息">5.3 以单元测试的方式发送消息</a><br/>
<a href="#六项目构建的说明">六、项目构建的说明</a><br/>
## 正文<br/>
## 二、关键依赖
## 一、 项目结构说明
在父工程的项目中统一导入依赖rabbitmq的starter(spring-boot-starter-amqp)父工程的pom.xml如下
1.1 之前关于spring 整合 rabbitmq 我们采用的是单项目的方式为了使得用例更具有实际意义这里采用maven多模块的构建方式在spring-boot-rabbitmq下构建三个子模块
```xml
<?xml version="1.0" encoding="UTF-8"?> 1. rabbitmq-common 是公共模块用于存放公共的接口、配置和bean,被rabbitmq-producer和rabbitmq-consumer在pom.xml中引用
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2. rabbitmq-producer 是消息的生产者模块;
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 3. rabbitmq-consumer是消息的消费者模块。
<modelVersion>4.0.0</modelVersion> 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),里面有详细的配图说明。
<packaging>pom</packaging>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-rabbitmq.png"/> </div>
<modules>
<module>rabbitmq-consumer</module>
<module>rabbitmq-producer</module>
<module>rabbitmq-common</module> ## 二、关键依赖
</modules>
在父工程的项目中统一导入依赖rabbitmq的starter(spring-boot-starter-amqp)父工程的pom.xml如下
<parent>
<groupId>org.springframework.boot</groupId> ```xml
<artifactId>spring-boot-starter-parent</artifactId> <?xml version="1.0" encoding="UTF-8"?>
<version>2.1.1.RELEASE</version> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<relativePath/> <!-- lookup parent from repository --> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
</parent>
<groupId>com.heibaiying</groupId> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-boot-rabbitmq</artifactId> <packaging>pom</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-rabbitmq</name> <modules>
<description>RabbitMQ project for Spring Boot</description> <module>rabbitmq-consumer</module>
<module>rabbitmq-producer</module>
<properties> <module>rabbitmq-common</module>
<java.version>1.8</java.version> </modules>
</properties>
<parent>
<dependencies> <groupId>org.springframework.boot</groupId>
<dependency> <artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId> <version>2.1.1.RELEASE</version>
<artifactId>spring-boot-starter-amqp</artifactId> <relativePath/> <!-- lookup parent from repository -->
</dependency> </parent>
<dependency> <groupId>com.heibaiying</groupId>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-rabbitmq</artifactId>
<artifactId>spring-boot-starter-web</artifactId> <version>0.0.1-SNAPSHOT</version>
</dependency> <name>spring-boot-rabbitmq</name>
<description>RabbitMQ project for Spring Boot</description>
<dependency>
<groupId>org.projectlombok</groupId> <properties>
<artifactId>lombok</artifactId> <java.version>1.8</java.version>
<optional>true</optional> </properties>
</dependency>
<dependency> <dependencies>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-starter-test</artifactId> <groupId>org.springframework.boot</groupId>
<scope>test</scope> <artifactId>spring-boot-starter-amqp</artifactId>
</dependency> </dependency>
</dependencies> <dependency>
<groupId>org.springframework.boot</groupId>
</project> <artifactId>spring-boot-starter-web</artifactId>
``` </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
## 三、公共模块rabbitmq-common <artifactId>lombok</artifactId>
<optional>true</optional>
- bean 下为公共的实体类。 </dependency>
- constant 下为公共配置,用静态常量引用。(这里我使用静态常量是为了方便引用,实际中也可以按照情况,抽取为公共配置文件) <dependency>
<groupId>org.springframework.boot</groupId>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-common.png"/> </div> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
```java </dependency>
package com.heibaiying.constant; </dependencies>
/** </project>
* @author : heibaiying ```
* @description : rabbit 公用配置信息
*/
public class RabbitInfo {
## 三、公共模块rabbitmq-common
// queue 配置
public static final String QUEUE_NAME = "spring.boot.simple.queue"; - bean 下为公共的实体类。
public static final String QUEUE_DURABLE = "true"; - constant 下为公共配置,用静态常量引用。(这里我使用静态常量是为了方便引用,实际中也可以按照情况,抽取为公共配置文件)
// exchange 配置 <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-common.png"/> </div>
public static final String EXCHANGE_NAME = "spring.boot.simple.exchange";
public static final String EXCHANGE_TYPE = "topic"; ```java
package com.heibaiying.constant;
// routing key
public static final String ROUTING_KEY = "springboot.simple.*"; /**
} * @author : heibaiying
* @description : rabbit 公用配置信息
``` */
public class RabbitInfo {
// queue 配置
## 四、服务消费者rabbitmq-consumer public static final String QUEUE_NAME = "spring.boot.simple.queue";
public static final String QUEUE_DURABLE = "true";
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-consumer.png"/> </div>
// exchange 配置
#### 4.1 消息消费者配置 public static final String EXCHANGE_NAME = "spring.boot.simple.exchange";
public static final String EXCHANGE_TYPE = "topic";
```yaml
spring: // routing key
rabbitmq: public static final String ROUTING_KEY = "springboot.simple.*";
addresses: 127.0.0.1:5672 }
# RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672 ```
username: guest
password: guest
virtual-host: /
listener: ## 四、服务消费者rabbitmq-consumer
simple:
# 为了保证信息能够被正确消费,建议签收模式设置为手工签收,并在代码中实现手工签收 <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-consumer.png"/> </div>
acknowledge-mode: manual
# 侦听器调用者线程的最小数量 #### 4.1 消息消费者配置
concurrency: 10
# 侦听器调用者线程的最大数量 ```yaml
max-concurrency: 50 spring:
``` rabbitmq:
addresses: 127.0.0.1:5672
#### 4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者 # RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
1. 使用注解创建的交换机、队列、和绑定关系会在项目初始化的时候自动创建,但是不会重复创建; username: guest
2. 这里我们创建两个消息监听器,分别演示消息是基本类型和消息是对象时的配置区别。 password: guest
virtual-host: /
```java listener:
/** simple:
* @author : heibaiying # 为了保证信息能够被正确消费,建议签收模式设置为手工签收,并在代码中实现手工签收
* @description : 消息是对象的消费者 acknowledge-mode: manual
*/ # 侦听器调用者线程的最小数量
concurrency: 10
@Component # 侦听器调用者线程的最大数量
@Slf4j max-concurrency: 50
public class RabbitmqBeanConsumer { ```
@RabbitListener(bindings = @QueueBinding( #### 4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者
value = @Queue(value = RabbitBeanInfo.QUEUE_NAME, durable = RabbitBeanInfo.QUEUE_DURABLE),
exchange = @Exchange(value = RabbitBeanInfo.EXCHANGE_NAME, type = RabbitBeanInfo.EXCHANGE_TYPE), 1. 使用注解创建的交换机、队列、和绑定关系会在项目初始化的时候自动创建,但是不会重复创建;
key = RabbitBeanInfo.ROUTING_KEY) 2. 这里我们创建两个消息监听器,分别演示消息是基本类型和消息是对象时的配置区别。
)
@RabbitHandler ```java
public void onMessage(@Payload Programmer programmer, @Headers Map<String, Object> headers, Channel channel) throws Exception { /**
log.info("programmer:{} ", programmer); * @author : heibaiying
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG); * @description : 消息是对象的消费者
channel.basicAck(deliveryTag, false); */
}
} @Component
``` @Slf4j
public class RabbitmqBeanConsumer {
```java
@Component @RabbitListener(bindings = @QueueBinding(
@Slf4j value = @Queue(value = RabbitBeanInfo.QUEUE_NAME, durable = RabbitBeanInfo.QUEUE_DURABLE),
public class RabbitmqConsumer { exchange = @Exchange(value = RabbitBeanInfo.EXCHANGE_NAME, type = RabbitBeanInfo.EXCHANGE_TYPE),
key = RabbitBeanInfo.ROUTING_KEY)
@RabbitListener(bindings = @QueueBinding( )
value = @Queue(value = RabbitInfo.QUEUE_NAME, durable = RabbitInfo.QUEUE_DURABLE), @RabbitHandler
exchange = @Exchange(value = RabbitInfo.EXCHANGE_NAME, type = RabbitInfo.EXCHANGE_TYPE), public void onMessage(@Payload Programmer programmer, @Headers Map<String, Object> headers, Channel channel) throws Exception {
key = RabbitInfo.ROUTING_KEY) log.info("programmer:{} ", programmer);
) Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
@RabbitHandler channel.basicAck(deliveryTag, false);
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是一个单调递增的正整数 ```java
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG); @Component
// 第二个参数代表是否一次签收多条,当该参数为 true 时,则可以一次性确认 DELIVERY_TAG 小于等于传入值的所有消息 @Slf4j
channel.basicAck(deliveryTag, false); 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
## 五、 消息生产者rabbitmq-producer public void onMessage(Message message, Channel channel) throws Exception {
MessageHeaders headers = message.getHeaders();
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-producer.png"/> </div> // 获取消息头信息和消息体
log.info("msgInfo:{} ; payload:{} ", headers.get("msgInfo"), message.getPayload());
#### 5.1 消息生产者配置 // DELIVERY_TAG 代表 RabbitMQ 向该Channel投递的这条消息的唯一标识ID是一个单调递增的正整数
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
```yaml // 第二个参数代表是否一次签收多条,当该参数为 true 时,则可以一次性确认 DELIVERY_TAG 小于等于传入值的所有消息
spring: channel.basicAck(deliveryTag, false);
rabbitmq: }
addresses: 127.0.0.1:5672
# RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/" }
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672 ```
username: guest
password: guest
virtual-host: /
# 是否启用发布者确认 具体确认回调实现见代码 ## 五、 消息生产者rabbitmq-producer
publisher-confirms: true
# 是否启用发布者返回 具体返回回调实现见代码 <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-producer.png"/> </div>
publisher-returns: true
# 是否启用强制消息 保证消息的有效监听 #### 5.1 消息生产者配置
template.mandatory: true
```yaml
server: spring:
port: 8090 rabbitmq:
``` addresses: 127.0.0.1:5672
# RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
#### 5.2 创建消息生产者 # 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
username: guest
```java password: guest
/** virtual-host: /
* @author : heibaiying # 是否启用发布者确认 具体确认回调实现见代码
* @description : 消息生产者 publisher-confirms: true
*/ # 是否启用发布者返回 具体返回回调实现见代码
@Component publisher-returns: true
@Slf4j # 是否启用强制消息 保证消息的有效监听
public class RabbitmqProducer { template.mandatory: true
@Autowired server:
private RabbitTemplate rabbitTemplate; port: 8090
```
public void sendSimpleMessage(Map<String, Object> headers, Object message,
String messageId, String exchangeName, String key) { #### 5.2 创建消息生产者
// 自定义消息头
MessageHeaders messageHeaders = new MessageHeaders(headers); ```java
// 创建消息 /**
Message<Object> msg = MessageBuilder.createMessage(message, messageHeaders); * @author : heibaiying
/* 确认的回调 确认消息是否到达 Broker 服务器 其实就是是否到达交换器 * @description : 消息生产者
如果发送时候指定的交换器不存在 ack就是false 代表消息不可达 */ */
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { @Component
log.info("correlationData{} , ack:{}", correlationData.getId(), ack); @Slf4j
if (!ack) { public class RabbitmqProducer {
System.out.println("进行对应的消息补偿机制");
} @Autowired
}); private RabbitTemplate rabbitTemplate;
/* 消息失败的回调
* 例如消息已经到达交换器上,但路由键匹配任何绑定到该交换器的队列,会触发这个回调,此时 replyText: NO_ROUTE public void sendSimpleMessage(Map<String, Object> headers, Object message,
*/ String messageId, String exchangeName, String key) {
rabbitTemplate.setReturnCallback((message1, replyCode, replyText, exchange, routingKey) -> { // 自定义消息头
log.info("message:{}; replyCode: {}; replyText: {} ; exchange:{} ; routingKey:{}", MessageHeaders messageHeaders = new MessageHeaders(headers);
message1, replyCode, replyText, exchange, routingKey); // 创建消息
}); Message<Object> msg = MessageBuilder.createMessage(message, messageHeaders);
// 在实际中ID 应该是全局唯一 能够唯一标识消息 消息不可达的时候触发ConfirmCallback回调方法时可以获取该值进行对应的错误处理 /* 确认的回调 确认消息是否到达 Broker 服务器 其实就是是否到达交换器
CorrelationData correlationData = new CorrelationData(messageId); 如果发送时候指定的交换器不存在 ack就是false 代表消息不可达 */
rabbitTemplate.convertAndSend(exchangeName, key, msg, correlationData); rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
} log.info("correlationData{} , ack:{}", correlationData.getId(), ack);
} if (!ack) {
``` System.out.println("进行对应的消息补偿机制");
}
#### 5.3 以单元测试的方式发送消息 });
/* 消息失败的回调
```java * 例如消息已经到达交换器上,但路由键匹配任何绑定到该交换器的队列,会触发这个回调,此时 replyText: NO_ROUTE
@RunWith(SpringRunner.class) */
@SpringBootTest rabbitTemplate.setReturnCallback((message1, replyCode, replyText, exchange, routingKey) -> {
public class RabbitmqProducerTests { log.info("message:{}; replyCode: {}; replyText: {} ; exchange:{} ; routingKey:{}",
message1, replyCode, replyText, exchange, routingKey);
@Autowired });
private RabbitmqProducer producer; // 在实际中ID 应该是全局唯一 能够唯一标识消息 消息不可达的时候触发ConfirmCallback回调方法时可以获取该值进行对应的错误处理
CorrelationData correlationData = new CorrelationData(messageId);
/*** rabbitTemplate.convertAndSend(exchangeName, key, msg, correlationData);
* 发送消息体为简单数据类型的消息 }
*/ }
@Test ```
public void send() {
Map<String, Object> heads = new HashMap<>(); #### 5.3 以单元测试的方式发送消息
heads.put("msgInfo", "自定义消息头信息");
// 模拟生成消息ID,在实际中应该是全局唯一的 消息不可达时候可以在setConfirmCallback回调中取得可以进行对应的重发或错误处理 ```java
String id = String.valueOf(Math.round(Math.random() * 10000)); @RunWith(SpringRunner.class)
producer.sendSimpleMessage(heads, "hello Spring", id, RabbitInfo.EXCHANGE_NAME, "springboot.simple.abc"); @SpringBootTest
} public class RabbitmqProducerTests {
@Autowired
/*** private RabbitmqProducer producer;
* 发送消息体为bean的消息
*/ /***
@Test * 发送消息体为简单数据类型的消息
public void sendBean() { */
String id = String.valueOf(Math.round(Math.random() * 10000)); @Test
Programmer programmer = new Programmer("xiaoMing", 12, 12123.45f, new Date()); public void send() {
producer.sendSimpleMessage(null, programmer, id, RabbitBeanInfo.EXCHANGE_NAME, RabbitBeanInfo.ROUTING_KEY); 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的消息
因为在项目中consumer和producer模块均依赖公共模块,所以在构建consumer和producer项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行: */
@Test
```shell public void sendBean() {
mvn install -Dmaven.test.skip = true 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中 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> 因为在项目中consumer和producer模块均依赖公共模块,所以在构建consumer和producer项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
<artifactId>spring-boot-rabbitmq</artifactId>
<version>0.0.1-SNAPSHOT</version> ```shell
</parent> mvn install -Dmaven.test.skip = true
```
<artifactId>rabbitmq-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version> consumer中 pom.xml如下
<name>rabbitmq-consumer</name>
<description>RabbitMQ consumer project for Spring Boot</description> ```xml
<?xml version="1.0" encoding="UTF-8"?>
<properties> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<java.version>1.8</java.version> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
</properties> <modelVersion>4.0.0</modelVersion>
<dependencies> <parent>
<dependency> <groupId>com.heibaiying</groupId>
<groupId>com.heibaiying</groupId> <artifactId>spring-boot-rabbitmq</artifactId>
<artifactId>rabbitmq-common</artifactId> <version>0.0.1-SNAPSHOT</version>
<version>0.0.1-SNAPSHOT</version> </parent>
<scope>compile</scope>
</dependency> <artifactId>rabbitmq-consumer</artifactId>
</dependencies> <version>0.0.1-SNAPSHOT</version>
<name>rabbitmq-consumer</name>
<build> <description>RabbitMQ consumer project for Spring Boot</description>
<plugins>
<plugin> <properties>
<groupId>org.springframework.boot</groupId> <java.version>1.8</java.version>
<artifactId>spring-boot-maven-plugin</artifactId> </properties>
</plugin>
</plugins> <dependencies>
</build> <dependency>
</project> <groupId>com.heibaiying</groupId>
``` <artifactId>rabbitmq-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
producer中 pom.xml如下 <scope>compile</scope>
</dependency>
```xml </dependencies>
<?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" <build>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <plugins>
<modelVersion>4.0.0</modelVersion> <plugin>
<groupId>org.springframework.boot</groupId>
<parent> <artifactId>spring-boot-maven-plugin</artifactId>
<groupId>com.heibaiying</groupId> </plugin>
<artifactId>spring-boot-rabbitmq</artifactId> </plugins>
<version>0.0.1-SNAPSHOT</version> </build>
</parent> </project>
```
<artifactId>rabbitmq-producer</artifactId>
<version>0.0.1-SNAPSHOT</version> producer中 pom.xml如下
<name>rabbitmq-producer</name>
<description>RabbitMQ producer project for Spring Boot</description> ```xml
<?xml version="1.0" encoding="UTF-8"?>
<properties> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<java.version>1.8</java.version> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
</properties> <modelVersion>4.0.0</modelVersion>
<dependencies> <parent>
<dependency> <groupId>com.heibaiying</groupId>
<groupId>com.heibaiying</groupId> <artifactId>spring-boot-rabbitmq</artifactId>
<artifactId>rabbitmq-common</artifactId> <version>0.0.1-SNAPSHOT</version>
<version>0.0.1-SNAPSHOT</version> </parent>
<scope>compile</scope>
</dependency> <artifactId>rabbitmq-producer</artifactId>
</dependencies> <version>0.0.1-SNAPSHOT</version>
<name>rabbitmq-producer</name>
<build> <description>RabbitMQ producer project for Spring Boot</description>
<plugins>
<plugin> <properties>
<groupId>org.springframework.boot</groupId> <java.version>1.8</java.version>
<artifactId>spring-boot-maven-plugin</artifactId> </properties>
</plugin>
</plugins> <dependencies>
</build> <dependency>
<groupId>com.heibaiying</groupId>
</project> <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 # spring boot 整合 redis
## 一、说明 ## 目录<br/>
<a href="#一说明">一、说明</a><br/>
#### 1.1 项目结构 &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/>
1. RedisConfig.java实现了redisTemplate 序列化与反序列化的配置; <a href="#二整合-Redis">二、整合 Redis</a><br/>
2. RedisOperation和RedisObjectOperation分别封装了对基本类型和对象的操作。 &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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-redis.png"/> </div> &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/>
#### 1.2 项目主要依赖 <a href="#附Redis的数据结构和操作命令">Redis的数据结构和操作命令</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-预备">1.1 预备</a><br/>
```xml &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#111-全局命令">1.1.1 全局命令</a><br/>
<!--redis starter --> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#112-数据结构和内部编码">1.1.2 数据结构和内部编码</a><br/>
<dependency> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#113-单线程架构">1.1.3 单线程架构</a><br/>
<groupId>org.springframework.boot</groupId> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-字符串">1.2 字符串</a><br/>
<artifactId>spring-boot-starter-data-redis</artifactId> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#13-哈希">1.3 哈希</a><br/>
</dependency> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#14-列表">1.4 列表</a><br/>
<!--jackson 序列化包 --> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#15-集合">1.5 集合</a><br/>
<dependency> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#16-有序集合">1.6 有序集合</a><br/>
<groupId>com.fasterxml.jackson.core</groupId> &nbsp;&nbsp;&nbsp;&nbsp;<a href="#17-键管理">1.7 键管理</a><br/>
<artifactId>jackson-databind</artifactId> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#171-单个键管理">1.7.1 单个键管理</a><br/>
<version>2.9.8</version> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#1键重命名">1.键重命名 </a><br/>
</dependency> &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/>
## 二、整合 Redis &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/>
#### 2.1 在application.yml 中配置redis数据源 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#2flushdb/flushall">2.flushdb/flushall </a><br/>
## 正文<br/>
```yaml
spring:
redis: ## 一、说明
host: 127.0.0.1
port: 6379 #### 1.1 项目结构
# 默认采用的也是 0 号数据库 redis官方在4.0之后版本就不推荐采用单节点多数据库(db1-db15)的方式存储数据,如果有需要应该采用集群方式构建
database: 0 1. RedisConfig.java实现了redisTemplate 序列化与反序列化的配置;
2. RedisOperation和RedisObjectOperation分别封装了对基本类型和对象的操作。
# 如果是集群节点 采用如下配置指定节点
#spring.redis.cluster.nodes <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-redis.png"/> </div>
```
#### 1.2 项目主要依赖
#### 2.2 封装redis基本操作
```xml
需要说明的是spring boot 提供了两个template 用于操作redis: <!--redis starter -->
<dependency>
- StringRedisTemplate由于redis在大多数使用情况下都是操作字符串类型的存储所以spring boot 将对字符串的操作单独封装在StringRedisTemplate <groupId>org.springframework.boot</groupId>
- RedisTemplate<Object, Object> redis 用于操作任意类型的template。 <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
```java <!--jackson 序列化包 -->
/** <dependency>
* @author : heibaiying <groupId>com.fasterxml.jackson.core</groupId>
* @description : redis 基本操作 <artifactId>jackson-databind</artifactId>
*/ <version>2.9.8</version>
</dependency>
@Component ```
public class RedisOperation {
@Autowired
private StringRedisTemplate redisTemplate; ## 二、整合 Redis
/*** #### 2.1 在application.yml 中配置redis数据源
* 操作普通字符串
*/ ```yaml
public void StringSet(String key, String value) { spring:
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue(); redis:
valueOperations.set(key, value); host: 127.0.0.1
} port: 6379
# 默认采用的也是 0 号数据库 redis官方在4.0之后版本就不推荐采用单节点多数据库(db1-db15)的方式存储数据,如果有需要应该采用集群方式构建
/*** database: 0
* 操作列表
*/ # 如果是集群节点 采用如下配置指定节点
public void ListSet(String key, List<String> values) { #spring.redis.cluster.nodes
ListOperations<String, String> listOperations = redisTemplate.opsForList(); ```
values.forEach(value -> listOperations.leftPush(key, value));
} #### 2.2 封装redis基本操作
/*** 需要说明的是spring boot 提供了两个template 用于操作redis:
* 操作集合
*/ - StringRedisTemplate由于redis在大多数使用情况下都是操作字符串类型的存储所以spring boot 将对字符串的操作单独封装在StringRedisTemplate
public void SetSet(String key, Set<String> values) { - RedisTemplate<Object, Object> redis 用于操作任意类型的template。
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
values.forEach(value -> setOperations.add(key, value)); ```java
} /**
* @author : heibaiying
/*** * @description : redis 基本操作
* 获取字符串 */
*/
public String StringGet(String key) { @Component
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue(); public class RedisOperation {
return valueOperations.get(key);
} @Autowired
private StringRedisTemplate redisTemplate;
/***
* 列表弹出元素 /***
*/ * 操作普通字符串
public String ListLeftPop(String key) { */
ListOperations<String, String> listOperations = redisTemplate.opsForList(); public void StringSet(String key, String value) {
return listOperations.leftPop(key, 2, TimeUnit.SECONDS); ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
} valueOperations.set(key, value);
}
/***
* 集合弹出元素 /***
*/ * 操作列表
public String SetPop(String key) { */
SetOperations<String, String> setOperations = redisTemplate.opsForSet(); public void ListSet(String key, List<String> values) {
return setOperations.pop(key); ListOperations<String, String> listOperations = redisTemplate.opsForList();
} values.forEach(value -> listOperations.leftPush(key, value));
}
}
``` /***
* 操作集合
```java */
/** public void SetSet(String key, Set<String> values) {
* @author : heibaiying SetOperations<String, String> setOperations = redisTemplate.opsForSet();
* @description : redis 基本操作 values.forEach(value -> setOperations.add(key, value));
*/ }
@Component /***
public class RedisObjectOperation { * 获取字符串
*/
@Autowired public String StringGet(String key) {
private RedisTemplate<Object, Object> objectRedisTemplate; ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
/*** }
* 操作对象
*/ /***
public void ObjectSet(Object key, Object value) { * 列表弹出元素
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue(); */
valueOperations.set(key, value); public String ListLeftPop(String key) {
} ListOperations<String, String> listOperations = redisTemplate.opsForList();
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
/*** }
* 操作元素为对象列表
*/ /***
public void ListSet(Object key, List<Object> values) { * 集合弹出元素
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList(); */
values.forEach(value -> listOperations.leftPush(key, value)); public String SetPop(String key) {
} SetOperations<String, String> setOperations = redisTemplate.opsForSet();
return setOperations.pop(key);
/*** }
* 操作元素为对象集合
*/ }
public void SetSet(Object key, Set<Object> values) { ```
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
values.forEach(value -> setOperations.add(key, value)); ```java
} /**
* @author : heibaiying
/*** * @description : redis 基本操作
* 获取对象 */
*/
public Object ObjectGet(Object key) { @Component
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue(); public class RedisObjectOperation {
return valueOperations.get(key);
} @Autowired
private RedisTemplate<Object, Object> objectRedisTemplate;
/***
* 列表弹出元素 /***
*/ * 操作对象
public Object ListLeftPop(Object key) { */
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList(); public void ObjectSet(Object key, Object value) {
return listOperations.leftPop(key, 2, TimeUnit.SECONDS); ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
} valueOperations.set(key, value);
}
/***
* 集合弹出元素 /***
*/ * 操作元素为对象列表
public Object SetPop(Object key) { */
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet(); public void ListSet(Object key, List<Object> values) {
return setOperations.pop(key); ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
} values.forEach(value -> listOperations.leftPush(key, value));
}
}
``` /***
* 操作元素为对象集合
#### 2.3 redisTemplate 序列化为json格式与反序列化 */
public void SetSet(Object key, Set<Object> values) {
这里需要说明的spring boot 的 redisTemplate 本身是实现了对象的序列化与反序列化的是支持直接存取对象的。但是这里的序列化默认采用的是JdkSerializationRedisSerializer.serialize()序列化为二进制码这个本身是不影响redisTemplate 的操作的因为redisTemplate在取出数据的时候会自动进行反序列化。 SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
values.forEach(value -> setOperations.add(key, value));
但是如果我们在命令行中使用get命令去获取数据时候得到的是一串不直观的二进制码所以我们尽量将其序列化为直观的json格式存储。 }
```java /***
/** * 获取对象
* @author : heibaiying */
* @description : 自定义序列化器 public Object ObjectGet(Object key) {
* 不定义的话默认采用的是serializer.JdkSerializationRedisSerializer.serialize()序列化为二进制字节码 存储在数据库中不直观 ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
*/ return valueOperations.get(key);
@Configuration }
public class RedisConfig {
/***
@Bean * 列表弹出元素
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { */
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); public Object ListLeftPop(Object key) {
redisTemplate.setConnectionFactory(redisConnectionFactory); ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
// 使用Jackson2JsonRedisSerialize 需要导入依赖 com.fasterxml.jackson.core jackson-databind }
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
/***
ObjectMapper objectMapper = new ObjectMapper(); * 集合弹出元素
// 第一个参数表示: 表示所有访问者都受到影响 包括 字段, getter / isGetter,settercreator */
// 第二个参数表示: 所有类型的访问修饰符都是可接受的,不论是公有还有私有表变量都会被序列化 public Object SetPop(Object key) {
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); return setOperations.pop(key);
}
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
}
// 设置key,value 序列化规则 ```
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer()); #### 2.3 redisTemplate 序列化为json格式与反序列化
redisTemplate.afterPropertiesSet();
return redisTemplate; 这里需要说明的spring boot 的 redisTemplate 本身是实现了对象的序列化与反序列化的是支持直接存取对象的。但是这里的序列化默认采用的是JdkSerializationRedisSerializer.serialize()序列化为二进制码这个本身是不影响redisTemplate 的操作的因为redisTemplate在取出数据的时候会自动进行反序列化。
}
} 但是如果我们在命令行中使用get命令去获取数据时候得到的是一串不直观的二进制码所以我们尽量将其序列化为直观的json格式存储。
```
```java
#### 2.4 测试 /**
* @author : heibaiying
```java * @description : 自定义序列化器
@RunWith(SpringRunner.class) * 不定义的话默认采用的是serializer.JdkSerializationRedisSerializer.serialize()序列化为二进制字节码 存储在数据库中不直观
@SpringBootTest */
public class RedisTests { @Configuration
public class RedisConfig {
@Autowired
private RedisOperation redisOperation; @Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
@Test RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
public void StringOperation() { redisTemplate.setConnectionFactory(redisConnectionFactory);
redisOperation.StringSet("hello", "redis");
String s = redisOperation.StringGet("hello"); // 使用Jackson2JsonRedisSerialize 需要导入依赖 com.fasterxml.jackson.core jackson-databind
Assert.assertEquals(s, "redis"); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
}
ObjectMapper objectMapper = new ObjectMapper();
@Test // 第一个参数表示: 表示所有访问者都受到影响 包括 字段, getter / isGetter,settercreator
public void ListOperation() { // 第二个参数表示: 所有类型的访问修饰符都是可接受的,不论是公有还有私有表变量都会被序列化
redisOperation.ListSet("skill", Arrays.asList("java", "oracle", "vue")); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
String s = redisOperation.ListLeftPop("skill"); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
Assert.assertEquals(s, "vue");
} jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
/* // 设置key,value 序列化规则
* 需要注意的是Redis的集合set不仅不允许有重复元素并且集合中的元素是无序的 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
* 不能通过索引下标获取元素。哪怕你在java中传入的集合是有序的newLinkedHashSet但是实际在Redis存储的还是无序的集合 redisTemplate.setKeySerializer(new StringRedisSerializer());
*/ redisTemplate.afterPropertiesSet();
@Test return redisTemplate;
public void SetOperation() { }
redisOperation.SetSet("skillSet", Sets.newLinkedHashSet("java", "oracle", "vue")); }
String s = redisOperation.SetPop("skillSet"); ```
Assert.assertNotNull(s);
} #### 2.4 测试
} ```java
``` @RunWith(SpringRunner.class)
@SpringBootTest
<br/> public class RedisTests {
## 附Redis的数据结构和操作命令 @Autowired
private RedisOperation redisOperation;
### 1.1 预备
@Test
#### 1.1.1 全局命令 public void StringOperation() {
redisOperation.StringSet("hello", "redis");
1. 查看所有键: **keys \*** String s = redisOperation.StringGet("hello");
Assert.assertEquals(s, "redis");
2. 查看键总数:**dbsize** }
3. 检查键是否存在:**exists key** @Test
public void ListOperation() {
4. 删除键:**del key [key ...]** 支持删除多个键 redisOperation.ListSet("skill", Arrays.asList("java", "oracle", "vue"));
String s = redisOperation.ListLeftPop("skill");
5. 键过期:**expire key seconds** Assert.assertEquals(s, "vue");
}
ttl命令会返回键的剩余过期时间 它有3种返回值
/*
- 大于等于0的整数 键剩余的过期时间。 * 需要注意的是Redis的集合set不仅不允许有重复元素并且集合中的元素是无序的
- -1 键没设置过期时间。 * 不能通过索引下标获取元素。哪怕你在java中传入的集合是有序的newLinkedHashSet但是实际在Redis存储的还是无序的集合
- -2 键不存在 */
@Test
6. 键的数据结构 **type key** public void SetOperation() {
redisOperation.SetSet("skillSet", Sets.newLinkedHashSet("java", "oracle", "vue"));
#### 1.1.2 数据结构和内部编码 String s = redisOperation.SetPop("skillSet");
Assert.assertNotNull(s);
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所示。 <br/>
3. 单线程避免了线程切换和竞态产生的消耗。
## 附Redis的数据结构和操作命令
### 1.2 字符串
### 1.1 预备
| 作用 | 格式 | 参数或示例 |
| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | #### 1.1.1 全局命令
| 设置值 | 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(空) | 1. 查看所有键: **keys \***
| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 |
| 批量获取值 | mget key [key ...] | mget a b c d | 2. 查看键总数:**dbsize**
| 计数 | incr key decr key incrby key increment指定数值自增<br/>decrby key decrement指定数值自减<br/>incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。<br/>键不存在创建键并按照值为0自增或自减 返回结果为1。 |
| 追加值 | append key value | 向字符串的默认追加值 | 3. 检查键是否存在:**exists key**
| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
| 设置并返回原值 | getset key value | | 4. 删除键:**del key [key ...]** 支持删除多个键
| 设置指定位置的租字符串 | setrange key offeset value | |
| 获取部分字符串 | getrange key start end | | 5. 键过期:**expire key seconds**
### 1.3 哈希 ttl命令会返回键的剩余过期时间 它有3种返回值
| 作用 | 格式 | 参数或示例 | - 大于等于0的整数 键剩余的过期时间。
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | - -1 键没设置过期时间。
| 设置值 | hset key field value | hset user:1 name tom<br/>hset user:1 age 12 | - -2 键不存在
| 获取值 | hget key field | hget user:1 name |
| 删除field | hdel key field [field ...] | | 6. 键的数据结构 **type key**
| 计算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 | #### 1.1.2 数据结构和内部编码
| 判断field是否存在 | hexists key field | |
| 获取所有field | hkeys key | | type命令实际返回的就是当前键的数据结构类型 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合)
| 获取所有value | hvals key | |
| 获取所有的filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。<br/>获取全部 可以使用hscan命令 该命令会渐进式遍历哈希类型 | #### 1.1.3 单线程架构
| 计数 | hincrby key field<br/>hincrbyfloat key field | |
1. 纯内存访问, Redis将所有数据放在内存中 内存的响应时长大约为100纳秒 这是Redis达到每秒万级别访问的重要基础。
### 1.4 列表 2. 非阻塞I/O Redis使用epoll作为I/O多路复用技术的实现 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间 如图2-6所示。
3. 单线程避免了线程切换和竞态产生的消耗。
| 作用 | 格式 | 参数或示例 |
| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ### 1.2 字符串
| 增 | 左侧插入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 | | | 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds 为键设置秒级过期时间。 <br/>px milliseconds 为键设置毫秒级过期时间。<br/>nx 键必须不存在, 才可以设置成功, 用于添加。<br/>xx 与nx相反 键必须存在, 才可以设置成功, 用于更新。 |
| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout | key[key...] 多个列表的键。 timeout 阻塞时间\|等待时间(单位: 秒) | | 获取值 | 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。 |
### 1.5 集合 | 追加值 | append key value | 向字符串的默认追加值 |
| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
集合set 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素** 并且集合中的元素是无序的, **不能通过索引下标获取元素** | 设置并返回原值 | getset key value | |
| 设置指定位置的租字符串 | setrange key offeset value | |
**集合内操作** | 获取部分字符串 | getrange key start end | |
| 作用 | 格式 | 参数或示例 | ### 1.3 哈希
| -------------------- | ------------------------------ | ----------------------------------------- |
| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 | | 作用 | 格式 | 参数或示例 |
| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 | | ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 计算元素个数 | scard key | | | 设置值 | hset key field value | hset user:1 name tom<br/>hset user:1 age 12 |
| 判断元素是否在集合中 | sismember key element | | | 获取值 | hget key field | hget user:1 name |
| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素count 默认为1 | | 删除field | hdel key field [field ...] | |
| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素spop 会 | | 计算field个数 | hlen key | |
| 获取集合中所有元素 | smembers key | 可用sscan 代替 | | 批量设置或获取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 | |
| 求多个集合的交集 | sinter key [key ...] |
| 求多个集合的并集 | suinon key [key ...] | ### 1.4 列表
| 求多个集合的差集 | sdiff key [key ...] |
| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...] <br/>suionstore destination key [key ...]<br/>sdiffstore destination key [key ...] | | 作用 | 格式 | 参数或示例 |
| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
### 1.6 有序集合 | 增 | 左侧插入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 |
有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数score 作为排序的依据。 | 删 | 从列表左侧弹出元素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 阻塞时间\|等待时间(单位: 秒) |
| 作用 | 格式 | 参数或示例 |
| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 添加成员 | zadd key score member [score member ...] | nx member必须不存在 才可设置成功, 用于添加。<br> xx member必须存在 才可以设置成功, 用于更新。<br/>ch 返回此次操作后, 有序集合元素和分数发生变化的个数<br/>incr 对score做增加 相当于后面介绍的zincrby。 | ### 1.5 集合
| 计算成员个数 | zcard key | |
| 计算某个成员的分数 | zscore key member | | 集合set 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素** 并且集合中的元素是无序的, **不能通过索引下标获取元素**
| 计算某个成员的排名 | 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 | | | 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 |
| 删除指定分数范围的成员 | zremrangebyscore key min max | | | 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 |
| 计算元素个数 | scard key | |
**集合间操作** | 判断元素是否在集合中 | sismember key element | |
| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素count 默认为1 |
| 作用 | 格式 | | 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素spop 会 |
| ---- | ------------------------------------------------------------ | | 获取集合中所有元素 | smembers key | 可用sscan 代替 |
| 交集 | 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...] 需要做交集计算的键。 | 求多个集合的交集 | sinter key [key ...] |
- weights weight[weight...] 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重 每个键的权重默认是1。 | 求多个集合的并集 | suinon key [key ...] |
- aggregate sum|min|max 计算成员交集后, 分值可以按照sum 、min最小值 、 max最大值 做汇总, 默认值是sum。 | 求多个集合的差集 | sdiff key [key ...] |
| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...] <br/>suionstore destination key [key ...]<br/>sdiffstore destination key [key ...] |
### 1.7 键管理
### 1.6 有序集合
#### 1.7.1 单个键管理
有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数score 作为排序的依据。
##### 1.键重命名
**集合内操作**
**rename key newkey**
| 作用 | 格式 | 参数或示例 |
为了防止被强行rename Redis提供了renamenx命令 确保只有newKey不存在时候才被覆盖。 | ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 添加成员 | zadd key score member [score member ...] | nx member必须不存在 才可设置成功, 用于添加。<br> xx member必须存在 才可以设置成功, 用于更新。<br/>ch 返回此次操作后, 有序集合元素和分数发生变化的个数<br/>incr 对score做增加 相当于后面介绍的zincrby。 |
##### 2. 随机返回键 | 计算成员个数 | zcard key | |
| 计算某个成员的分数 | zscore key member | |
**random key** | 计算某个成员的排名 | zrank key member zrevrank key member | zrank是从分数从低到高返回排名 zrevrank反之。 |
| 删除成员 | zrem key member [member ...] | |
##### 3.键过期 | 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom |
| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回 zrevrange反之。 |
- expire key seconds 键在seconds秒后过期。 | 返回指定分数范围内的成员 | 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分别代表无限小和无限大 |
- expireat key timestamp 键在秒级时间戳timestamp后过期。 | 删除指定排名内的升序元素 | zremrangerank key start end | |
- pexpire key milliseconds 键在milliseconds毫秒后过期。 | 删除指定分数范围的成员 | zremrangebyscore key min max | |
- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期
**集合间操作**
注意:
| 作用 | 格式 |
1. 如果expire key的键不存在 返回结果为0 | ---- | ------------------------------------------------------------ |
2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样 | 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
3. persist key t命令可以将键的过期时间清除 | 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
4. 对于字符串类型键, 执行set命令会去掉过期时间 这个问题很容易在开发中被忽视
5. Redis不支持二级数据结构例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置 - destination 交集计算结果保存到这个键。
6. setex命令作为set+expire的组合 不但是原子执行, 同时减少了一次网络通讯的时间 - numkeys 需要做交集计算键的个数。
- key[key...] 需要做交集计算的键。
#### 1.7.2 键遍历 - weights weight[weight...] 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重 每个键的权重默认是1。
- aggregate sum|min|max 计算成员交集后, 分值可以按照sum 、min最小值 、 max最大值 做汇总, 默认值是sum。
##### 1. 全量键遍历
### 1.7 键管理
**keys pattern**
#### 1.7.1 单个键管理
##### 2. 渐进式遍历
##### 1.键重命名
scan cursor \[match pattern] \[count number]
**rename key newkey**
- cursor是必需参数 实际上cursor是一个游标 第一次遍历从0开始 每次scan遍历完都会返回当前游标的值 直到游标值为0 表示遍历结束。
- match pattern是可选参数 它的作用的是做模式的匹配, 这点和keys的模式匹配很像 为了防止被强行rename Redis提供了renamenx命令 确保只有newKey不存在时候才被覆盖
- count number是可选参数 它的作用是表明每次要遍历的键个数, 默认值是10 此参数可以适当增大。
##### 2. 随机返回键
#### 1.7.3 数据库管理
**random key**
##### 1.切换数据库
##### 3.键过期
**select dbIndex**
- expire key seconds 键在seconds秒后过期。
##### 2.flushdb/flushall - 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

View File

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

View File

@ -1,101 +1,110 @@
# spring boot 整合 tomcat # spring boot 整合 tomcat
## 一、说明 ## 目录<br/>
<a href="#一说明">一、说明</a><br/>
#### 1.1 项目结构说明 &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/>
spring boot 整合 tomcat 后支持jsp 的使用内置容器默认是不支持jsp所以项目整合后采用jspController 跳转到show.jsp测试整合是否成功。 <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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-tomcat.png"/> </div> &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/>
#### 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>
```
## 正文<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 # spring boot websocket
## 一、说明 ## 目录<br/>
<a href="#一说明">一、说明</a><br/>
### 1.1 项目结构说明 &nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-主要依赖">1.2 主要依赖</a><br/>
1. 项目模拟一个简单的群聊功能为区分不同的聊天客户端登录时候将临时用户名存储在session当中 <a href="#二spring-boot-websocket">二、spring boot websocket</a><br/>
2. 关于websocket的主要配置在websocket文件夹下 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-创建消息处理类ChatSocket使用@ServerEndpoint声明websocket服务">2.1 创建消息处理类ChatSocket使用@ServerEndpoint声明websocket服务</a><br/>
3. 模板引擎采用freemaker &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-配置ServerEndpointExporterServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务。">2.2 配置ServerEndpointExporterServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务</a><br/>
4. 项目以web的方式构建。 &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/>
<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";
}
}
```
## 正文<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 # spring-boot-yml-profile
## 一、项目结构 ## 目录<br/>
<a href="#一项目结构">一、项目结构</a><br/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-yml-profile.png"/> </div> <a href="#二常用-yaml-语法讲解">二、常用 yaml 语法讲解</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-基本规则">2.1 基本规则</a><br/>
## 二、常用 yaml 语法讲解 &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/>
项目中的yml配置文件如下 &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/>
```yaml &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#26-特殊符号">2.6 特殊符号</a><br/>
programmer: <a href="#三spring-boot-与-yaml">三、spring boot 与 yaml</a><br/>
name: xiaoming-DEV &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31--spring-boot-支持使用-${appname}-引用预先定义的值">3.1 spring boot 支持使用 ${app.name} 引用预先定义的值</a><br/>
married: false &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-spring-boot-支持使用-${randomxxx}-配置随机值">3.2 spring boot 支持使用 ${random.xxx} 配置随机值</a><br/>
hireDate: 2018/12/23 <a href="#四@ConfigurationProperties实现属性绑定">四、@ConfigurationProperties实现属性绑定</a><br/>
salary: 66666.88 <a href="#五多配置文件">五、多配置文件</a><br/>
random: ${random.int[1024,65536]} <a href="#六优先级的说明">六、优先级的说明</a><br/>
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中的配置文件。
对上面的配置中常用的规则可以精简如下:
## 正文<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中的配置文件。
对上面的配置中常用的规则可以精简如下:

View File

@ -1,433 +1,455 @@
# spring-cloud-config # spring-cloud-config
## 一、config 简介 ## 目录<br/>
<a href="#一config-简介">一、config 简介</a><br/>
spring cloud config 分为服务端和客户端服务端称为分布式配置中心集中管理配置文件客户端为各个业务单元它们从配置中心获取相关配置同时config 还实现了配置热更新,在服务不停机的情况下刷新配置。 <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/>
+ config-server: 配置中心; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#35-http请求地址和资源文件映射">3.5 http请求地址和资源文件映射</a><br/>
+ config-client: 服务单元,可以从配置中心获取相关配置; <a href="#四config-client-搭建">四、config-client 搭建</a><br/>
+ eureka: 注册中心。 &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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-config.png"/> </div> &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/>
## 三、config-server 配置中心的实现 &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/>
#### 3.1 导入依赖 &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/>
```xml
<?xml version="1.0" encoding="UTF-8"?> ## 一、config 简介
<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"> spring cloud config 分为服务端和客户端服务端称为分布式配置中心集中管理配置文件客户端为各个业务单元它们从配置中心获取相关配置同时config 还实现了配置热更新,在服务不停机的情况下刷新配置。
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.heibaiying.config</groupId> ## 二、项目结构
<artifactId>spring-cloud-config</artifactId>
<version>0.0.1-SNAPSHOT</version> + config-server: 配置中心;
</parent> + config-client: 服务单元,可以从配置中心获取相关配置;
+ eureka: 注册中心。
<artifactId>config-server</artifactId>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-config.png"/> </div>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> ## 三、config-server 配置中心的实现
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId> #### 3.1 导入依赖
</dependency>
<dependency> ```xml
<groupId>org.springframework.cloud</groupId> <?xml version="1.0" encoding="UTF-8"?>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
</dependency> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
</dependencies> <modelVersion>4.0.0</modelVersion>
<build> <parent>
<plugins> <groupId>com.heibaiying.config</groupId>
<plugin> <artifactId>spring-cloud-config</artifactId>
<groupId>org.springframework.boot</groupId> <version>0.0.1-SNAPSHOT</version>
<artifactId>spring-boot-maven-plugin</artifactId> </parent>
</plugin>
</plugins> <artifactId>config-server</artifactId>
</build>
<dependencies>
</project> <dependency>
``` <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
#### 3.2 在启动类上添加@EnableDiscoveryClient和@EnableConfigServer 注解 <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
```java </dependency>
@SpringBootApplication <dependency>
@EnableDiscoveryClient <groupId>org.springframework.cloud</groupId>
@EnableConfigServer <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
public class ConfigServerApplication { </dependency>
</dependencies>
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args); <build>
} <plugins>
<plugin>
} <groupId>org.springframework.boot</groupId>
``` <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
#### 3.3 指定注册中心地址,并配置git仓库地址的配置文件路径
</project>
```yaml ```
server:
port: 8020
# 指定注册中心地址
eureka: #### 3.2 在启动类上添加@EnableDiscoveryClient和@EnableConfigServer 注解
client:
serviceUrl: ```java
defaultZone: http://localhost:8010/eureka/ @SpringBootApplication
# 指定服务命名 @EnableDiscoveryClient
spring: @EnableConfigServer
application: public class ConfigServerApplication {
name: config-server
cloud: public static void main(String[] args) {
config: SpringApplication.run(ConfigServerApplication.class, args);
server: }
git:
uri: https://github.com/heibaiying/spring-samples-for-all/ }
search-paths: spring-cloud/spring-cloud-test-config/ ```
# 如果代码仓库是公开的 则 不需要设置用户名和密码
username:
password:
# 指定拉取的配置文件的存放位置,配置文件最后存储的目录为 basedir + search-paths #### 3.3 指定注册中心地址,并配置git仓库地址的配置文件路径
# 这个地方还需要注意的是,配置文件的仓库最好只放配置文件
# 因为配置中心不仅会拉取search-paths下的文件还会把uri指定仓库中的全部文件拉取到basedir下 ```yaml
basedir: D:\git-config server:
# 指定分支 port: 8020
label: master # 指定注册中心地址
``` eureka:
client:
这里的git 仓库就是本用例的仓库,是公开的仓库,所以不用配置用户名和密码,配置文件如下 serviceUrl:
defaultZone: http://localhost:8010/eureka/
- application.yml 为主配置; # 指定服务命名
- application-dev.yml 为开发环境配置。 spring:
application:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-git.png"/> </div> name: config-server
cloud:
config:
server:
#### 3.4 启动eureka和config-server服务访问 http://localhost:8020/application-dev.yml git:
uri: https://github.com/heibaiying/spring-samples-for-all/
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-application-dev.png"/> </div> search-paths: spring-cloud/spring-cloud-test-config/
# 如果代码仓库是公开的 则 不需要设置用户名和密码
这里需要注意的拉取配置的时候我们此时指定拉取的是dev配置application.yml实际 配置如下: username:
password:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-dev.png"/> </div> # 指定拉取的配置文件的存放位置,配置文件最后存储的目录为 basedir + search-paths
# 这个地方还需要注意的是,配置文件的仓库最好只放配置文件
这说明在用配置中心拉取配置的时候和我们在本地开发的时候是一致的配置是互补的即dev中的实际配置应该是主配置和dev配置的结合且遵循同名属性精确优先的原则。 # 因为配置中心不仅会拉取search-paths下的文件还会把uri指定仓库中的全部文件拉取到basedir下
basedir: D:\git-config
# 指定分支
label: master
#### 3.5 http请求地址和资源文件映射 ```
在本用例中如果我们想要直接访问主配置,用以下路径 http://localhost:8020/application.yml 是不行的,会得到错误页面。如果想要访问主配置,可以用http://localhost:8020/application-X.yml其中可以是任意字符原因是 这里的git 仓库就是本用例的仓库,是公开的仓库,所以不用配置用户名和密码,配置文件如下
请求地址和实际的配置文件应该遵循以下规则application为配置文件名profile 为环境label为分支如果不指定默认就是master分支 - application.yml 为主配置;
- application-dev.yml 为开发环境配置。
- /{application}/{profile}[/{label}]
- /{application}-{profile}.yml <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-git.png"/> </div>
- /{label}/{application}-{profile}.yml
- /{application}-{profile}.properties
- /{label}/{application}-{profile}.properties
#### 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>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-a.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>
## 四、config-client 搭建
这说明在用配置中心拉取配置的时候和我们在本地开发的时候是一致的配置是互补的即dev中的实际配置应该是主配置和dev配置的结合且遵循同名属性精确优先的原则。
#### 4.1 导入依赖
```xml
<?xml version="1.0" encoding="UTF-8"?> #### 3.5 http请求地址和资源文件映射
<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"> 在本用例中如果我们想要直接访问主配置,用以下路径 http://localhost:8020/application.yml 是不行的,会得到错误页面。如果想要访问主配置,可以用http://localhost:8020/application-X.yml其中可以是任意字符原因是
<modelVersion>4.0.0</modelVersion>
请求地址和实际的配置文件应该遵循以下规则application为配置文件名profile 为环境label为分支如果不指定默认就是master分支
<parent>
<groupId>com.heibaiying.config</groupId> - /{application}/{profile}[/{label}]
<artifactId>spring-cloud-config</artifactId> - /{application}-{profile}.yml
<version>0.0.1-SNAPSHOT</version> - /{label}/{application}-{profile}.yml
</parent> - /{application}-{profile}.properties
- /{label}/{application}-{profile}.properties
<artifactId>config-client</artifactId>
访问主配置:
<dependencies>
<dependency> <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-a.png"/> </div>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--config client--> ## 四、config-client 搭建
<dependency>
<groupId>org.springframework.cloud</groupId> #### 4.1 导入依赖
<artifactId>spring-cloud-starter-config</artifactId>
</dependency> ```xml
<!--eureka-client--> <?xml version="1.0" encoding="UTF-8"?>
<dependency> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<groupId>org.springframework.cloud</groupId> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <modelVersion>4.0.0</modelVersion>
</dependency>
</dependencies> <parent>
<groupId>com.heibaiying.config</groupId>
<build> <artifactId>spring-cloud-config</artifactId>
<plugins> <version>0.0.1-SNAPSHOT</version>
<plugin> </parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>config-client</artifactId>
</plugin>
</plugins> <dependencies>
</build> <dependency>
<groupId>org.springframework.boot</groupId>
</project> <artifactId>spring-boot-starter-web</artifactId>
</dependency>
``` <!--config client-->
<dependency>
#### 4.2 新建 `bootstrap.yml`配置文件,指定注册中心地址和配置中心服务名,并在启动类上开启自动注册@EnableDiscoveryClient <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
这里需要特别说明的是在之前的所有项目中我们采用的配置文件都是application.yml,但是这里**一定要采用bootstrap.yml**。 </dependency>
<!--eureka-client-->
假设我们的数据库配置是放在远程配置中心的那么我们应该先去远程配置中心拉取配置然后再去进行数据库的自动化配置反之如果我们先进行了数据库的自动化配置那么就会因为找不到url或驱动而抛出异常。 <dependency>
<groupId>org.springframework.cloud</groupId>
- bootstrap.ymlbootstrap.properties用来程序引导时执行应用于更加早期配置信息读取bootstrap.yml 先于 application.yml 加载。 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- application.ymlapplication.properties) 应用程序各个模块的配置信息。 </dependencies>
```yaml <build>
server: <plugins>
port: 8030 <plugin>
spring: <groupId>org.springframework.boot</groupId>
application: <artifactId>spring-boot-maven-plugin</artifactId>
name: config-client </plugin>
cloud: </plugins>
config: </build>
discovery:
enabled: true </project>
# 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用的
serviceId: config-server ```
# 指定分支
label: master #### 4.2 新建 `bootstrap.yml`配置文件,指定注册中心地址和配置中心服务名,并在启动类上开启自动注册@EnableDiscoveryClient
# 指定环境
profile: dev 这里需要特别说明的是在之前的所有项目中我们采用的配置文件都是application.yml,但是这里**一定要采用bootstrap.yml**。
假设我们的数据库配置是放在远程配置中心的那么我们应该先去远程配置中心拉取配置然后再去进行数据库的自动化配置反之如果我们先进行了数据库的自动化配置那么就会因为找不到url或驱动而抛出异常。
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
# 因为我们必须要先从注册中心去获取可用的配置中心, 从配置中心去拉取配置 - bootstrap.ymlbootstrap.properties用来程序引导时执行应用于更加早期配置信息读取bootstrap.yml 先于 application.yml 加载。
eureka:
client: - application.ymlapplication.properties) 应用程序各个模块的配置信息。
serviceUrl:
defaultZone: http://localhost:8010/eureka/ ```yaml
server:
``` port: 8030
spring:
```java application:
@SpringBootApplication name: config-client
@EnableDiscoveryClient cloud:
public class ConfigClientApplication { config:
discovery:
public static void main(String[] args) { enabled: true
SpringApplication.run(ConfigClientApplication.class, args); # 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用的
} serviceId: config-server
# 指定分支
} label: master
# 指定环境
``` profile: dev
#### 4.3 创建配置映射类用于测试
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
```java # 因为我们必须要先从注册中心去获取可用的配置中心, 从配置中心去拉取配置
@Component eureka:
@ConfigurationProperties(prefix = "programmer") client:
@Data serviceUrl:
@ToString defaultZone: http://localhost:8010/eureka/
public class Programmer{
```
private String name;
private int age; ```java
private boolean married; @SpringBootApplication
private Date hireDate; @EnableDiscoveryClient
private float salary; public class ConfigClientApplication {
private int random;
private Map<String, String> skill; public static void main(String[] args) {
private List company; SpringApplication.run(ConfigClientApplication.class, args);
private School school; }
} }
```
```
```java
@RestController #### 4.3 创建配置映射类用于测试
public class ConfigController {
```java
@Autowired @Component
private Programmer programmer; @ConfigurationProperties(prefix = "programmer")
@Data
@RequestMapping("programmer") @ToString
public String getProgrammer() { public class Programmer{
return programmer.toString();
} private String name;
} private int age;
``` private boolean married;
private Date hireDate;
#### 4.4 依次启动eureka,config-server,config-client ,访问 http://localhost:8030/programmer private float salary;
private int random;
这里需要注意是在启动eureka和config-server要稍等一会在启动config-client这里是为了确保config-server已经将服务注册到eureka然后我们的config-client才能从eureka中获取配置中心的服务。 private Map<String, String> skill;
private List company;
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-client-programmer.png"/> </div> private School school;
启动的时候可以从控制台看到如下拉取服务的信息: }
```
```shell
Fetching config from server at : http://localhost:8020/ ```java
Located environment: name=config-client, profiles=[dev], label=master, version=50dcfb85cd751e4f28761cd6bad84c1f73034002, state=null @RestController
``` public class ConfigController {
@Autowired
private Programmer programmer;
## 五、集成 spring-cloud-bus 实现配置热更新
@RequestMapping("programmer")
#### 5.1 消息总线简介 public String getProgrammer() {
return programmer.toString();
在微服务的架构中我们通常想要构建一个共同的消息主题被所有微服务实例所监听以便对所有微服务实例的管理和通知这就是消息总线spring cloud bus 就是消息总线的一种实现。 }
}
目前spring cloud bus 支持的消息中间件有 RabbitMQ和kafka, 我们下面的整合采用的是RrabbitMQ。 ```
关于热更新只需要对配置客户端config-client做更改不需要对config-server做改动。 #### 4.4 依次启动eureka,config-server,config-client ,访问 http://localhost:8030/programmer
#### 5.1 导入bus依赖 这里需要注意是在启动eureka和config-server要稍等一会在启动config-client这里是为了确保config-server已经将服务注册到eureka然后我们的config-client才能从eureka中获取配置中心的服务。
```xml <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-client-programmer.png"/> </div>
<dependency>
<groupId>org.springframework.cloud</groupId> 启动的时候可以从控制台看到如下拉取服务的信息:
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency> ```shell
<!--因为要用到端点功能(主要是刷新端点),所以需要导入actuator--> Fetching config from server at : http://localhost:8020/
<dependency> Located environment: name=config-client, profiles=[dev], label=master, version=50dcfb85cd751e4f28761cd6bad84c1f73034002, state=null
<groupId>org.springframework.boot</groupId> ```
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
```
## 五、集成 spring-cloud-bus 实现配置热更新
#### 5.2 修改bootstrap.yml 配置开启总线配置配置rabbitmq 和 开启热刷新[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)
#### 5.1 消息总线简介
```yml
server: 在微服务的架构中我们通常想要构建一个共同的消息主题被所有微服务实例所监听以便对所有微服务实例的管理和通知这就是消息总线spring cloud bus 就是消息总线的一种实现。
port: 8030
spring: 目前spring cloud bus 支持的消息中间件有 RabbitMQ和kafka, 我们下面的整合采用的是RrabbitMQ。
application:
name: config-client 关于热更新只需要对配置客户端config-client做更改不需要对config-server做改动。
cloud:
config: #### 5.1 导入bus依赖
discovery:
enabled: true ```xml
# 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用 <dependency>
serviceId: config-server <groupId>org.springframework.cloud</groupId>
# 指定分支 <artifactId>spring-cloud-starter-bus-amqp</artifactId>
label: master </dependency>
# 指定环境 <!--因为要用到端点功能(主要是刷新端点),所以需要导入actuator-->
profile: dev <dependency>
bus: <groupId>org.springframework.boot</groupId>
#开启总线 <artifactId>spring-boot-starter-actuator</artifactId>
enabled: true </dependency>
# 打开ack跟踪的标志默认关闭 ```
trace:
enabled: true #### 5.2 修改bootstrap.yml 配置开启总线配置配置rabbitmq 和 开启热刷新[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)
# 使用bus实现热更新
rabbitmq: ```yml
host: 127.0.0.1 server:
port: 5672 port: 8030
username: guest spring:
password: guest application:
name: config-client
cloud:
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定 config:
# 因为我们必须要先从注册中心去获取可用的配置中心, 然后从配置中心去拉取配置 discovery:
eureka: enabled: true
client: # 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用
serviceUrl: serviceId: config-server
defaultZone: http://localhost:8010/eureka/ # 指定分支
label: master
# 暴露热刷新的端点 # 指定环境
management: profile: dev
endpoints: bus:
web: #开启总线
exposure: enabled: true
include: bus-refresh # 打开ack跟踪的标志默认关闭
trace:
``` enabled: true
# 使用bus实现热更新
#### 5.3 用@RefreshScope指定需要热刷新的配置 rabbitmq:
host: 127.0.0.1
```java port: 5672
@Component username: guest
@ConfigurationProperties(prefix = "programmer") password: guest
@Data
@ToString
@RefreshScope // 定义下面配置热刷新范围 # 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
public class Programmer{ # 因为我们必须要先从注册中心去获取可用的配置中心, 然后从配置中心去拉取配置
eureka:
private String name; client:
private int age; serviceUrl:
private boolean married; defaultZone: http://localhost:8010/eureka/
private Date hireDate;
private float salary; # 暴露热刷新的端点
private int random; management:
private Map<String, String> skill; endpoints:
private List company; web:
private School school; exposure:
include: bus-refresh
}
``` ```
#### 5.4 依次启动eurekaconfig-server, config-client 服务 #### 5.3 用@RefreshScope指定需要热刷新的配置
在client服务端启动时候可以在控制台 看到bus 自动创建了交换机、队列等 ```java
@Component
``` @ConfigurationProperties(prefix = "programmer")
Created new connection: rabbitConnectionFactory#496c6d94:22/SimpleConnection@185d85d2 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 63713] @Data
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. @ToString
``` @RefreshScope // 定义下面配置热刷新范围
public class Programmer{
也可以在 rabbitmq 管控台查看
private String name;
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-exchange.png"/> </div> private int age;
private boolean married;
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-queue.png"/> </div> private Date hireDate;
private float salary;
private int random;
private Map<String, String> skill;
#### 5.6 直接在 git 上修改配置文件,然后用 `post` 触发热刷新端点 http://localhost:8030/actuator/bus-refresh ,即可看到配置已经热刷新 private List company;
private School school;
注意: 这里的只能用 post 方式请求 ,可以用 postman 等测试软件
}
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/bus-refresh.png"/> </div> ```
热刷新的过程在控制台有详细的打印,部分日志如下: #### 5.4 依次启动eurekaconfig-server, config-client 服务
```shell 在client服务端启动时候可以在控制台 看到bus 自动创建了交换机、队列等
# 消息传播
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] 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.
Fetching config from server at : http://DESKTOP-8JGSFLJ:8020/ ```
# 刷新应用上下文 AnnotationConfigApplicationContext
Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@62e12f66 也可以在 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 高可用注册中心的搭建 # 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为服务注册中心负责服务的管理 eureka-server为服务注册中心负责服务的管理
@ -155,7 +172,7 @@ public class EurekaClientApplication {
这里需要主要的是仅仅status中出现其他注册中心时并不一定是搭建成功的**一定是当注册中心的DS Replicas 和 available replicas中显示其余的注册中心时候**,才代表搭建成功。 这里需要主要的是仅仅status中出现其他注册中心时并不一定是搭建成功的**一定是当注册中心的DS Replicas 和 available replicas中显示其余的注册中心时候**,才代表搭建成功。
#### **4.2.1 点击下面注册中心的可用实例列表中的地址,访问链接分以下几个情况:** #### 4.2.1 点击下面注册中心的可用实例列表中的地址,访问链接分以下几个情况:
1. hostname和prefer-ip-address都没有配置则访问 主机名:服务名:端口号, 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/> ## 正文<br/>
## 一、eureka 简介
## 一、eureka 简介
Spring Cloud Eureka使用Netflix Eureka来实现服务注册与发现它既包含了服务端组件也包含了客户端组件。
Spring Cloud Eureka使用Netflix Eureka来实现服务注册与发现它既包含了服务端组件也包含了客户端组件。
**Eureka服务端**:服务的注册中心,负责维护注册的服务列表。
**Eureka服务端**:服务的注册中心,负责维护注册的服务列表。
**Eureka客户端** 在应用程序运行时Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。同时它也能把从服务端查询到服务信息缓存到本地并周期性地刷新服务状态。
**Eureka客户端** 在应用程序运行时Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。同时它也能把从服务端查询到服务信息缓存到本地并周期性地刷新服务状态。
## 二、项目结构
## 二、项目结构
eureka-server为服务注册中心负责服务的管理
eureka-server为服务注册中心负责服务的管理
eureka-client 为eureka客户端
eureka-client 为eureka客户端
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-eureka.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-eureka.png"/> </div>
## 三、三步搭建eureka 服务注册中心
## 三、三步搭建eureka 服务注册中心
#### 3.1 引入eureka服务端依赖
#### 3.1 引入eureka服务端依赖
```xml
<dependency> ```xml
<groupId>org.springframework.cloud</groupId> <dependency>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <groupId>org.springframework.cloud</groupId>
</dependency> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
``` </dependency>
```
#### 3.2 eureka 服务端配置
#### 3.2 eureka 服务端配置
```yaml
server: ```yaml
port: 8010 server:
eureka: port: 8010
instance: eureka:
hostname: localhost instance:
client: hostname: localhost
# 设置为false,代表不向注册中心注册自己 client:
register-with-eureka: false # 设置为false,代表不向注册中心注册自己
# 注册中心主要用于维护服务,并不需要检索服务,所以设置为false register-with-eureka: false
fetch-registry: false # 注册中心主要用于维护服务,并不需要检索服务,所以设置为false
serviceUrl: fetch-registry: false
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ serviceUrl:
``` defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
```
#### 3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置
#### 3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置
```java
@SpringBootApplication ```java
@EnableEurekaServer @SpringBootApplication
public class EurekaServerApplication { @EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args); public static void main(String[] args) {
} SpringApplication.run(EurekaServerApplication.class, args);
}
}
``` }
```
## 四、三步搭建eureka 客户端
## 四、三步搭建eureka 客户端
#### 4.1 引入eureka客户端依赖
#### 4.1 引入eureka客户端依赖
```xml
<dependency> ```xml
<groupId>org.springframework.cloud</groupId> <dependency>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <groupId>org.springframework.cloud</groupId>
</dependency> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
``` </dependency>
```
#### 4.2 eureka 客户端配置
#### 4.2 eureka 客户端配置
```yaml
server: ```yaml
port: 8020 server:
# 指定服务命名 port: 8020
spring: # 指定服务命名
application: spring:
name: eureka-client application:
# 指定注册中心地址 name: eureka-client
eureka: # 指定注册中心地址
client: eureka:
serviceUrl: client:
defaultZone: http://localhost:8010/eureka/ serviceUrl:
``` defaultZone: http://localhost:8010/eureka/
```
#### 4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置
#### 4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置
```java
@SpringBootApplication ```java
@EnableDiscoveryClient @SpringBootApplication
public class EurekaClientApplication { @EnableDiscoveryClient
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args); public static void main(String[] args) {
} SpringApplication.run(EurekaClientApplication.class, args);
}
}
``` }
```
## 五、启动项目
## 五、启动项目
#### 5.1 进入注册中心控制台,查看服务注册情况
#### 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 # spring-cloud-feign
## 一、feign 简介 ## 目录<br/>
<a href="#一feign-简介">一、feign 简介</a><br/>
在上一个用例中我们使用ribbon+restTemplate 实现服务之间的远程调用实际上每一个调用都是模板化的内容所以spring cloud Feign 在此基础上进行了进一步的封装。我们只需要定义一个接口并使用feign注解的方式来进行配置同时采用springMvc 注解进行参数绑定就可以完成服务的调用。feign同时还内置实现了负载均衡、服务容错等功能。 <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/>
+ common: 公共的接口和实体类; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-指定注册中心地址并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients">4.2 指定注册中心地址,并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients</a><br/>
+ consumer: 服务的消费者采用feign调用产品服务 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-创建服务调用公共接口">4.3 创建服务调用公共接口</a><br/>
+ producer服务的提供者 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#44-继承公共接口创建CProductFeign-用@FeignClient声明为feign客户端">4.4 继承公共接口创建CProductFeign@FeignClient声明为feign客户端</a><br/>
+ eureka: 注册中心。 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#45--注入使用-feign-服务调用接口">4.5 注入使用 feign 服务调用接口</a><br/>
<a href="#五启动测试">五、启动测试</a><br/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-feign.png"/> </div> &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/>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-producer.png"/> </div> ## 正文<br/>
#### 3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。 ## 一、feign 简介
ProductService.java 在上一个用例中我们使用ribbon+restTemplate 实现服务之间的远程调用实际上每一个调用都是模板化的内容所以spring cloud Feign 在此基础上进行了进一步的封装。我们只需要定义一个接口并使用feign注解的方式来进行配置同时采用springMvc 注解进行参数绑定就可以完成服务的调用。feign同时还内置实现了负载均衡、服务容错等功能。
```java
/**
* @author : heibaiying ## 二、项目结构
* @description : 产品提供接口实现类
*/ + common: 公共的接口和实体类;
@Service + consumer: 服务的消费者采用feign调用产品服务
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> { + producer服务的提供者
+ eureka: 注册中心。
private static List<Product> productList = new ArrayList<>();
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-feign.png"/> </div>
public Product queryProductById(int id) {
return productList.stream().filter(p->p.getId()==id).collect(Collectors.toList()).get(0);
}
public List<Product> queryAllProducts() { ## 三、服务提供者的实现
return productList;
} <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-producer.png"/> </div>
@Override #### 3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。
public void saveProduct(Product product) {
productList.add(product); ProductService.java
}
```java
@Override /**
public void onApplicationEvent(WebServerInitializedEvent event) { * @author : heibaiying
int port = event.getWebServer().getPort(); * @description : 产品提供接口实现类
for (long i = 0; i < 20; i++) { */
productList.add(new Product(i, port + "产品" + i, i / 2 == 0, new Date(), 66.66f * i)); @Service
} public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {
}
} private static List<Product> productList = new ArrayList<>();
```
public Product queryProductById(int id) {
ProducerController.java return productList.stream().filter(p->p.getId()==id).collect(Collectors.toList()).get(0);
}
```java
@RestController
public class ProducerController implements ProductFeign { public List<Product> queryAllProducts() {
return productList;
@Autowired }
private IProductService productService;
@Override
@GetMapping("products") public void saveProduct(Product product) {
public List<Product> productList() { productList.add(product);
return productService.queryAllProducts(); }
}
@Override
@GetMapping("product/{id}") public void onApplicationEvent(WebServerInitializedEvent event) {
public Product productDetail(@PathVariable int id) { int port = event.getWebServer().getPort();
return productService.queryProductById(id); for (long i = 0; i < 20; i++) {
} productList.add(new Product(i, port + "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
}
@PostMapping("product") }
public void save(@RequestBody Product product) { }
productService.saveProduct(product); ```
}
} ProducerController.java
```
```java
#### 3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient @RestController
public class ProducerController implements ProductFeign {
```java
server: @Autowired
port: 8020 private IProductService productService;
# 指定服务命名
spring: @GetMapping("products")
application: public List<Product> productList() {
name: producer return productService.queryAllProducts();
# 指定注册中心地址 }
eureka:
client: @GetMapping("product/{id}")
serviceUrl: public Product productDetail(@PathVariable int id) {
defaultZone: http://localhost:8010/eureka/ return productService.queryProductById(id);
``` }
```java @PostMapping("product")
@SpringBootApplication public void save(@RequestBody Product product) {
@EnableDiscoveryClient productService.saveProduct(product);
public class ProducerApplication { }
}
public static void main(String[] args) { ```
SpringApplication.run(ProducerApplication.class, args);
} #### 3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
} ```java
server:
``` port: 8020
# 指定服务命名
spring:
application:
## 四、服务消费者的实现 name: producer
# 指定注册中心地址
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-consumer.png"/> </div> eureka:
client:
#### 4.1 导入openfeign依赖 serviceUrl:
defaultZone: http://localhost:8010/eureka/
```xml ```
<!-- feign 依赖-->
<dependency> ```java
<groupId>org.springframework.cloud</groupId> @SpringBootApplication
<artifactId>spring-cloud-starter-openfeign</artifactId> @EnableDiscoveryClient
</dependency> public class ProducerApplication {
```
public static void main(String[] args) {
#### 4.2 指定注册中心地址,并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients SpringApplication.run(ProducerApplication.class, args);
}
@EnableFeignClients 会去扫描工程中所有用 @FeignClient 声明的 feign 客户端。
}
```java
server: ```
port: 8080
# 指定服务命名
spring:
application: ## 四、服务消费者的实现
name: consumer
# 指定注册中心地址 <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-consumer.png"/> </div>
eureka:
client: #### 4.1 导入openfeign依赖
serviceUrl:
defaultZone: http://localhost:8010/eureka/ ```xml
``` <!-- feign 依赖-->
<dependency>
```java <groupId>org.springframework.cloud</groupId>
@SpringBootApplication <artifactId>spring-cloud-starter-openfeign</artifactId>
@EnableDiscoveryClient </dependency>
@EnableFeignClients ```
public class ConsumerApplication {
#### 4.2 指定注册中心地址,并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args); @EnableFeignClients 会去扫描工程中所有用 @FeignClient 声明的 feign 客户端。
}
```java
} server:
``` port: 8080
# 指定服务命名
#### 4.3 创建服务调用公共接口 spring:
application:
```java name: consumer
/** # 指定注册中心地址
* @author : heibaiying eureka:
* @description : 声明式服务调用 client:
*/ serviceUrl:
public interface ProductFeign { defaultZone: http://localhost:8010/eureka/
```
@GetMapping("products")
List<Product> productList(); ```java
@SpringBootApplication
/** @EnableDiscoveryClient
* 这是需要强调的是使用feign时候@PathVariable一定要用value指明参数 @EnableFeignClients
* 不然会抛出.IllegalStateException: PathVariable annotation was empty on param 异常 public class ConsumerApplication {
*/
@GetMapping("product/{id}") public static void main(String[] args) {
Product productDetail(@PathVariable(value = "id") int id); SpringApplication.run(ConsumerApplication.class, args);
}
@PostMapping("product") }
void save(@RequestBody Product product); ```
}
#### 4.3 创建服务调用公共接口
```
```java
按照官方对于服务最佳化的推荐,这里我们的服务调用接口放在公共模块中,因为在实际的开发中,同一个服务调用接口可能被多个模块所使用。 /**
* @author : heibaiying
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/common-feign.png"/> </div> * @description : 声明式服务调用
*/
public interface ProductFeign {
#### 4.4 继承公共接口创建CProductFeign 用@FeignClient声明为feign客户端 @GetMapping("products")
List<Product> productList();
```java
/** /**
* @author : heibaiying * 这是需要强调的是使用feign时候@PathVariable一定要用value指明参数
* @description : 声明式接口调用 * 不然会抛出.IllegalStateException: PathVariable annotation was empty on param 异常
*/ */
@FeignClient(value = "producer",configuration = FeignConfig.class) @GetMapping("product/{id}")
public interface CProductFeign extends ProductFeign { Product productDetail(@PathVariable(value = "id") int id);
}
``` @PostMapping("product")
void save(@RequestBody Product product);
#### 4.5 注入使用 feign 服务调用接口 }
```java ```
@Controller
@RequestMapping("sell") 按照官方对于服务最佳化的推荐,这里我们的服务调用接口放在公共模块中,因为在实际的开发中,同一个服务调用接口可能被多个模块所使用。
public class SellController {
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/common-feign.png"/> </div>
@Autowired
private CProductFeign cproductFeign;
@GetMapping("products") #### 4.4 继承公共接口创建CProductFeign 用@FeignClient声明为feign客户端
public String productList(Model model) {
List<Product> products = cproductFeign.productList(); ```java
model.addAttribute("products", products); /**
return "products"; * @author : heibaiying
} * @description : 声明式接口调用
*/
@GetMapping("product/{id}") @FeignClient(value = "producer",configuration = FeignConfig.class)
public String productDetail(@PathVariable int id, Model model) { public interface CProductFeign extends ProductFeign {
Product product = cproductFeign.productDetail(id);
model.addAttribute("product", product); }
return "product"; ```
}
#### 4.5 注入使用 feign 服务调用接口
@PostMapping("product") ```java
public String save(@RequestParam String productName) { @Controller
long id = Math.round(Math.random() * 100); @RequestMapping("sell")
Product product = new Product(id, productName, false, new Date(), 88); public class SellController {
cproductFeign.save(product);
return "redirect:products"; @Autowired
} private CProductFeign cproductFeign;
}
``` @GetMapping("products")
public String productList(Model model) {
List<Product> products = cproductFeign.productList();
model.addAttribute("products", products);
## 五、启动测试 return "products";
}
#### 5.1 启动一个Eureka服务、三个producer服务注意区分端口、和一个消费者服务
@GetMapping("product/{id}")
feign 的依赖中导入了spring-cloud-starter-netflix-ribbon依赖并且在内部实现了基于ribbon的客户端负载均衡所以我们这里启动三个producer实例来观察负载均衡的情况。 public String productDetail(@PathVariable int id, Model model) {
Product product = cproductFeign.productDetail(id);
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div> model.addAttribute("product", product);
return "product";
**服务注册中心:** }
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-eureka.png"/> </div>
@PostMapping("product")
#### 5.2 访问http://localhost:8080/sell/products 查看负载均衡的调用结果 public String save(@RequestParam String productName) {
long id = Math.round(Math.random() * 100);
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8020.png"/> </div> Product product = new Product(id, productName, false, new Date(), 88);
cproductFeign.save(product);
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8030.png"/> </div> return "redirect:products";
}
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-8040.png"/> </div> }
```
## 五、启动测试
## 六、 feign 的服务容错
#### 5.1 启动一个Eureka服务、三个producer服务注意区分端口、和一个消费者服务
#### 6.1 feign 的依赖中默认导入了hystrix 的相关依赖,我们不需要额外导入,只需要开启相关配置即可
feign 的依赖中导入了spring-cloud-starter-netflix-ribbon依赖并且在内部实现了基于ribbon的客户端负载均衡所以我们这里启动三个producer实例来观察负载均衡的情况。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-hystrix-maven.png"/> </div>
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div>
**服务注册中心:**
#### 6.2 在application.yml 中开启hystrix
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-eureka.png"/> </div>
```yml
feign: #### 5.2 访问http://localhost:8080/sell/products 查看负载均衡的调用结果
hystrix:
# 如果为true则OpenFign客户端将使用Hystrix断路器进行封装 默认为false <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8020.png"/> </div>
enabled: true
``` <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8030.png"/> </div>
#### 6.3 创建`CProductFeignImpl`,继承feign接口CProductFeign定义熔断时候的回退处理 <div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-8040.png"/> </div>
```java
/**
* @author : heibaiying
* @description : 定义发生熔断时候的回退处理。除了继承自CProductFeign,还需要用@Component声明为spring的组件
*/ ## 六、 feign 的服务容错
@Component
public class CProductFeignImpl implements CProductFeign { #### 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>
@Override
public List<Product> productList() {
return new ArrayList<>();
} #### 6.2 在application.yml 中开启hystrix
@Override ```yml
public Product productDetail(int id) { feign:
return null; hystrix:
} # 如果为true则OpenFign客户端将使用Hystrix断路器进行封装 默认为false
enabled: true
@Override ```
public void save(Product product) {
#### 6.3 创建`CProductFeignImpl`,继承feign接口CProductFeign定义熔断时候的回退处理
}
} ```java
``` /**
* @author : heibaiying
页面的简单容错处理: * @description : 定义发生熔断时候的回退处理。除了继承自CProductFeign,还需要用@Component声明为spring的组件
*/
```html @Component
<!doctype html> public class CProductFeignImpl implements CProductFeign {
<html lang="en">
<head> // 发生熔断时候,返回空集合,前端页面会做容错显示
<title>产品列表</title> @Override
</head> public List<Product> productList() {
<body> return new ArrayList<>();
<h3>产品列表:点击查看详情</h3> }
<form action="/sell/product" method="post">
<input type="text" name="productName"> @Override
<input type="submit" value="新增产品"> public Product productDetail(int id) {
</form> return null;
<ul> }
<#if (products?size>0) >
<#list products as product> @Override
<li> public void save(Product product) {
<a href="/sell/product/${product.id}">${product.name}</a>
</li> }
</#list> }
<#else> ```
<h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
</#if> 页面的简单容错处理:
</ul>
</body> ```html
</html> <!doctype html>
``` <html lang="en">
<head>
#### 6.4 在 @FeignClient 注解中用fallback参数指定熔断时候的回退处理 <title>产品列表</title>
</head>
```java <body>
/** <h3>产品列表:点击查看详情</h3>
* @author : heibaiying <form action="/sell/product" method="post">
* @description : 声明式接口调用 <input type="text" name="productName">
*/ <input type="submit" value="新增产品">
@FeignClient(value = "producer",configuration = FeignConfig.class,fallback = CProductFeignImpl.class) </form>
public interface CProductFeign extends ProductFeign { <ul>
<#if (products?size>0) >
} <#list products as product>
``` <li>
<a href="/sell/product/${product.id}">${product.name}</a>
#### 6.5 测试熔断处理 </li>
</#list>
hystrix 默认调用超时时间为2s ,这里我们使用线程休眠的方式来模拟超时熔断。 <#else>
<h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
```java </#if>
public List<Product> queryAllProducts() { </ul>
/*用于测试 hystrix 超时熔断 </body>
try { </html>
int i = new Random().nextInt(2500); ```
Thread.sleep(i);
} catch (InterruptedException e) { #### 6.4 在 @FeignClient 注解中用fallback参数指定熔断时候的回退处理
e.printStackTrace();
}*/ ```java
return productList; /**
} * @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;
}
```
测试结果:

View File

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

View File

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

View File

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

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="#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="#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="#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/> ## 正文<br/>
@ -166,7 +166,7 @@ public class OracleDaoImpl implements OracleDao {
} }
``` ```
#### 4.新建测试类进行测试 #### 4新建测试类进行测试
```java ```java
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)