增加README.md文章导航
This commit is contained in:
		@@ -1,314 +1,332 @@
 | 
			
		||||
# spring boot actuator
 | 
			
		||||
 | 
			
		||||
## 一、用例涉及到的概念综述
 | 
			
		||||
 | 
			
		||||
### 1.1 端点
 | 
			
		||||
 | 
			
		||||
执行器端点(endpoints)可用于监控应用及与应用进行交互,Spring  Boot包含很多内置的端点,你也可以添加自己的。例如,health端点提供了应用的基本健康信息。  端点暴露的方式取决于你采用的技术类型,大部分应用选择HTTP监控,端点的ID映射到一个URL。例如,health端点默认映射到/health。
 | 
			
		||||
 | 
			
		||||
下面的端点都是可用的:
 | 
			
		||||
 | 
			
		||||
| ID          | 描述                                                         | 是否敏感 |
 | 
			
		||||
| ----------- | ------------------------------------------------------------ | -------- |
 | 
			
		||||
| actuator    | 为其他端点提供基于超文本的导航页面,需要添加Spring HATEOAS依赖 | true     |
 | 
			
		||||
| autoconfig  | 显示一个自动配置类的报告,该报告展示所有自动配置候选者及它们被应用或未被应用的原因 | true     |
 | 
			
		||||
| beans       | 显示一个应用中所有Spring Beans的完整列表                     | true     |
 | 
			
		||||
| configprops | 显示一个所有@ConfigurationProperties的集合列表               | true     |
 | 
			
		||||
| dump        | 执行一个线程转储                                             | true     |
 | 
			
		||||
| env         | 暴露来自Spring ConfigurableEnvironment的属性                 | true     |
 | 
			
		||||
| flyway      | 显示数据库迁移路径,如果有的话                               | true     |
 | 
			
		||||
| health      | 展示应用的健康信息(当使用一个未认证连接访问时显示一个简单的'status',使用认证连接访问则显示全部信息详情) | false    |
 | 
			
		||||
| info        | 显示任意的应用信息                                           | false    |
 | 
			
		||||
| liquibase   | 展示任何Liquibase数据库迁移路径,如果有的话                  | true     |
 | 
			
		||||
| metrics     | 展示当前应用的'metrics'信息                                  | true     |
 | 
			
		||||
| mappings    | 显示一个所有@RequestMapping路径的集合列表                    | true     |
 | 
			
		||||
| shutdown    | 允许应用以优雅的方式关闭(默认情况下不启用)                 | true     |
 | 
			
		||||
| trace       | 显示trace信息(默认为最新的100条HTTP请求)                   | true     |
 | 
			
		||||
 | 
			
		||||
如果使用Spring MVC,你还可以使用以下端点:
 | 
			
		||||
 | 
			
		||||
| ID       | 描述                                                         | 是否敏感 |
 | 
			
		||||
| -------- | ------------------------------------------------------------ | -------- |
 | 
			
		||||
| docs     | 展示Actuator的文档,包括示例请求和响应,需添加spring-boot-actuator-docs依赖 | false    |
 | 
			
		||||
| heapdump | 返回一个GZip压缩的hprof堆转储文件                            | true     |
 | 
			
		||||
| jolokia  | 通过HTTP暴露JMX beans(依赖Jolokia)                         | true     |
 | 
			
		||||
| logfile  | 返回日志文件内容(如果设置logging.file或logging.path属性),支持使用HTTP Range头接收日志文件内容的部分信息 |          |
 | 
			
		||||
 | 
			
		||||
注:根据端点暴露的方式,sensitive属性可用做安全提示,例如,在使用HTTP访问敏感(sensitive)端点时需要提供用户名/密码(如果没有启用web安全,可能会简化为禁止访问该端点)。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.2 启用端点
 | 
			
		||||
 | 
			
		||||
默认情况下,除了以外的所有端点shutdown都已启用。要配置端点的启用,请使用其management.endpoint.<id>.enabled属性。以下示例启用shutdown端点:
 | 
			
		||||
 | 
			
		||||
```properties
 | 
			
		||||
management.endpoint.shutdown.enabled = true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.3 暴露端点
 | 
			
		||||
 | 
			
		||||
由于端点可能包含敏感信息,因此应仔细考虑何时公开它们。下表显示了内置端点的默认曝光情况:
 | 
			
		||||
 | 
			
		||||
| ID             | JMX   | Web  |
 | 
			
		||||
| -------------- | ----- | ---- |
 | 
			
		||||
| auditevents    | 是    | 没有 |
 | 
			
		||||
| beans          | 是    | 没有 |
 | 
			
		||||
| conditions     | 是    | 没有 |
 | 
			
		||||
| configprops    | 是    | 没有 |
 | 
			
		||||
| env            | 是    | 没有 |
 | 
			
		||||
| flyway         | 是    | 没有 |
 | 
			
		||||
| health         | 是    | 是   |
 | 
			
		||||
| heapdump       | N / A | 没有 |
 | 
			
		||||
| httptrace      | 是    | 没有 |
 | 
			
		||||
| info           | 是    | 是   |
 | 
			
		||||
| jolokia        | N / A | 没有 |
 | 
			
		||||
| logfile        | N / A | 没有 |
 | 
			
		||||
| loggers        | 是    | 没有 |
 | 
			
		||||
| liquibase      | 是    | 没有 |
 | 
			
		||||
| metrics        | 是    | 没有 |
 | 
			
		||||
| mappings       | 是    | 没有 |
 | 
			
		||||
| prometheus     | N / A | 没有 |
 | 
			
		||||
| scheduledtasks | 是    | 没有 |
 | 
			
		||||
| sessions       | 是    | 没有 |
 | 
			
		||||
| shutdown       | 是    | 没有 |
 | 
			
		||||
| threaddump     | 是    | 没有 |
 | 
			
		||||
 | 
			
		||||
**可以选择是否暴露端点(include)或者排除端点(exclude),其中排除优先于暴露:**
 | 
			
		||||
 | 
			
		||||
| 属性                                      | 默认         |
 | 
			
		||||
| ----------------------------------------- | ------------ |
 | 
			
		||||
| management.endpoints.jmx.exposure.exclude |              |
 | 
			
		||||
| management.endpoints.jmx.exposure.include | *            |
 | 
			
		||||
| management.endpoints.web.exposure.exclude |              |
 | 
			
		||||
| management.endpoints.web.exposure.include | info, health |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.4 健康检查信息
 | 
			
		||||
 | 
			
		||||
您可以使用健康信息来检查正在运行的应用程序的状态。health端点公开的信息取决于management.endpoint.health.show-details可以使用以下值之一配置的属性:
 | 
			
		||||
 | 
			
		||||
| 名称            | 描述                                                         |
 | 
			
		||||
| --------------- | ------------------------------------------------------------ |
 | 
			
		||||
| never           | 细节永远不会显示。                                           |
 | 
			
		||||
| when-authorized | 详细信息仅向授权用户显示。授权角色可以使用配置 management.endpoint.health.roles。 |
 | 
			
		||||
| always          | 详细信息显示给所有用户。                                     |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
1. CustomHealthIndicator 自定义健康指标;
 | 
			
		||||
2. CustomHealthAggregator:自定义健康聚合规则;
 | 
			
		||||
3. CustomEndPoint:自定义端点。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-actuator.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-actuator</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 1.3 项目配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
management:
 | 
			
		||||
  endpoints:
 | 
			
		||||
    web:
 | 
			
		||||
      exposure:
 | 
			
		||||
        # 这里用* 代表暴露所有端点只是为了观察效果,实际中按照需进行端点暴露
 | 
			
		||||
        include: "*"
 | 
			
		||||
  endpoint:
 | 
			
		||||
    health:
 | 
			
		||||
      # 详细信息显示给所有用户。
 | 
			
		||||
      show-details: always
 | 
			
		||||
  health:
 | 
			
		||||
    status:
 | 
			
		||||
      http-mapping:
 | 
			
		||||
        # 自定义健康检查返回状态码对应的http状态码
 | 
			
		||||
        FATAL:  503
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 1.4 查看监控状态
 | 
			
		||||
 | 
			
		||||
导入actuator 的 start 并进行配置后,访问http://127.0.0.1:8080/actuator/health 就可以看到对应的项目监控状态。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health.png"/> </div>
 | 
			
		||||
 | 
			
		||||
需要注意的是这里的监控状态根据实际项目所用到的技术不同而不同。因为以下HealthIndicators情况在适当时由Spring Boot自动配置的:
 | 
			
		||||
 | 
			
		||||
| 名称                                                         | 描述                             |
 | 
			
		||||
| ------------------------------------------------------------ | -------------------------------- |
 | 
			
		||||
| [CassandraHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraHealthIndicator.java) | 检查Cassandra数据库是否启动。    |
 | 
			
		||||
| [DiskSpaceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java) | 检查磁盘空间不足。               |
 | 
			
		||||
| [DataSourceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java) | 检查是否可以获得连接DataSource。 |
 | 
			
		||||
| [ElasticsearchHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.java) | 检查Elasticsearch集群是否启动。  |
 | 
			
		||||
| [InfluxDbHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java) | 检查InfluxDB服务器是否启动。     |
 | 
			
		||||
| [JmsHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jms/JmsHealthIndicator.java) | 检查JMS代理是否启动。            |
 | 
			
		||||
| [MailHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java) | 检查邮件服务器是否启动。         |
 | 
			
		||||
| [MongoHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mongo/MongoHealthIndicator.java) | 检查Mongo数据库是否启动。        |
 | 
			
		||||
| [Neo4jHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java) | 检查Neo4j服务器是否启动。        |
 | 
			
		||||
| [RabbitHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/amqp/RabbitHealthIndicator.java) | 检查Rabbit服务器是否启动。       |
 | 
			
		||||
| [RedisHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisHealthIndicator.java) | 检查Redis服务器是否启动。        |
 | 
			
		||||
| [SolrHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java) | 检查Solr服务器是否已启动。       |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、自定义健康检查指标
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义健康检查指标
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomHealthIndicator implements HealthIndicator {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Health health() {
 | 
			
		||||
        double random = Math.random();
 | 
			
		||||
        // 这里用随机数模拟健康检查的结果
 | 
			
		||||
        if (random > 0.5) {
 | 
			
		||||
            return Health.status("FATAL").withDetail("error code", "某健康专项检查失败").build();
 | 
			
		||||
        } else {
 | 
			
		||||
            return Health.up().withDetail("success code", "自定义检查一切正常").build();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
自定义检查通过的情况下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-health-up.png"/> </div>
 | 
			
		||||
 | 
			
		||||
自定义检查失败的情况:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health-fatal-200.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
这里我们可以看到自定义检查不论是否通过都不会影响整体的status,两种情况下都是status都是“up”。如果我们想通过自定义的检查检查去影响最终的检查结果,比如我们健康检查针对的是支付业务,在支付业务的不可用的情况下,我们就认为整个服务是不可用的。这个时候就需要实现自定义实现健康状态的聚合。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、自定义健康状态聚合规则
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 对所有的自定义健康指标进行聚合,按照自定义规则返回总和健康状态
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomHealthAggregator implements HealthAggregator {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Health aggregate(Map<String, Health> healths) {
 | 
			
		||||
        for (Health health : healths.values()) {
 | 
			
		||||
            // 聚合规则可以自定义,这里假设我们自定义的监控状态中有一项FATAL,就认为整个服务都是不可用的,否则认为整个服务是可用的
 | 
			
		||||
            if (health.getStatus().getCode().equals("FATAL")) {
 | 
			
		||||
                return Health.status("FATAL").withDetail("error code", "综合判断后服务宕机").build();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return Health.up().build();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
当我们自定义健康检查不通过时候的结果如下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-heath-503.png"/> </div>
 | 
			
		||||
 | 
			
		||||
这里需要注意的是返回我们自定义的聚合状态的时候,状态码也变成了503,这是我们在配置文件中进行定义的:
 | 
			
		||||
 | 
			
		||||
```properties
 | 
			
		||||
management.health.status.http-mapping.FATAL = 503
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
下表显示了内置状态的默认状态映射:
 | 
			
		||||
 | 
			
		||||
| Status         | Mapping                                      |
 | 
			
		||||
| -------------- | -------------------------------------------- |
 | 
			
		||||
| DOWN           | SERVICE_UNAVAILABLE (503)                    |
 | 
			
		||||
| OUT_OF_SERVICE | SERVICE_UNAVAILABLE (503)                    |
 | 
			
		||||
| UP             | No mapping by default, so http status is 200 |
 | 
			
		||||
| UNKNOWN        | No mapping by default, so http status is 200 |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、@Endpoint自定义端点
 | 
			
		||||
 | 
			
		||||
#### 5.1 自定义端点
 | 
			
		||||
 | 
			
		||||
spring boot 支持使用@Endpoint来自定义端点暴露应用信息。这里我们采用第三方sigar来暴露服务所在硬件的监控信息。
 | 
			
		||||
 | 
			
		||||
Sigar是Hyperic-hq产品的基础包,是Hyperic HQ主要的数据收集组件。Sigar.jar的底层是用C语言编写的,它通过本地方法来调用操作系统API来获取系统相关数据 [jar包下载地址](https://sourceforge.net/projects/sigar/)。
 | 
			
		||||
 | 
			
		||||
Sigar为不同平台提供了不同的库文件,下载后需要将库文件放到服务所在主机的对应位置:
 | 
			
		||||
 | 
			
		||||
- Windows下配置:根据自己的操作系统版本选择sigar-amd64-winnt.dll或sigar-x86-winnt.dll拷贝到C:\Windows\System32中
 | 
			
		||||
 | 
			
		||||
- Linux下配置:将libsigar-amd64-linux.so或libsigar-x86-linux.so拷贝到/usr/lib64或/lib64或/lib或/usr/lib目录下,如果不起作用,还需要sudochmod 744修改libsigar-amd64-linux.so文件权限
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Endpoint(id = "customEndPoint")
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomEndPoint {
 | 
			
		||||
 | 
			
		||||
    @ReadOperation
 | 
			
		||||
    public Map<String, Object> getCupInfo() throws SigarException {
 | 
			
		||||
 | 
			
		||||
        Map<String, Object> cupInfoMap = new LinkedHashMap<>();
 | 
			
		||||
 | 
			
		||||
        Sigar sigar = new Sigar();
 | 
			
		||||
 | 
			
		||||
        CpuInfo infoList[] = sigar.getCpuInfoList();
 | 
			
		||||
        CpuPerc[] cpuList = sigar.getCpuPercList();
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < infoList.length; i++) {
 | 
			
		||||
            CpuInfo info = infoList[i];
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 的总量MHz", info.getMhz());                            // CPU的总量MHz
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 生产商", info.getVendor());                            // 获得CPU的生产商,如:Intel
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 类别", info.getModel());                               // 获得CPU的类别,如:Core
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 缓存数量", info.getCacheSize());                       // 缓冲存储器数量
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 用户使用率", CpuPerc.format(cpuList[i].getUser()));    // 用户使用率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 系统使用率", CpuPerc.format(cpuList[i].getSys()));     // 系统使用率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 当前等待率", CpuPerc.format(cpuList[i].getWait()));    // 当前等待率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 当前错误率", CpuPerc.format(cpuList[i].getNice()));    // 当前错误率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 当前空闲率", CpuPerc.format(cpuList[i].getIdle()));    // 当前空闲率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 总的使用率", CpuPerc.format(cpuList[i].getCombined()));// 总的使用率
 | 
			
		||||
        }
 | 
			
		||||
        return cupInfoMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
其中可用的方法注解由http操作决定:
 | 
			
		||||
 | 
			
		||||
| operation        | HTTP方法 |
 | 
			
		||||
| ---------------- | -------- |
 | 
			
		||||
| @ReadOperation   | GET      |
 | 
			
		||||
| @WriteOperation  | POST     |
 | 
			
		||||
| @DeleteOperation | DELETE   |
 | 
			
		||||
 | 
			
		||||
#### 5.2 访问自定义端点http://127.0.0.1:8080/actuator/customEndPoint
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-customEndPoint.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
关于 Sigar 的 更多监控参数可以参考博客:[java读取计算机CPU、内存等信息(Sigar使用)](https://blog.csdn.net/wudiazu/article/details/73829324)
 | 
			
		||||
 | 
			
		||||
Sigar 下载包中也提供了各种参数的参考用例:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/sigar.png"/> </div>
 | 
			
		||||
# spring boot actuator
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一用例涉及到的概念综述">一、用例涉及到的概念综述</a><br/>
 | 
			
		||||
    <a href="#11-端点">1.1 端点</a><br/>
 | 
			
		||||
    <a href="#12-启用端点">1.2 启用端点</a><br/>
 | 
			
		||||
    <a href="#13-暴露端点">1.3 暴露端点</a><br/>
 | 
			
		||||
    <a href="#14-健康检查信息">1.4 健康检查信息</a><br/>
 | 
			
		||||
<a href="#二项目说明">二、项目说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
 | 
			
		||||
        <a href="#12-主要依赖">1.2 主要依赖</a><br/>
 | 
			
		||||
        <a href="#13-项目配置">1.3 项目配置</a><br/>
 | 
			
		||||
        <a href="#14-查看监控状态">1.4 查看监控状态</a><br/>
 | 
			
		||||
<a href="#三自定义健康检查指标">三、自定义健康检查指标</a><br/>
 | 
			
		||||
<a href="#四自定义健康状态聚合规则">四、自定义健康状态聚合规则</a><br/>
 | 
			
		||||
<a href="#五@Endpoint自定义端点">五、@Endpoint自定义端点</a><br/>
 | 
			
		||||
        <a href="#51-自定义端点">5.1 自定义端点</a><br/>
 | 
			
		||||
        <a href="#52-访问自定义端点http//1270018080/actuator/customEndPoint">5.2 访问自定义端点http://127.0.0.1:8080/actuator/customEndPoint</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、用例涉及到的概念综述
 | 
			
		||||
 | 
			
		||||
### 1.1 端点
 | 
			
		||||
 | 
			
		||||
执行器端点(endpoints)可用于监控应用及与应用进行交互,Spring  Boot包含很多内置的端点,你也可以添加自己的。例如,health端点提供了应用的基本健康信息。  端点暴露的方式取决于你采用的技术类型,大部分应用选择HTTP监控,端点的ID映射到一个URL。例如,health端点默认映射到/health。
 | 
			
		||||
 | 
			
		||||
下面的端点都是可用的:
 | 
			
		||||
 | 
			
		||||
| ID          | 描述                                                         | 是否敏感 |
 | 
			
		||||
| ----------- | ------------------------------------------------------------ | -------- |
 | 
			
		||||
| actuator    | 为其他端点提供基于超文本的导航页面,需要添加Spring HATEOAS依赖 | true     |
 | 
			
		||||
| autoconfig  | 显示一个自动配置类的报告,该报告展示所有自动配置候选者及它们被应用或未被应用的原因 | true     |
 | 
			
		||||
| beans       | 显示一个应用中所有Spring Beans的完整列表                     | true     |
 | 
			
		||||
| configprops | 显示一个所有@ConfigurationProperties的集合列表               | true     |
 | 
			
		||||
| dump        | 执行一个线程转储                                             | true     |
 | 
			
		||||
| env         | 暴露来自Spring ConfigurableEnvironment的属性                 | true     |
 | 
			
		||||
| flyway      | 显示数据库迁移路径,如果有的话                               | true     |
 | 
			
		||||
| health      | 展示应用的健康信息(当使用一个未认证连接访问时显示一个简单的'status',使用认证连接访问则显示全部信息详情) | false    |
 | 
			
		||||
| info        | 显示任意的应用信息                                           | false    |
 | 
			
		||||
| liquibase   | 展示任何Liquibase数据库迁移路径,如果有的话                  | true     |
 | 
			
		||||
| metrics     | 展示当前应用的'metrics'信息                                  | true     |
 | 
			
		||||
| mappings    | 显示一个所有@RequestMapping路径的集合列表                    | true     |
 | 
			
		||||
| shutdown    | 允许应用以优雅的方式关闭(默认情况下不启用)                 | true     |
 | 
			
		||||
| trace       | 显示trace信息(默认为最新的100条HTTP请求)                   | true     |
 | 
			
		||||
 | 
			
		||||
如果使用Spring MVC,你还可以使用以下端点:
 | 
			
		||||
 | 
			
		||||
| ID       | 描述                                                         | 是否敏感 |
 | 
			
		||||
| -------- | ------------------------------------------------------------ | -------- |
 | 
			
		||||
| docs     | 展示Actuator的文档,包括示例请求和响应,需添加spring-boot-actuator-docs依赖 | false    |
 | 
			
		||||
| heapdump | 返回一个GZip压缩的hprof堆转储文件                            | true     |
 | 
			
		||||
| jolokia  | 通过HTTP暴露JMX beans(依赖Jolokia)                         | true     |
 | 
			
		||||
| logfile  | 返回日志文件内容(如果设置logging.file或logging.path属性),支持使用HTTP Range头接收日志文件内容的部分信息 |          |
 | 
			
		||||
 | 
			
		||||
注:根据端点暴露的方式,sensitive属性可用做安全提示,例如,在使用HTTP访问敏感(sensitive)端点时需要提供用户名/密码(如果没有启用web安全,可能会简化为禁止访问该端点)。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.2 启用端点
 | 
			
		||||
 | 
			
		||||
默认情况下,除了以外的所有端点shutdown都已启用。要配置端点的启用,请使用其management.endpoint.<id>.enabled属性。以下示例启用shutdown端点:
 | 
			
		||||
 | 
			
		||||
```properties
 | 
			
		||||
management.endpoint.shutdown.enabled = true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.3 暴露端点
 | 
			
		||||
 | 
			
		||||
由于端点可能包含敏感信息,因此应仔细考虑何时公开它们。下表显示了内置端点的默认曝光情况:
 | 
			
		||||
 | 
			
		||||
| ID             | JMX   | Web  |
 | 
			
		||||
| -------------- | ----- | ---- |
 | 
			
		||||
| auditevents    | 是    | 没有 |
 | 
			
		||||
| beans          | 是    | 没有 |
 | 
			
		||||
| conditions     | 是    | 没有 |
 | 
			
		||||
| configprops    | 是    | 没有 |
 | 
			
		||||
| env            | 是    | 没有 |
 | 
			
		||||
| flyway         | 是    | 没有 |
 | 
			
		||||
| health         | 是    | 是   |
 | 
			
		||||
| heapdump       | N / A | 没有 |
 | 
			
		||||
| httptrace      | 是    | 没有 |
 | 
			
		||||
| info           | 是    | 是   |
 | 
			
		||||
| jolokia        | N / A | 没有 |
 | 
			
		||||
| logfile        | N / A | 没有 |
 | 
			
		||||
| loggers        | 是    | 没有 |
 | 
			
		||||
| liquibase      | 是    | 没有 |
 | 
			
		||||
| metrics        | 是    | 没有 |
 | 
			
		||||
| mappings       | 是    | 没有 |
 | 
			
		||||
| prometheus     | N / A | 没有 |
 | 
			
		||||
| scheduledtasks | 是    | 没有 |
 | 
			
		||||
| sessions       | 是    | 没有 |
 | 
			
		||||
| shutdown       | 是    | 没有 |
 | 
			
		||||
| threaddump     | 是    | 没有 |
 | 
			
		||||
 | 
			
		||||
**可以选择是否暴露端点(include)或者排除端点(exclude),其中排除优先于暴露:**
 | 
			
		||||
 | 
			
		||||
| 属性                                      | 默认         |
 | 
			
		||||
| ----------------------------------------- | ------------ |
 | 
			
		||||
| management.endpoints.jmx.exposure.exclude |              |
 | 
			
		||||
| management.endpoints.jmx.exposure.include | *            |
 | 
			
		||||
| management.endpoints.web.exposure.exclude |              |
 | 
			
		||||
| management.endpoints.web.exposure.include | info, health |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.4 健康检查信息
 | 
			
		||||
 | 
			
		||||
您可以使用健康信息来检查正在运行的应用程序的状态。health端点公开的信息取决于management.endpoint.health.show-details可以使用以下值之一配置的属性:
 | 
			
		||||
 | 
			
		||||
| 名称            | 描述                                                         |
 | 
			
		||||
| --------------- | ------------------------------------------------------------ |
 | 
			
		||||
| never           | 细节永远不会显示。                                           |
 | 
			
		||||
| when-authorized | 详细信息仅向授权用户显示。授权角色可以使用配置 management.endpoint.health.roles。 |
 | 
			
		||||
| always          | 详细信息显示给所有用户。                                     |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
1. CustomHealthIndicator 自定义健康指标;
 | 
			
		||||
2. CustomHealthAggregator:自定义健康聚合规则;
 | 
			
		||||
3. CustomEndPoint:自定义端点。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-actuator.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-actuator</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 1.3 项目配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
management:
 | 
			
		||||
  endpoints:
 | 
			
		||||
    web:
 | 
			
		||||
      exposure:
 | 
			
		||||
        # 这里用* 代表暴露所有端点只是为了观察效果,实际中按照需进行端点暴露
 | 
			
		||||
        include: "*"
 | 
			
		||||
  endpoint:
 | 
			
		||||
    health:
 | 
			
		||||
      # 详细信息显示给所有用户。
 | 
			
		||||
      show-details: always
 | 
			
		||||
  health:
 | 
			
		||||
    status:
 | 
			
		||||
      http-mapping:
 | 
			
		||||
        # 自定义健康检查返回状态码对应的http状态码
 | 
			
		||||
        FATAL:  503
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 1.4 查看监控状态
 | 
			
		||||
 | 
			
		||||
导入actuator 的 start 并进行配置后,访问http://127.0.0.1:8080/actuator/health 就可以看到对应的项目监控状态。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health.png"/> </div>
 | 
			
		||||
 | 
			
		||||
需要注意的是这里的监控状态根据实际项目所用到的技术不同而不同。因为以下HealthIndicators情况在适当时由Spring Boot自动配置的:
 | 
			
		||||
 | 
			
		||||
| 名称                                                         | 描述                             |
 | 
			
		||||
| ------------------------------------------------------------ | -------------------------------- |
 | 
			
		||||
| [CassandraHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraHealthIndicator.java) | 检查Cassandra数据库是否启动。    |
 | 
			
		||||
| [DiskSpaceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java) | 检查磁盘空间不足。               |
 | 
			
		||||
| [DataSourceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java) | 检查是否可以获得连接DataSource。 |
 | 
			
		||||
| [ElasticsearchHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.java) | 检查Elasticsearch集群是否启动。  |
 | 
			
		||||
| [InfluxDbHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java) | 检查InfluxDB服务器是否启动。     |
 | 
			
		||||
| [JmsHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jms/JmsHealthIndicator.java) | 检查JMS代理是否启动。            |
 | 
			
		||||
| [MailHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java) | 检查邮件服务器是否启动。         |
 | 
			
		||||
| [MongoHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mongo/MongoHealthIndicator.java) | 检查Mongo数据库是否启动。        |
 | 
			
		||||
| [Neo4jHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java) | 检查Neo4j服务器是否启动。        |
 | 
			
		||||
| [RabbitHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/amqp/RabbitHealthIndicator.java) | 检查Rabbit服务器是否启动。       |
 | 
			
		||||
| [RedisHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisHealthIndicator.java) | 检查Redis服务器是否启动。        |
 | 
			
		||||
| [SolrHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java) | 检查Solr服务器是否已启动。       |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、自定义健康检查指标
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义健康检查指标
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomHealthIndicator implements HealthIndicator {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Health health() {
 | 
			
		||||
        double random = Math.random();
 | 
			
		||||
        // 这里用随机数模拟健康检查的结果
 | 
			
		||||
        if (random > 0.5) {
 | 
			
		||||
            return Health.status("FATAL").withDetail("error code", "某健康专项检查失败").build();
 | 
			
		||||
        } else {
 | 
			
		||||
            return Health.up().withDetail("success code", "自定义检查一切正常").build();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
自定义检查通过的情况下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-health-up.png"/> </div>
 | 
			
		||||
 | 
			
		||||
自定义检查失败的情况:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health-fatal-200.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
这里我们可以看到自定义检查不论是否通过都不会影响整体的status,两种情况下都是status都是“up”。如果我们想通过自定义的检查检查去影响最终的检查结果,比如我们健康检查针对的是支付业务,在支付业务的不可用的情况下,我们就认为整个服务是不可用的。这个时候就需要实现自定义实现健康状态的聚合。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、自定义健康状态聚合规则
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 对所有的自定义健康指标进行聚合,按照自定义规则返回总和健康状态
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomHealthAggregator implements HealthAggregator {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Health aggregate(Map<String, Health> healths) {
 | 
			
		||||
        for (Health health : healths.values()) {
 | 
			
		||||
            // 聚合规则可以自定义,这里假设我们自定义的监控状态中有一项FATAL,就认为整个服务都是不可用的,否则认为整个服务是可用的
 | 
			
		||||
            if (health.getStatus().getCode().equals("FATAL")) {
 | 
			
		||||
                return Health.status("FATAL").withDetail("error code", "综合判断后服务宕机").build();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return Health.up().build();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
当我们自定义健康检查不通过时候的结果如下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-heath-503.png"/> </div>
 | 
			
		||||
 | 
			
		||||
这里需要注意的是返回我们自定义的聚合状态的时候,状态码也变成了503,这是我们在配置文件中进行定义的:
 | 
			
		||||
 | 
			
		||||
```properties
 | 
			
		||||
management.health.status.http-mapping.FATAL = 503
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
下表显示了内置状态的默认状态映射:
 | 
			
		||||
 | 
			
		||||
| Status         | Mapping                                      |
 | 
			
		||||
| -------------- | -------------------------------------------- |
 | 
			
		||||
| DOWN           | SERVICE_UNAVAILABLE (503)                    |
 | 
			
		||||
| OUT_OF_SERVICE | SERVICE_UNAVAILABLE (503)                    |
 | 
			
		||||
| UP             | No mapping by default, so http status is 200 |
 | 
			
		||||
| UNKNOWN        | No mapping by default, so http status is 200 |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、@Endpoint自定义端点
 | 
			
		||||
 | 
			
		||||
#### 5.1 自定义端点
 | 
			
		||||
 | 
			
		||||
spring boot 支持使用@Endpoint来自定义端点暴露应用信息。这里我们采用第三方sigar来暴露服务所在硬件的监控信息。
 | 
			
		||||
 | 
			
		||||
Sigar是Hyperic-hq产品的基础包,是Hyperic HQ主要的数据收集组件。Sigar.jar的底层是用C语言编写的,它通过本地方法来调用操作系统API来获取系统相关数据 [jar包下载地址](https://sourceforge.net/projects/sigar/)。
 | 
			
		||||
 | 
			
		||||
Sigar为不同平台提供了不同的库文件,下载后需要将库文件放到服务所在主机的对应位置:
 | 
			
		||||
 | 
			
		||||
- Windows下配置:根据自己的操作系统版本选择sigar-amd64-winnt.dll或sigar-x86-winnt.dll拷贝到C:\Windows\System32中
 | 
			
		||||
 | 
			
		||||
- Linux下配置:将libsigar-amd64-linux.so或libsigar-x86-linux.so拷贝到/usr/lib64或/lib64或/lib或/usr/lib目录下,如果不起作用,还需要sudochmod 744修改libsigar-amd64-linux.so文件权限
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Endpoint(id = "customEndPoint")
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomEndPoint {
 | 
			
		||||
 | 
			
		||||
    @ReadOperation
 | 
			
		||||
    public Map<String, Object> getCupInfo() throws SigarException {
 | 
			
		||||
 | 
			
		||||
        Map<String, Object> cupInfoMap = new LinkedHashMap<>();
 | 
			
		||||
 | 
			
		||||
        Sigar sigar = new Sigar();
 | 
			
		||||
 | 
			
		||||
        CpuInfo infoList[] = sigar.getCpuInfoList();
 | 
			
		||||
        CpuPerc[] cpuList = sigar.getCpuPercList();
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < infoList.length; i++) {
 | 
			
		||||
            CpuInfo info = infoList[i];
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 的总量MHz", info.getMhz());                            // CPU的总量MHz
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 生产商", info.getVendor());                            // 获得CPU的生产商,如:Intel
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 类别", info.getModel());                               // 获得CPU的类别,如:Core
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 缓存数量", info.getCacheSize());                       // 缓冲存储器数量
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 用户使用率", CpuPerc.format(cpuList[i].getUser()));    // 用户使用率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 系统使用率", CpuPerc.format(cpuList[i].getSys()));     // 系统使用率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 当前等待率", CpuPerc.format(cpuList[i].getWait()));    // 当前等待率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 当前错误率", CpuPerc.format(cpuList[i].getNice()));    // 当前错误率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 当前空闲率", CpuPerc.format(cpuList[i].getIdle()));    // 当前空闲率
 | 
			
		||||
            cupInfoMap.put("CPU " + i + " 总的使用率", CpuPerc.format(cpuList[i].getCombined()));// 总的使用率
 | 
			
		||||
        }
 | 
			
		||||
        return cupInfoMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
其中可用的方法注解由http操作决定:
 | 
			
		||||
 | 
			
		||||
| operation        | HTTP方法 |
 | 
			
		||||
| ---------------- | -------- |
 | 
			
		||||
| @ReadOperation   | GET      |
 | 
			
		||||
| @WriteOperation  | POST     |
 | 
			
		||||
| @DeleteOperation | DELETE   |
 | 
			
		||||
 | 
			
		||||
#### 5.2 访问自定义端点http://127.0.0.1:8080/actuator/customEndPoint
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-customEndPoint.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
关于 Sigar 的 更多监控参数可以参考博客:[java读取计算机CPU、内存等信息(Sigar使用)](https://blog.csdn.net/wudiazu/article/details/73829324)
 | 
			
		||||
 | 
			
		||||
Sigar 下载包中也提供了各种参数的参考用例:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,247 +1,259 @@
 | 
			
		||||
# spring-boot 基础
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
1. 本项目搭建一个简单的hello spring 的 web工程,简单说明spring-boot 的开箱即用的特性;
 | 
			
		||||
2. 模板引擎采用freemaker 和 thymeleaf 作为示例,分别对应模板文件makershow.ftl 和 leafShow.html;
 | 
			
		||||
3. spring boot 2.x 默认是不支持jsp的,需要额外的配置,关于使用jsp的整合可以参考[spring-boot-jsp](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-jsp)项目。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-base.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目依赖
 | 
			
		||||
 | 
			
		||||
导入相关的starter(启动器)
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-parent</artifactId>
 | 
			
		||||
        <version>2.1.1.RELEASE</version>
 | 
			
		||||
        <relativePath/> <!-- lookup parent from repository -->
 | 
			
		||||
    </parent>
 | 
			
		||||
    <groupId>com.heibaiying</groupId>
 | 
			
		||||
    <artifactId>spring-boot-base</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>spring-boot-base</name>
 | 
			
		||||
    <description>Demo project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <!--模板引擎-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-freemarker</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--web 启动器-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--lombok 插件-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.projectlombok</groupId>
 | 
			
		||||
            <artifactId>lombok</artifactId>
 | 
			
		||||
            <optional>true</optional>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--测试相关依赖-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <!--Spring Boot的Maven插件(Spring Boot Maven plugin)能够以Maven的方式为应用提供Spring Boot的支持-->
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
1. spring boot 项目默认继承自spring-boot-starter-parent,而spring-boot-starter-parent继承自spring-boot-dependencies,  spring-boot-dependencies中定义了关于spring boot 依赖的各种jar包的版本,是spring boot 的版本管理中心。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dependencies.png"/> </div>
 | 
			
		||||
 | 
			
		||||
2. 关于spring boot 2.x官方支持的所有starter 可以参见官方文档 [Table 13.1. Spring Boot application starters](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#using-boot-starter)
 | 
			
		||||
# spring-boot 基础
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
 | 
			
		||||
        <a href="#12-项目依赖">1.2 项目依赖</a><br/>
 | 
			
		||||
<a href="#二spring-boot-主启动类">二、spring boot 主启动类</a><br/>
 | 
			
		||||
<a href="#三开箱即用的web工程">三、开箱即用的web工程</a><br/>
 | 
			
		||||
<a href="#四模板引擎">四、模板引擎</a><br/>
 | 
			
		||||
        <a href="#41-freemarker">4.1 freemarker</a><br/>
 | 
			
		||||
        <a href="#42-thymeleaf">4.2 thymeleaf</a><br/>
 | 
			
		||||
        <a href="#43-文档说明">4.3 文档说明</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、spring boot 主启动类
 | 
			
		||||
 | 
			
		||||
 如果采用IDEA 或者 Spring Tool Suite (STS) 等开发工具创建的spring boot 工程,会默认创建启动类,如果没有创建,需要手动创建启动类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
package com.heibaiying.springbootbase;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.SpringApplication;
 | 
			
		||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
			
		||||
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
public class SpringBootBaseApplication {
 | 
			
		||||
 | 
			
		||||
    // 启动类默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(SpringBootBaseApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@SpringBootApplication 注解是一个复合注解,里面包含了@ComponentScan注解,默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Target(ElementType.TYPE)
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
@Documented
 | 
			
		||||
@Inherited
 | 
			
		||||
@SpringBootConfiguration
 | 
			
		||||
@EnableAutoConfiguration
 | 
			
		||||
@ComponentScan(excludeFilters = {
 | 
			
		||||
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
 | 
			
		||||
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
 | 
			
		||||
public @interface SpringBootApplication {
 | 
			
		||||
    ...
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、开箱即用的web工程
 | 
			
		||||
 | 
			
		||||
在springbootBaseApplication.java 的同级目录创建controller文件夹,并在其中创建RestfulController.java,启动项目访问localhost:8080/restful/programmers 即可看到项目搭建成功。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : restful 控制器
 | 
			
		||||
 */
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("restful")
 | 
			
		||||
public class RestfulController {
 | 
			
		||||
 | 
			
		||||
    @GetMapping("programmers")
 | 
			
		||||
    private List<Programmer> getProgrammers() {
 | 
			
		||||
        List<Programmer> programmers = new ArrayList<>();
 | 
			
		||||
        programmers.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
 | 
			
		||||
        programmers.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
 | 
			
		||||
        return programmers;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
这里之所以能够开箱即用,是因为我们在项目中导入spring-boot-starter-web启动器,而@SpringBootApplication 复合注解中默认开启了@EnableAutoConfiguration注解允许开启自动化配置,spring在检查导入starter-web的依赖后就会开启web的自动化配置。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、模板引擎
 | 
			
		||||
 | 
			
		||||
这里我们在一个项目中同时导入了freemaker 和 thymeleaf的starter(虽然并不推荐,但是在同一个项目中是可以混用这两种模板引擎的)。
 | 
			
		||||
 | 
			
		||||
#### 4.1 freemarker
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
 | 
			
		||||
 */
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("freemarker")
 | 
			
		||||
public class FreeMarkerController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("show")
 | 
			
		||||
    private String programmerShow(ModelMap modelMap){
 | 
			
		||||
        List<Programmer> programmerList=new ArrayList<>();
 | 
			
		||||
        programmerList.add(new Programmer("xiaoming",12,100000.00f,LocalDate.of(2019,Month.AUGUST,2)));
 | 
			
		||||
        programmerList.add(new Programmer("xiaohong",23,900000.00f,LocalDate.of(2013,Month.FEBRUARY,2)));
 | 
			
		||||
        modelMap.addAttribute("programmers",programmerList);
 | 
			
		||||
        return "markerShow";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <title>freemarker模板引擎</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <ul>
 | 
			
		||||
        <#list programmers as programmer>
 | 
			
		||||
           <li>姓名: ${programmer.name} 年龄: ${programmer.age}</li>
 | 
			
		||||
        </#list>
 | 
			
		||||
    </ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 thymeleaf
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
 | 
			
		||||
 */
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("thymeleaf")
 | 
			
		||||
public class ThymeleafController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("show")
 | 
			
		||||
    private String programmerShow(ModelMap modelMap) {
 | 
			
		||||
        List<Programmer> programmerList = new ArrayList<>();
 | 
			
		||||
        programmerList.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
 | 
			
		||||
        programmerList.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
 | 
			
		||||
        modelMap.addAttribute("programmers", programmerList);
 | 
			
		||||
        return "leafShow";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <title>thymeleaf模板引擎</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <ul th:each="programmer:${programmers}">
 | 
			
		||||
        <li>
 | 
			
		||||
            姓名:<span th:text="${programmer.name}"></span>
 | 
			
		||||
            薪水:<span th:text="${programmer.salary}"></span>
 | 
			
		||||
        </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.3 文档说明
 | 
			
		||||
 | 
			
		||||
freemarker:提供了完善的中文文档,地址 http://freemarker.foofun.cn/
 | 
			
		||||
 | 
			
		||||
thymeleaf:官方英文文档地址:[thymeleaf 3.0.11RELEASE](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
1. 本项目搭建一个简单的hello spring 的 web工程,简单说明spring-boot 的开箱即用的特性;
 | 
			
		||||
2. 模板引擎采用freemaker 和 thymeleaf 作为示例,分别对应模板文件makershow.ftl 和 leafShow.html;
 | 
			
		||||
3. spring boot 2.x 默认是不支持jsp的,需要额外的配置,关于使用jsp的整合可以参考[spring-boot-jsp](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-jsp)项目。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-base.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目依赖
 | 
			
		||||
 | 
			
		||||
导入相关的starter(启动器)
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-parent</artifactId>
 | 
			
		||||
        <version>2.1.1.RELEASE</version>
 | 
			
		||||
        <relativePath/> <!-- lookup parent from repository -->
 | 
			
		||||
    </parent>
 | 
			
		||||
    <groupId>com.heibaiying</groupId>
 | 
			
		||||
    <artifactId>spring-boot-base</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>spring-boot-base</name>
 | 
			
		||||
    <description>Demo project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <!--模板引擎-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-freemarker</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--web 启动器-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--lombok 插件-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.projectlombok</groupId>
 | 
			
		||||
            <artifactId>lombok</artifactId>
 | 
			
		||||
            <optional>true</optional>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--测试相关依赖-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <!--Spring Boot的Maven插件(Spring Boot Maven plugin)能够以Maven的方式为应用提供Spring Boot的支持-->
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
1. spring boot 项目默认继承自spring-boot-starter-parent,而spring-boot-starter-parent继承自spring-boot-dependencies,  spring-boot-dependencies中定义了关于spring boot 依赖的各种jar包的版本,是spring boot 的版本管理中心。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dependencies.png"/> </div>
 | 
			
		||||
 | 
			
		||||
2. 关于spring boot 2.x官方支持的所有starter 可以参见官方文档 [Table 13.1. Spring Boot application starters](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#using-boot-starter)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、spring boot 主启动类
 | 
			
		||||
 | 
			
		||||
 如果采用IDEA 或者 Spring Tool Suite (STS) 等开发工具创建的spring boot 工程,会默认创建启动类,如果没有创建,需要手动创建启动类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
package com.heibaiying.springbootbase;
 | 
			
		||||
 | 
			
		||||
import org.springframework.boot.SpringApplication;
 | 
			
		||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
			
		||||
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
public class SpringBootBaseApplication {
 | 
			
		||||
 | 
			
		||||
    // 启动类默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(SpringBootBaseApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@SpringBootApplication 注解是一个复合注解,里面包含了@ComponentScan注解,默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Target(ElementType.TYPE)
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
@Documented
 | 
			
		||||
@Inherited
 | 
			
		||||
@SpringBootConfiguration
 | 
			
		||||
@EnableAutoConfiguration
 | 
			
		||||
@ComponentScan(excludeFilters = {
 | 
			
		||||
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
 | 
			
		||||
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
 | 
			
		||||
public @interface SpringBootApplication {
 | 
			
		||||
    ...
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、开箱即用的web工程
 | 
			
		||||
 | 
			
		||||
在springbootBaseApplication.java 的同级目录创建controller文件夹,并在其中创建RestfulController.java,启动项目访问localhost:8080/restful/programmers 即可看到项目搭建成功。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : restful 控制器
 | 
			
		||||
 */
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("restful")
 | 
			
		||||
public class RestfulController {
 | 
			
		||||
 | 
			
		||||
    @GetMapping("programmers")
 | 
			
		||||
    private List<Programmer> getProgrammers() {
 | 
			
		||||
        List<Programmer> programmers = new ArrayList<>();
 | 
			
		||||
        programmers.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
 | 
			
		||||
        programmers.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
 | 
			
		||||
        return programmers;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
这里之所以能够开箱即用,是因为我们在项目中导入spring-boot-starter-web启动器,而@SpringBootApplication 复合注解中默认开启了@EnableAutoConfiguration注解允许开启自动化配置,spring在检查导入starter-web的依赖后就会开启web的自动化配置。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、模板引擎
 | 
			
		||||
 | 
			
		||||
这里我们在一个项目中同时导入了freemaker 和 thymeleaf的starter(虽然并不推荐,但是在同一个项目中是可以混用这两种模板引擎的)。
 | 
			
		||||
 | 
			
		||||
#### 4.1 freemarker
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
 | 
			
		||||
 */
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("freemarker")
 | 
			
		||||
public class FreeMarkerController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("show")
 | 
			
		||||
    private String programmerShow(ModelMap modelMap){
 | 
			
		||||
        List<Programmer> programmerList=new ArrayList<>();
 | 
			
		||||
        programmerList.add(new Programmer("xiaoming",12,100000.00f,LocalDate.of(2019,Month.AUGUST,2)));
 | 
			
		||||
        programmerList.add(new Programmer("xiaohong",23,900000.00f,LocalDate.of(2013,Month.FEBRUARY,2)));
 | 
			
		||||
        modelMap.addAttribute("programmers",programmerList);
 | 
			
		||||
        return "markerShow";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <title>freemarker模板引擎</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <ul>
 | 
			
		||||
        <#list programmers as programmer>
 | 
			
		||||
           <li>姓名: ${programmer.name} 年龄: ${programmer.age}</li>
 | 
			
		||||
        </#list>
 | 
			
		||||
    </ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 thymeleaf
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
 | 
			
		||||
 */
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("thymeleaf")
 | 
			
		||||
public class ThymeleafController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("show")
 | 
			
		||||
    private String programmerShow(ModelMap modelMap) {
 | 
			
		||||
        List<Programmer> programmerList = new ArrayList<>();
 | 
			
		||||
        programmerList.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
 | 
			
		||||
        programmerList.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
 | 
			
		||||
        modelMap.addAttribute("programmers", programmerList);
 | 
			
		||||
        return "leafShow";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <title>thymeleaf模板引擎</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <ul th:each="programmer:${programmers}">
 | 
			
		||||
        <li>
 | 
			
		||||
            姓名:<span th:text="${programmer.name}"></span>
 | 
			
		||||
            薪水:<span th:text="${programmer.salary}"></span>
 | 
			
		||||
        </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.3 文档说明
 | 
			
		||||
 | 
			
		||||
freemarker:提供了完善的中文文档,地址 http://freemarker.foofun.cn/
 | 
			
		||||
 | 
			
		||||
thymeleaf:官方英文文档地址:[thymeleaf 3.0.11RELEASE](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf)
 | 
			
		||||
 | 
			
		||||
@@ -1,193 +1,203 @@
 | 
			
		||||
# spring boot data jpa
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-data-jpa.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependencies>
 | 
			
		||||
   <!-- data-jpa -->
 | 
			
		||||
    <dependency>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-data-jpa</artifactId>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <!--引入mysql驱动-->
 | 
			
		||||
    <dependency>
 | 
			
		||||
        <groupId>mysql</groupId>
 | 
			
		||||
        <artifactId>mysql-connector-java</artifactId>
 | 
			
		||||
        <version>8.0.13</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
     <!--单元测试包-->
 | 
			
		||||
    <dependency>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
        <scope>test</scope>
 | 
			
		||||
    </dependency>
 | 
			
		||||
</dependencies>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 二、data jpa 的使用
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置数据源
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  datasource:
 | 
			
		||||
    url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
 | 
			
		||||
    username: root
 | 
			
		||||
    password: root
 | 
			
		||||
    driver-class-name: com.mysql.cj.jdbc.Driver
 | 
			
		||||
  jpa:
 | 
			
		||||
    hibernate:
 | 
			
		||||
      ddl-auto: update
 | 
			
		||||
    #Hibernate默认创建的表是myisam引擎,可以用以下方式指定为使用innodb创建表
 | 
			
		||||
    database-platform: org.hibernate.dialect.MySQL57Dialect
 | 
			
		||||
    show-sql: true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 新建查询接口
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 查询接口继承自CrudRepository,CrudRepository 默认定义了部分增删改查方法
 | 
			
		||||
 */
 | 
			
		||||
public interface ProgRepository extends CrudRepository<Programmer, Integer> {
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 方法名遵循命名规范的查询 更多命名规范可以参考官方文档所列出的这张表格
 | 
			
		||||
     */
 | 
			
		||||
    List<Programmer> findAllByName(String name);
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     *分页排序查询
 | 
			
		||||
     */
 | 
			
		||||
    Page<Programmer> findAll(Pageable pageable);
 | 
			
		||||
# spring boot data jpa
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构">1.1 项目结构</a><br/>
 | 
			
		||||
        <a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
 | 
			
		||||
<a href="#二data-jpa-的使用">二、data jpa 的使用</a><br/>
 | 
			
		||||
        <a href="#21-在applicationyml-中配置数据源">2.1 在application.yml 中配置数据源</a><br/>
 | 
			
		||||
        <a href="#22-新建查询接口">2.2 新建查询接口</a><br/>
 | 
			
		||||
        <a href="#23--测试类">2.3  测试类</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 占位符查询
 | 
			
		||||
     */
 | 
			
		||||
    @Query(value = "select u from Programmer u where u.name = ?1 or u.salary =  ?2")
 | 
			
		||||
    List<Programmer> findByConditionAndOrder(String name, float salary, Sort.Order order);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 传入参数名称
 | 
			
		||||
     */
 | 
			
		||||
    @Query("select u from Programmer u where u.name = :name or u.age = :age")
 | 
			
		||||
    Programmer findByParam(@Param("name") String name,
 | 
			
		||||
                           @Param("age") int age);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
关于查询方法遵循的命名规范和关键词见下表:
 | 
			
		||||
 | 
			
		||||
| Keyword             | Sample                                                       | JPQL snippet                                                 |
 | 
			
		||||
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| `And`               | `findByLastnameAndFirstname`                                 | `… where x.lastname = ?1 and x.firstname = ?2`               |
 | 
			
		||||
| `Or`                | `findByLastnameOrFirstname`                                  | `… where x.lastname = ?1 or x.firstname = ?2`                |
 | 
			
		||||
| `Is,Equals`         | `findByFirstname`,`findByFirstnameIs`,<br>`findByFirstnameEquals` | `… where x.firstname = ?1`                                   |
 | 
			
		||||
| `Between`           | `findByStartDateBetween`                                     | `… where x.startDate between ?1 and ?2`                      |
 | 
			
		||||
| `LessThan`          | `findByAgeLessThan`                                          | `… where x.age < ?1`                                         |
 | 
			
		||||
| `LessThanEqual`     | `findByAgeLessThanEqual`                                     | `… where x.age <= ?1`                                        |
 | 
			
		||||
| `GreaterThan`       | `findByAgeGreaterThan`                                       | `… where x.age > ?1`                                         |
 | 
			
		||||
| `GreaterThanEqual`  | `findByAgeGreaterThanEqual`                                  | `… where x.age >= ?1`                                        |
 | 
			
		||||
| `After`             | `findByStartDateAfter`                                       | `… where x.startDate > ?1`                                   |
 | 
			
		||||
| `Before`            | `findByStartDateBefore`                                      | `… where x.startDate < ?1`                                   |
 | 
			
		||||
| `IsNull`            | `findByAgeIsNull`                                            | `… where x.age is null`                                      |
 | 
			
		||||
| `IsNotNull,NotNull` | `findByAge(Is)NotNull`                                       | `… where x.age not null`                                     |
 | 
			
		||||
| `Like`              | `findByFirstnameLike`                                        | `… where x.firstname like ?1`                                |
 | 
			
		||||
| `NotLike`           | `findByFirstnameNotLike`                                     | `… where x.firstname not like ?1`                            |
 | 
			
		||||
| `StartingWith`      | `findByFirstnameStartingWith`                                | `… where x.firstname like ?1`(parameter bound with appended `%`) |
 | 
			
		||||
| `EndingWith`        | `findByFirstnameEndingWith`                                  | `… where x.firstname like ?1`(parameter bound with prepended `%`) |
 | 
			
		||||
| `Containing`        | `findByFirstnameContaining`                                  | `… where x.firstname like ?1`(parameter bound wrapped in `%`) |
 | 
			
		||||
| `OrderBy`           | `findByAgeOrderByLastnameDesc`                               | `… where x.age = ?1 order by x.lastname desc`                |
 | 
			
		||||
| `Not`               | `findByLastnameNot`                                          | `… where x.lastname <> ?1`                                   |
 | 
			
		||||
| `In`                | `findByAgeIn(Collection<Age> ages)`                          | `… where x.age in ?1`                                        |
 | 
			
		||||
| `NotIn`             | `findByAgeNotIn(Collection<Age> ages)`                       | `… where x.age not in ?1`                                    |
 | 
			
		||||
| `True`              | `findByActiveTrue()`                                         | `… where x.active = true`                                    |
 | 
			
		||||
| `False`             | `findByActiveFalse()`                                        | `… where x.active = false`                                   |
 | 
			
		||||
| `IgnoreCase`        | `findByFirstnameIgnoreCase`                                  | `… where UPPER(x.firstame) = UPPER(?1)`                      |
 | 
			
		||||
 | 
			
		||||
#### 2.3  测试类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class DataJPATests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgRepository repository;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存数据测试
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void save() {
 | 
			
		||||
        // 保存单条数据
 | 
			
		||||
        repository.save(new Programmer("pro01", 12, 2121.34f, new Date()));
 | 
			
		||||
        // 保存多条数据
 | 
			
		||||
        List<Programmer> programmers = new ArrayList<>();
 | 
			
		||||
        programmers.add(new Programmer("pro02", 22, 3221.34f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("pro03", 32, 3321.34f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("pro04", 44, 4561.34f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("pro01", 44, 4561.34f, new Date()));
 | 
			
		||||
        repository.saveAll(programmers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询数据测试
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void get() {
 | 
			
		||||
 | 
			
		||||
        // 遵循命名规范的查询
 | 
			
		||||
        List<Programmer> programmers = repository.findAllByName("pro01");
 | 
			
		||||
        programmers.forEach(System.out::println);
 | 
			
		||||
 | 
			
		||||
        // 传入参数名称
 | 
			
		||||
        Programmer param = repository.findByParam("pro02", 22);
 | 
			
		||||
        System.out.println("findByParam:" + param);
 | 
			
		||||
 | 
			
		||||
        // 占位符查询
 | 
			
		||||
        List<Programmer> byCondition = repository.findByConditionAndOrder("pro03", 3321.34f, Sort.Order.asc("salary"));
 | 
			
		||||
        System.out.println("byCondition:" + byCondition);
 | 
			
		||||
 | 
			
		||||
        //条件与分页查询 需要注意的是这里的页数是从第0页开始计算的
 | 
			
		||||
        Page<Programmer> page = repository.findAll(PageRequest.of(0, 10, Sort.Direction.DESC, "salary"));
 | 
			
		||||
        page.get().forEach(System.out::println);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新数据测试
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void update() {
 | 
			
		||||
        // 保存主键相同的数据就认为是更新操作
 | 
			
		||||
        repository.save(new Programmer(1, "updatePro01", 12, 2121.34f, new Date()));
 | 
			
		||||
        Optional<Programmer> programmer = repository.findById(1);
 | 
			
		||||
        Assert.assertEquals(programmer.get().getName(), "updatePro01");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 删除数据测试
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        Optional<Programmer> programmer = repository.findById(2);
 | 
			
		||||
        if (programmer.isPresent()) {
 | 
			
		||||
            repository.deleteById(2);
 | 
			
		||||
        }
 | 
			
		||||
        Assert.assertFalse(programmer.isPresent());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-data-jpa.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependencies>
 | 
			
		||||
   <!-- data-jpa -->
 | 
			
		||||
    <dependency>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-data-jpa</artifactId>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <!--引入mysql驱动-->
 | 
			
		||||
    <dependency>
 | 
			
		||||
        <groupId>mysql</groupId>
 | 
			
		||||
        <artifactId>mysql-connector-java</artifactId>
 | 
			
		||||
        <version>8.0.13</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
     <!--单元测试包-->
 | 
			
		||||
    <dependency>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
        <scope>test</scope>
 | 
			
		||||
    </dependency>
 | 
			
		||||
</dependencies>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 二、data jpa 的使用
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置数据源
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  datasource:
 | 
			
		||||
    url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
 | 
			
		||||
    username: root
 | 
			
		||||
    password: root
 | 
			
		||||
    driver-class-name: com.mysql.cj.jdbc.Driver
 | 
			
		||||
  jpa:
 | 
			
		||||
    hibernate:
 | 
			
		||||
      ddl-auto: update
 | 
			
		||||
    #Hibernate默认创建的表是myisam引擎,可以用以下方式指定为使用innodb创建表
 | 
			
		||||
    database-platform: org.hibernate.dialect.MySQL57Dialect
 | 
			
		||||
    show-sql: true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 新建查询接口
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 查询接口继承自CrudRepository,CrudRepository 默认定义了部分增删改查方法
 | 
			
		||||
 */
 | 
			
		||||
public interface ProgRepository extends CrudRepository<Programmer, Integer> {
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 方法名遵循命名规范的查询 更多命名规范可以参考官方文档所列出的这张表格
 | 
			
		||||
     */
 | 
			
		||||
    List<Programmer> findAllByName(String name);
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     *分页排序查询
 | 
			
		||||
     */
 | 
			
		||||
    Page<Programmer> findAll(Pageable pageable);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 占位符查询
 | 
			
		||||
     */
 | 
			
		||||
    @Query(value = "select u from Programmer u where u.name = ?1 or u.salary =  ?2")
 | 
			
		||||
    List<Programmer> findByConditionAndOrder(String name, float salary, Sort.Order order);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 传入参数名称
 | 
			
		||||
     */
 | 
			
		||||
    @Query("select u from Programmer u where u.name = :name or u.age = :age")
 | 
			
		||||
    Programmer findByParam(@Param("name") String name,
 | 
			
		||||
                           @Param("age") int age);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
关于查询方法遵循的命名规范和关键词见下表:
 | 
			
		||||
 | 
			
		||||
| Keyword             | Sample                                                       | JPQL snippet                                                 |
 | 
			
		||||
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| `And`               | `findByLastnameAndFirstname`                                 | `… where x.lastname = ?1 and x.firstname = ?2`               |
 | 
			
		||||
| `Or`                | `findByLastnameOrFirstname`                                  | `… where x.lastname = ?1 or x.firstname = ?2`                |
 | 
			
		||||
| `Is,Equals`         | `findByFirstname`,`findByFirstnameIs`,<br>`findByFirstnameEquals` | `… where x.firstname = ?1`                                   |
 | 
			
		||||
| `Between`           | `findByStartDateBetween`                                     | `… where x.startDate between ?1 and ?2`                      |
 | 
			
		||||
| `LessThan`          | `findByAgeLessThan`                                          | `… where x.age < ?1`                                         |
 | 
			
		||||
| `LessThanEqual`     | `findByAgeLessThanEqual`                                     | `… where x.age <= ?1`                                        |
 | 
			
		||||
| `GreaterThan`       | `findByAgeGreaterThan`                                       | `… where x.age > ?1`                                         |
 | 
			
		||||
| `GreaterThanEqual`  | `findByAgeGreaterThanEqual`                                  | `… where x.age >= ?1`                                        |
 | 
			
		||||
| `After`             | `findByStartDateAfter`                                       | `… where x.startDate > ?1`                                   |
 | 
			
		||||
| `Before`            | `findByStartDateBefore`                                      | `… where x.startDate < ?1`                                   |
 | 
			
		||||
| `IsNull`            | `findByAgeIsNull`                                            | `… where x.age is null`                                      |
 | 
			
		||||
| `IsNotNull,NotNull` | `findByAge(Is)NotNull`                                       | `… where x.age not null`                                     |
 | 
			
		||||
| `Like`              | `findByFirstnameLike`                                        | `… where x.firstname like ?1`                                |
 | 
			
		||||
| `NotLike`           | `findByFirstnameNotLike`                                     | `… where x.firstname not like ?1`                            |
 | 
			
		||||
| `StartingWith`      | `findByFirstnameStartingWith`                                | `… where x.firstname like ?1`(parameter bound with appended `%`) |
 | 
			
		||||
| `EndingWith`        | `findByFirstnameEndingWith`                                  | `… where x.firstname like ?1`(parameter bound with prepended `%`) |
 | 
			
		||||
| `Containing`        | `findByFirstnameContaining`                                  | `… where x.firstname like ?1`(parameter bound wrapped in `%`) |
 | 
			
		||||
| `OrderBy`           | `findByAgeOrderByLastnameDesc`                               | `… where x.age = ?1 order by x.lastname desc`                |
 | 
			
		||||
| `Not`               | `findByLastnameNot`                                          | `… where x.lastname <> ?1`                                   |
 | 
			
		||||
| `In`                | `findByAgeIn(Collection<Age> ages)`                          | `… where x.age in ?1`                                        |
 | 
			
		||||
| `NotIn`             | `findByAgeNotIn(Collection<Age> ages)`                       | `… where x.age not in ?1`                                    |
 | 
			
		||||
| `True`              | `findByActiveTrue()`                                         | `… where x.active = true`                                    |
 | 
			
		||||
| `False`             | `findByActiveFalse()`                                        | `… where x.active = false`                                   |
 | 
			
		||||
| `IgnoreCase`        | `findByFirstnameIgnoreCase`                                  | `… where UPPER(x.firstame) = UPPER(?1)`                      |
 | 
			
		||||
 | 
			
		||||
#### 2.3  测试类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class DataJPATests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgRepository repository;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存数据测试
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void save() {
 | 
			
		||||
        // 保存单条数据
 | 
			
		||||
        repository.save(new Programmer("pro01", 12, 2121.34f, new Date()));
 | 
			
		||||
        // 保存多条数据
 | 
			
		||||
        List<Programmer> programmers = new ArrayList<>();
 | 
			
		||||
        programmers.add(new Programmer("pro02", 22, 3221.34f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("pro03", 32, 3321.34f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("pro04", 44, 4561.34f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("pro01", 44, 4561.34f, new Date()));
 | 
			
		||||
        repository.saveAll(programmers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询数据测试
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void get() {
 | 
			
		||||
 | 
			
		||||
        // 遵循命名规范的查询
 | 
			
		||||
        List<Programmer> programmers = repository.findAllByName("pro01");
 | 
			
		||||
        programmers.forEach(System.out::println);
 | 
			
		||||
 | 
			
		||||
        // 传入参数名称
 | 
			
		||||
        Programmer param = repository.findByParam("pro02", 22);
 | 
			
		||||
        System.out.println("findByParam:" + param);
 | 
			
		||||
 | 
			
		||||
        // 占位符查询
 | 
			
		||||
        List<Programmer> byCondition = repository.findByConditionAndOrder("pro03", 3321.34f, Sort.Order.asc("salary"));
 | 
			
		||||
        System.out.println("byCondition:" + byCondition);
 | 
			
		||||
 | 
			
		||||
        //条件与分页查询 需要注意的是这里的页数是从第0页开始计算的
 | 
			
		||||
        Page<Programmer> page = repository.findAll(PageRequest.of(0, 10, Sort.Direction.DESC, "salary"));
 | 
			
		||||
        page.get().forEach(System.out::println);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新数据测试
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void update() {
 | 
			
		||||
        // 保存主键相同的数据就认为是更新操作
 | 
			
		||||
        repository.save(new Programmer(1, "updatePro01", 12, 2121.34f, new Date()));
 | 
			
		||||
        Optional<Programmer> programmer = repository.findById(1);
 | 
			
		||||
        Assert.assertEquals(programmer.get().getName(), "updatePro01");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 删除数据测试
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        Optional<Programmer> programmer = repository.findById(2);
 | 
			
		||||
        if (programmer.isPresent()) {
 | 
			
		||||
            repository.deleteById(2);
 | 
			
		||||
        }
 | 
			
		||||
        Assert.assertFalse(programmer.isPresent());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,196 +1,207 @@
 | 
			
		||||
# spring boot 整合 druid+mybatis
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下;
 | 
			
		||||
 | 
			
		||||
2. 为了使用druid控制台的功能,项目以web的方式构建。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid-mybatis.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
需要说明的是按照spring 官方对于自定义的starter 命名规范的推荐:
 | 
			
		||||
 | 
			
		||||
- 官方的starter命名:spring-boot-starter-XXXX
 | 
			
		||||
- 其他第三方starter命名:XXXX-spring-boot-starte
 | 
			
		||||
 | 
			
		||||
所以mybatis的starter命名为mybatis-spring-boot-starter,如果有自定义starter需求,也需要按照此命名规则进行命名。
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.mybatis.spring.boot</groupId>
 | 
			
		||||
    <artifactId>mybatis-spring-boot-starter</artifactId>
 | 
			
		||||
    <version>1.3.2</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--引入mysql驱动-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>mysql</groupId>
 | 
			
		||||
    <artifactId>mysql-connector-java</artifactId>
 | 
			
		||||
    <version>8.0.13</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--druid 依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>com.alibaba</groupId>
 | 
			
		||||
    <artifactId>druid-spring-boot-starter</artifactId>
 | 
			
		||||
    <version>1.1.10</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
spring boot 与 mybatis 版本的对应关系:
 | 
			
		||||
 | 
			
		||||
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot   |
 | 
			
		||||
| --------------------------- | ------------------------------------------------------------ | ------------- |
 | 
			
		||||
| **1.3.x (1.3.1)**           | 1.3 or higher                                                | 1.5 or higher |
 | 
			
		||||
| **1.2.x (1.2.1)**           | 1.3 or higher                                                | 1.4 or higher |
 | 
			
		||||
| **1.1.x (1.1.1)**           | 1.3 or higher                                                | 1.3 or higher |
 | 
			
		||||
| **1.0.x (1.0.2)**           | 1.2 or higher                                                | 1.3 or higher |
 | 
			
		||||
# spring boot 整合 druid+mybatis
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构">1.1 项目结构</a><br/>
 | 
			
		||||
        <a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
 | 
			
		||||
<a href="#二整合-druid-+-mybatis">二、整合 druid + mybatis</a><br/>
 | 
			
		||||
        <a href="#21-在applicationyml-中配置数据源">2.1 在application.yml 中配置数据源</a><br/>
 | 
			
		||||
        <a href="#22--新建查询接口和controller">2.2  新建查询接口和controller</a><br/>
 | 
			
		||||
        <a href="#23-关于druid监控数据的外部化调用">2.3 关于druid监控数据的外部化调用</a><br/>
 | 
			
		||||
        <a href="#24-druid-控制台的使用默认访问地址-http//localhost8080/druid/loginhtml">2.4 druid 控制台的使用,默认访问地址 http://localhost:8080/druid/login.html</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、整合 druid + mybatis
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置数据源
 | 
			
		||||
 | 
			
		||||
本用例采用druid作为数据库连接池,虽然druid性能略逊于Hikari,但是提供了更为全面的监控管理,可以按照实际需求选用druid或者Hikari。(关于Hikari数据源的配置可以参考[spring-boot-mybatis项目](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mybatis))
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  datasource:
 | 
			
		||||
    url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
 | 
			
		||||
    username: root
 | 
			
		||||
    password: root
 | 
			
		||||
    driver-class-name: com.mysql.cj.jdbc.Driver
 | 
			
		||||
 | 
			
		||||
    # 使用 druid 作为连接池  更多配置的说明可以参见 druid starter 中文文档 https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
 | 
			
		||||
    type: com.alibaba.druid.pool.DruidDataSource
 | 
			
		||||
    druid:
 | 
			
		||||
      # 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
 | 
			
		||||
      initialSize: 5
 | 
			
		||||
      # 最小连接池数量
 | 
			
		||||
      minIdle: 5
 | 
			
		||||
      # 最大连接池数量
 | 
			
		||||
      maxActive: 10
 | 
			
		||||
      # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
 | 
			
		||||
      maxWait: 60000
 | 
			
		||||
      # Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
 | 
			
		||||
      timeBetweenEvictionRunsMillis: 60000
 | 
			
		||||
      # 连接保持空闲而不被驱逐的最小时间
 | 
			
		||||
      minEvictableIdleTimeMillis: 300000
 | 
			
		||||
      # 用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL
 | 
			
		||||
      validationQuery: SELECT 1
 | 
			
		||||
      # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
 | 
			
		||||
      testWhileIdle: true
 | 
			
		||||
      # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
 | 
			
		||||
      testOnBorrow: false
 | 
			
		||||
      # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
 | 
			
		||||
      testOnReturn: false
 | 
			
		||||
      # 是否自动回收超时连接
 | 
			
		||||
      removeAbandoned: true
 | 
			
		||||
      # 超时时间(以秒数为单位)
 | 
			
		||||
      remove-abandoned-timeout: 180
 | 
			
		||||
 | 
			
		||||
      # druid 监控的配置 如果不使用 druid 的监控功能的话 以下配置就不是必须的
 | 
			
		||||
      # 本项目监控台访问地址: http://localhost:8080/druid/login.html
 | 
			
		||||
 | 
			
		||||
      # WebStatFilter用于采集web-jdbc关联监控的数据。
 | 
			
		||||
      # 更多配置可参见: https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter
 | 
			
		||||
      web-stat-filter:
 | 
			
		||||
        # 是否开启 WebStatFilter 默认是true
 | 
			
		||||
        enabled: true
 | 
			
		||||
        # 需要拦截的url
 | 
			
		||||
        url-pattern: /*
 | 
			
		||||
        # 排除静态资源的请求
 | 
			
		||||
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
 | 
			
		||||
 | 
			
		||||
      # Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。
 | 
			
		||||
      # 更多配置可参见:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
 | 
			
		||||
      stat-view-servlet:
 | 
			
		||||
        #是否启用StatViewServlet 默认值true
 | 
			
		||||
        enabled: true
 | 
			
		||||
        # 需要拦截的url
 | 
			
		||||
        url-pattern: /druid/*
 | 
			
		||||
        # 允许清空统计数据
 | 
			
		||||
        reset-enable: true
 | 
			
		||||
        login-username: druid
 | 
			
		||||
        login-password: druid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# mybatis 相关配置
 | 
			
		||||
mybatis:
 | 
			
		||||
    configuration:
 | 
			
		||||
      # 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
 | 
			
		||||
      # oracle数据库建议配置为JdbcType.NULL, 默认是Other
 | 
			
		||||
      jdbc-type-for-null: 'null'
 | 
			
		||||
      # 是否打印sql语句 调试的时候可以开启
 | 
			
		||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2  新建查询接口和controller
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface ProgrammerDao {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Select("select * from programmer")
 | 
			
		||||
    List<Programmer> selectAll();
 | 
			
		||||
 | 
			
		||||
    @Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
 | 
			
		||||
    void save(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    @Select("select * from programmer where name = #{id}")
 | 
			
		||||
    Programmer selectById(int id);
 | 
			
		||||
 | 
			
		||||
    @Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
 | 
			
		||||
    int modify(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    @Delete(" delete from programmer where id = #{id}")
 | 
			
		||||
    void delete(int id);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
@RestController
 | 
			
		||||
public class ProgrammerController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgrammerDao programmerDao;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/programmers")
 | 
			
		||||
    public List<Programmer> get() {
 | 
			
		||||
        return programmerDao.selectAll();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 关于druid监控数据的外部化调用
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description :在 Spring Boot 中可以通过 HTTP 接口将 Druid 监控数据以JSON 的形式暴露出去,
 | 
			
		||||
 * 实际使用中你可以根据你的需要自由地对监控数据、暴露方式进行扩展。
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
public class DruidStatController {
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/stat")
 | 
			
		||||
    public Object druidStat() {
 | 
			
		||||
        // DruidStatManagerFacade#getDataSourceStatDataList 该方法可以获取所有数据源的监控数据
 | 
			
		||||
        return DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/druid-status.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 2.4 druid 控制台的使用,默认访问地址 http://localhost:8080/druid/login.html
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid%20%E6%8E%A7%E5%88%B6%E5%8F%B0.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下;
 | 
			
		||||
 | 
			
		||||
2. 为了使用druid控制台的功能,项目以web的方式构建。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid-mybatis.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
需要说明的是按照spring 官方对于自定义的starter 命名规范的推荐:
 | 
			
		||||
 | 
			
		||||
- 官方的starter命名:spring-boot-starter-XXXX
 | 
			
		||||
- 其他第三方starter命名:XXXX-spring-boot-starte
 | 
			
		||||
 | 
			
		||||
所以mybatis的starter命名为mybatis-spring-boot-starter,如果有自定义starter需求,也需要按照此命名规则进行命名。
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.mybatis.spring.boot</groupId>
 | 
			
		||||
    <artifactId>mybatis-spring-boot-starter</artifactId>
 | 
			
		||||
    <version>1.3.2</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--引入mysql驱动-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>mysql</groupId>
 | 
			
		||||
    <artifactId>mysql-connector-java</artifactId>
 | 
			
		||||
    <version>8.0.13</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--druid 依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>com.alibaba</groupId>
 | 
			
		||||
    <artifactId>druid-spring-boot-starter</artifactId>
 | 
			
		||||
    <version>1.1.10</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
spring boot 与 mybatis 版本的对应关系:
 | 
			
		||||
 | 
			
		||||
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot   |
 | 
			
		||||
| --------------------------- | ------------------------------------------------------------ | ------------- |
 | 
			
		||||
| **1.3.x (1.3.1)**           | 1.3 or higher                                                | 1.5 or higher |
 | 
			
		||||
| **1.2.x (1.2.1)**           | 1.3 or higher                                                | 1.4 or higher |
 | 
			
		||||
| **1.1.x (1.1.1)**           | 1.3 or higher                                                | 1.3 or higher |
 | 
			
		||||
| **1.0.x (1.0.2)**           | 1.2 or higher                                                | 1.3 or higher |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、整合 druid + mybatis
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置数据源
 | 
			
		||||
 | 
			
		||||
本用例采用druid作为数据库连接池,虽然druid性能略逊于Hikari,但是提供了更为全面的监控管理,可以按照实际需求选用druid或者Hikari。(关于Hikari数据源的配置可以参考[spring-boot-mybatis项目](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mybatis))
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  datasource:
 | 
			
		||||
    url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
 | 
			
		||||
    username: root
 | 
			
		||||
    password: root
 | 
			
		||||
    driver-class-name: com.mysql.cj.jdbc.Driver
 | 
			
		||||
 | 
			
		||||
    # 使用 druid 作为连接池  更多配置的说明可以参见 druid starter 中文文档 https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
 | 
			
		||||
    type: com.alibaba.druid.pool.DruidDataSource
 | 
			
		||||
    druid:
 | 
			
		||||
      # 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
 | 
			
		||||
      initialSize: 5
 | 
			
		||||
      # 最小连接池数量
 | 
			
		||||
      minIdle: 5
 | 
			
		||||
      # 最大连接池数量
 | 
			
		||||
      maxActive: 10
 | 
			
		||||
      # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
 | 
			
		||||
      maxWait: 60000
 | 
			
		||||
      # Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
 | 
			
		||||
      timeBetweenEvictionRunsMillis: 60000
 | 
			
		||||
      # 连接保持空闲而不被驱逐的最小时间
 | 
			
		||||
      minEvictableIdleTimeMillis: 300000
 | 
			
		||||
      # 用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL
 | 
			
		||||
      validationQuery: SELECT 1
 | 
			
		||||
      # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
 | 
			
		||||
      testWhileIdle: true
 | 
			
		||||
      # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
 | 
			
		||||
      testOnBorrow: false
 | 
			
		||||
      # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
 | 
			
		||||
      testOnReturn: false
 | 
			
		||||
      # 是否自动回收超时连接
 | 
			
		||||
      removeAbandoned: true
 | 
			
		||||
      # 超时时间(以秒数为单位)
 | 
			
		||||
      remove-abandoned-timeout: 180
 | 
			
		||||
 | 
			
		||||
      # druid 监控的配置 如果不使用 druid 的监控功能的话 以下配置就不是必须的
 | 
			
		||||
      # 本项目监控台访问地址: http://localhost:8080/druid/login.html
 | 
			
		||||
 | 
			
		||||
      # WebStatFilter用于采集web-jdbc关联监控的数据。
 | 
			
		||||
      # 更多配置可参见: https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter
 | 
			
		||||
      web-stat-filter:
 | 
			
		||||
        # 是否开启 WebStatFilter 默认是true
 | 
			
		||||
        enabled: true
 | 
			
		||||
        # 需要拦截的url
 | 
			
		||||
        url-pattern: /*
 | 
			
		||||
        # 排除静态资源的请求
 | 
			
		||||
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
 | 
			
		||||
 | 
			
		||||
      # Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。
 | 
			
		||||
      # 更多配置可参见:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
 | 
			
		||||
      stat-view-servlet:
 | 
			
		||||
        #是否启用StatViewServlet 默认值true
 | 
			
		||||
        enabled: true
 | 
			
		||||
        # 需要拦截的url
 | 
			
		||||
        url-pattern: /druid/*
 | 
			
		||||
        # 允许清空统计数据
 | 
			
		||||
        reset-enable: true
 | 
			
		||||
        login-username: druid
 | 
			
		||||
        login-password: druid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# mybatis 相关配置
 | 
			
		||||
mybatis:
 | 
			
		||||
    configuration:
 | 
			
		||||
      # 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
 | 
			
		||||
      # oracle数据库建议配置为JdbcType.NULL, 默认是Other
 | 
			
		||||
      jdbc-type-for-null: 'null'
 | 
			
		||||
      # 是否打印sql语句 调试的时候可以开启
 | 
			
		||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2  新建查询接口和controller
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface ProgrammerDao {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Select("select * from programmer")
 | 
			
		||||
    List<Programmer> selectAll();
 | 
			
		||||
 | 
			
		||||
    @Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
 | 
			
		||||
    void save(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    @Select("select * from programmer where name = #{id}")
 | 
			
		||||
    Programmer selectById(int id);
 | 
			
		||||
 | 
			
		||||
    @Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
 | 
			
		||||
    int modify(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    @Delete(" delete from programmer where id = #{id}")
 | 
			
		||||
    void delete(int id);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
@RestController
 | 
			
		||||
public class ProgrammerController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgrammerDao programmerDao;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/programmers")
 | 
			
		||||
    public List<Programmer> get() {
 | 
			
		||||
        return programmerDao.selectAll();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 关于druid监控数据的外部化调用
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description :在 Spring Boot 中可以通过 HTTP 接口将 Druid 监控数据以JSON 的形式暴露出去,
 | 
			
		||||
 * 实际使用中你可以根据你的需要自由地对监控数据、暴露方式进行扩展。
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
public class DruidStatController {
 | 
			
		||||
 | 
			
		||||
    @GetMapping("/stat")
 | 
			
		||||
    public Object druidStat() {
 | 
			
		||||
        // DruidStatManagerFacade#getDataSourceStatDataList 该方法可以获取所有数据源的监控数据
 | 
			
		||||
        return DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/druid-status.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 2.4 druid 控制台的使用,默认访问地址 http://localhost:8080/druid/login.html
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,348 +1,362 @@
 | 
			
		||||
# spring boot 整合 dubbo
 | 
			
		||||
 | 
			
		||||
## 一、 项目结构说明
 | 
			
		||||
 | 
			
		||||
1.1  按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-boot-dubbo下构建三个子模块:
 | 
			
		||||
 | 
			
		||||
1. boot-dubbo-common 是公共模块,用于存放公共的接口和bean,被boot-dubbo-provider和boot-dubbo-consumer在pom.xml中引用;
 | 
			
		||||
2. boot-dubbo-provider 是服务的提供者,提供商品的查询服务;
 | 
			
		||||
3. boot-dubbo-consumer是服务的消费者,调用provider提供的查询服务。
 | 
			
		||||
 | 
			
		||||
1.2  本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记  [Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dubbo.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、关键依赖
 | 
			
		||||
 | 
			
		||||
在父工程的项目中统一导入依赖dubbo的starter,父工程的pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <packaging>pom</packaging>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
        <module>boot-dubbo-common</module>
 | 
			
		||||
        <module>boot-dubbo-consumer</module>
 | 
			
		||||
        <module>boot-dubbo-provider</module>
 | 
			
		||||
    </modules>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-parent</artifactId>
 | 
			
		||||
        <version>2.1.1.RELEASE</version>
 | 
			
		||||
        <relativePath/> <!-- lookup parent from repository -->
 | 
			
		||||
    </parent>
 | 
			
		||||
    <groupId>com.heibaiying</groupId>
 | 
			
		||||
    <artifactId>spring-boot-dubbo</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>spring-boot-dubbo</name>
 | 
			
		||||
    <description>Demo project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-freemarker</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-devtools</artifactId>
 | 
			
		||||
            <scope>runtime</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.projectlombok</groupId>
 | 
			
		||||
            <artifactId>lombok</artifactId>
 | 
			
		||||
            <optional>true</optional>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--引入dubbo start依赖-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.alibaba.boot</groupId>
 | 
			
		||||
            <artifactId>dubbo-spring-boot-starter</artifactId>
 | 
			
		||||
            <version>0.2.0</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、公共模块(boot-dubbo-common)
 | 
			
		||||
 | 
			
		||||
- api 下为公共的调用接口;
 | 
			
		||||
- bean 下为公共的实体类。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-common.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 四、 服务提供者(boot-dubbo-provider)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-provider.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 4.1 提供方配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
dubbo:
 | 
			
		||||
  application:
 | 
			
		||||
    name: boot-duboo-provider
 | 
			
		||||
  # 指定注册协议和注册地址  dubbo推荐使用zookeeper作为注册中心,并且在start依赖中引入了zookeeper的java客户端Curator
 | 
			
		||||
  registry:
 | 
			
		||||
    protocol: zookeeper
 | 
			
		||||
    address: 127.0.0.1:2181
 | 
			
		||||
  protocol.name: dubbo
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2  使用注解@Service暴露服务
 | 
			
		||||
 | 
			
		||||
需要注意的是这里的@Service注解不是spring的注解,而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
package com.heibaiying.dubboprovider.service;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.dubbo.config.annotation.Service;
 | 
			
		||||
import com.heibaiying.api.IProductService;
 | 
			
		||||
import com.heibaiying.bean.Product;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 产品提供接口实现类
 | 
			
		||||
 */
 | 
			
		||||
@Service(timeout = 5000)
 | 
			
		||||
public class ProductService implements IProductService {
 | 
			
		||||
 | 
			
		||||
    private static List<Product> productList = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        for (int i = 0; i < 20; i++) {
 | 
			
		||||
            productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Product queryProductById(int id) {
 | 
			
		||||
        for (Product product : productList) {
 | 
			
		||||
            if (product.getId() == id) {
 | 
			
		||||
                return product;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public List<Product> queryAllProducts() {
 | 
			
		||||
        return productList;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 五、服务消费者(boot-dubbo-consumer)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-consumer1.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.消费方的配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
dubbo:
 | 
			
		||||
  application:
 | 
			
		||||
    name: boot-duboo-provider
 | 
			
		||||
  # 指定注册协议和注册地址  dubbo推荐使用zookeeper作为注册中心,并且在start依赖中引入了zookeeper的java客户端Curator
 | 
			
		||||
  registry:
 | 
			
		||||
    protocol: zookeeper
 | 
			
		||||
    address: 127.0.0.1:2181
 | 
			
		||||
  protocol.name: dubbo
 | 
			
		||||
  # 关闭所有服务的启动时检查 (没有提供者时报错)视实际情况设置
 | 
			
		||||
  consumer:
 | 
			
		||||
    check: false
 | 
			
		||||
server:
 | 
			
		||||
port: 8090
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.使用注解@Reference引用远程服务
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
package com.heibaiying.dubboconsumer.controller;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.dubbo.config.annotation.Reference;
 | 
			
		||||
import com.heibaiying.api.IProductService;
 | 
			
		||||
import com.heibaiying.bean.Product;
 | 
			
		||||
import org.springframework.stereotype.Controller;
 | 
			
		||||
import org.springframework.ui.Model;
 | 
			
		||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("sell")
 | 
			
		||||
public class SellController {
 | 
			
		||||
 | 
			
		||||
    // dubbo远程引用注解
 | 
			
		||||
    @Reference
 | 
			
		||||
    private IProductService productService;
 | 
			
		||||
 | 
			
		||||
    @RequestMapping
 | 
			
		||||
    public String productList(Model model) {
 | 
			
		||||
        List<Product> products = productService.queryAllProducts();
 | 
			
		||||
        model.addAttribute("products", products);
 | 
			
		||||
        return "products";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("product/{id}")
 | 
			
		||||
    public String productDetail(@PathVariable int id, Model model) {
 | 
			
		||||
        Product product = productService.queryProductById(id);
 | 
			
		||||
        model.addAttribute("product", product);
 | 
			
		||||
        return "product";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 六、项目构建的说明
 | 
			
		||||
 | 
			
		||||
因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
mvn install -Dmaven.test.skip = true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
consumer中 pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <artifactId>spring-boot-dubbo</artifactId>
 | 
			
		||||
        <groupId>com.heibaiying</groupId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>boot-dubbo-consumer</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>boot-dubbo-consumer</name>
 | 
			
		||||
    <description>dubbo project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <!--引入对公共模块的依赖-->
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.heibaiying</groupId>
 | 
			
		||||
            <artifactId>boot-dubbo-common</artifactId>
 | 
			
		||||
            <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
provider中 pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <artifactId>spring-boot-dubbo</artifactId>
 | 
			
		||||
        <groupId>com.heibaiying</groupId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <artifactId>boot-dubbo-provider</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>boot-dubbo-provider</name>
 | 
			
		||||
    <description>dubbo project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <!--引入对公共模块的依赖-->
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.heibaiying</groupId>
 | 
			
		||||
            <artifactId>boot-dubbo-common</artifactId>
 | 
			
		||||
            <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 七、关于dubbo新版本管理控制台的安装说明
 | 
			
		||||
 | 
			
		||||
安装:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
 | 
			
		||||
cd /var/tmp/dubbo-ops
 | 
			
		||||
mvn clean package
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
配置:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
配置文件为:
 | 
			
		||||
dubbo-admin-backend/src/main/resources/application.properties
 | 
			
		||||
主要的配置有 默认的配置就是127.0.0.1:2181:
 | 
			
		||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
启动:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
mvn --projects dubbo-admin-backend spring-boot:run
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
访问:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
http://127.0.0.1:8080
 | 
			
		||||
# spring boot 整合 dubbo
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
 | 
			
		||||
<a href="#二关键依赖">二、关键依赖</a><br/>
 | 
			
		||||
<a href="#三公共模块boot-dubbo-common">三、公共模块(boot-dubbo-common)</a><br/>
 | 
			
		||||
<a href="#四-服务提供者boot-dubbo-provider">四、 服务提供者(boot-dubbo-provider)</a><br/>
 | 
			
		||||
        <a href="#41-提供方配置">4.1 提供方配置</a><br/>
 | 
			
		||||
        <a href="#42--使用注解@Service暴露服务">4.2  使用注解@Service暴露服务</a><br/>
 | 
			
		||||
<a href="#五服务消费者boot-dubbo-consumer">五、服务消费者(boot-dubbo-consumer)</a><br/>
 | 
			
		||||
        <a href="#1消费方的配置">1.消费方的配置</a><br/>
 | 
			
		||||
        <a href="#2使用注解@Reference引用远程服务">2.使用注解@Reference引用远程服务</a><br/>
 | 
			
		||||
<a href="#六项目构建的说明">六、项目构建的说明</a><br/>
 | 
			
		||||
<a href="#七关于dubbo新版本管理控制台的安装说明">七、关于dubbo新版本管理控制台的安装说明</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、 项目结构说明
 | 
			
		||||
 | 
			
		||||
1.1  按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-boot-dubbo下构建三个子模块:
 | 
			
		||||
 | 
			
		||||
1. boot-dubbo-common 是公共模块,用于存放公共的接口和bean,被boot-dubbo-provider和boot-dubbo-consumer在pom.xml中引用;
 | 
			
		||||
2. boot-dubbo-provider 是服务的提供者,提供商品的查询服务;
 | 
			
		||||
3. boot-dubbo-consumer是服务的消费者,调用provider提供的查询服务。
 | 
			
		||||
 | 
			
		||||
1.2  本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记  [Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dubbo.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、关键依赖
 | 
			
		||||
 | 
			
		||||
在父工程的项目中统一导入依赖dubbo的starter,父工程的pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <packaging>pom</packaging>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
        <module>boot-dubbo-common</module>
 | 
			
		||||
        <module>boot-dubbo-consumer</module>
 | 
			
		||||
        <module>boot-dubbo-provider</module>
 | 
			
		||||
    </modules>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-parent</artifactId>
 | 
			
		||||
        <version>2.1.1.RELEASE</version>
 | 
			
		||||
        <relativePath/> <!-- lookup parent from repository -->
 | 
			
		||||
    </parent>
 | 
			
		||||
    <groupId>com.heibaiying</groupId>
 | 
			
		||||
    <artifactId>spring-boot-dubbo</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>spring-boot-dubbo</name>
 | 
			
		||||
    <description>Demo project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-freemarker</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-devtools</artifactId>
 | 
			
		||||
            <scope>runtime</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.projectlombok</groupId>
 | 
			
		||||
            <artifactId>lombok</artifactId>
 | 
			
		||||
            <optional>true</optional>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--引入dubbo start依赖-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.alibaba.boot</groupId>
 | 
			
		||||
            <artifactId>dubbo-spring-boot-starter</artifactId>
 | 
			
		||||
            <version>0.2.0</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、公共模块(boot-dubbo-common)
 | 
			
		||||
 | 
			
		||||
- api 下为公共的调用接口;
 | 
			
		||||
- bean 下为公共的实体类。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-common.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 四、 服务提供者(boot-dubbo-provider)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-provider.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 4.1 提供方配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
dubbo:
 | 
			
		||||
  application:
 | 
			
		||||
    name: boot-duboo-provider
 | 
			
		||||
  # 指定注册协议和注册地址  dubbo推荐使用zookeeper作为注册中心,并且在start依赖中引入了zookeeper的java客户端Curator
 | 
			
		||||
  registry:
 | 
			
		||||
    protocol: zookeeper
 | 
			
		||||
    address: 127.0.0.1:2181
 | 
			
		||||
  protocol.name: dubbo
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2  使用注解@Service暴露服务
 | 
			
		||||
 | 
			
		||||
需要注意的是这里的@Service注解不是spring的注解,而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
package com.heibaiying.dubboprovider.service;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.dubbo.config.annotation.Service;
 | 
			
		||||
import com.heibaiying.api.IProductService;
 | 
			
		||||
import com.heibaiying.bean.Product;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 产品提供接口实现类
 | 
			
		||||
 */
 | 
			
		||||
@Service(timeout = 5000)
 | 
			
		||||
public class ProductService implements IProductService {
 | 
			
		||||
 | 
			
		||||
    private static List<Product> productList = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        for (int i = 0; i < 20; i++) {
 | 
			
		||||
            productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Product queryProductById(int id) {
 | 
			
		||||
        for (Product product : productList) {
 | 
			
		||||
            if (product.getId() == id) {
 | 
			
		||||
                return product;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public List<Product> queryAllProducts() {
 | 
			
		||||
        return productList;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 五、服务消费者(boot-dubbo-consumer)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-consumer1.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.消费方的配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
dubbo:
 | 
			
		||||
  application:
 | 
			
		||||
    name: boot-duboo-provider
 | 
			
		||||
  # 指定注册协议和注册地址  dubbo推荐使用zookeeper作为注册中心,并且在start依赖中引入了zookeeper的java客户端Curator
 | 
			
		||||
  registry:
 | 
			
		||||
    protocol: zookeeper
 | 
			
		||||
    address: 127.0.0.1:2181
 | 
			
		||||
  protocol.name: dubbo
 | 
			
		||||
  # 关闭所有服务的启动时检查 (没有提供者时报错)视实际情况设置
 | 
			
		||||
  consumer:
 | 
			
		||||
    check: false
 | 
			
		||||
server:
 | 
			
		||||
port: 8090
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.使用注解@Reference引用远程服务
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
package com.heibaiying.dubboconsumer.controller;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.dubbo.config.annotation.Reference;
 | 
			
		||||
import com.heibaiying.api.IProductService;
 | 
			
		||||
import com.heibaiying.bean.Product;
 | 
			
		||||
import org.springframework.stereotype.Controller;
 | 
			
		||||
import org.springframework.ui.Model;
 | 
			
		||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("sell")
 | 
			
		||||
public class SellController {
 | 
			
		||||
 | 
			
		||||
    // dubbo远程引用注解
 | 
			
		||||
    @Reference
 | 
			
		||||
    private IProductService productService;
 | 
			
		||||
 | 
			
		||||
    @RequestMapping
 | 
			
		||||
    public String productList(Model model) {
 | 
			
		||||
        List<Product> products = productService.queryAllProducts();
 | 
			
		||||
        model.addAttribute("products", products);
 | 
			
		||||
        return "products";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("product/{id}")
 | 
			
		||||
    public String productDetail(@PathVariable int id, Model model) {
 | 
			
		||||
        Product product = productService.queryProductById(id);
 | 
			
		||||
        model.addAttribute("product", product);
 | 
			
		||||
        return "product";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 六、项目构建的说明
 | 
			
		||||
 | 
			
		||||
因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
mvn install -Dmaven.test.skip = true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
consumer中 pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <artifactId>spring-boot-dubbo</artifactId>
 | 
			
		||||
        <groupId>com.heibaiying</groupId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>boot-dubbo-consumer</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>boot-dubbo-consumer</name>
 | 
			
		||||
    <description>dubbo project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <!--引入对公共模块的依赖-->
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.heibaiying</groupId>
 | 
			
		||||
            <artifactId>boot-dubbo-common</artifactId>
 | 
			
		||||
            <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
provider中 pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <artifactId>spring-boot-dubbo</artifactId>
 | 
			
		||||
        <groupId>com.heibaiying</groupId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <artifactId>boot-dubbo-provider</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>boot-dubbo-provider</name>
 | 
			
		||||
    <description>dubbo project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <!--引入对公共模块的依赖-->
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.heibaiying</groupId>
 | 
			
		||||
            <artifactId>boot-dubbo-common</artifactId>
 | 
			
		||||
            <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 七、关于dubbo新版本管理控制台的安装说明
 | 
			
		||||
 | 
			
		||||
安装:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
 | 
			
		||||
cd /var/tmp/dubbo-ops
 | 
			
		||||
mvn clean package
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
配置:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
配置文件为:
 | 
			
		||||
dubbo-admin-backend/src/main/resources/application.properties
 | 
			
		||||
主要的配置有 默认的配置就是127.0.0.1:2181:
 | 
			
		||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
启动:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
mvn --projects dubbo-admin-backend spring-boot:run
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
访问:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
http://127.0.0.1:8080
 | 
			
		||||
@@ -1,84 +1,94 @@
 | 
			
		||||
# spring boot 内置容器 整合 jsp
 | 
			
		||||
# spring boot 内置容器 整合 jsp
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构">1.1 项目结构</a><br/>
 | 
			
		||||
        <a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
 | 
			
		||||
<a href="#二整合-jsp">二、整合 jsp</a><br/>
 | 
			
		||||
        <a href="#21-导入整合的依赖">2.1 导入整合的依赖</a><br/>
 | 
			
		||||
        <a href="#22-在applicationyml-中指定访问视图文件的前缀和后缀">2.2 在application.yml 中指定访问视图文件的前缀和后缀 </a><br/>
 | 
			
		||||
        <a href="#23--新建controller和showjsp-测试整合是否成功">2.3  新建controller和show.jsp 测试整合是否成功</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-jsp.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--整合 jsp 依赖包-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.apache.tomcat.embed</groupId>
 | 
			
		||||
    <artifactId>tomcat-embed-jasper</artifactId>
 | 
			
		||||
    <scope>provided</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--jsp jstl 标签支持-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>javax.servlet</groupId>
 | 
			
		||||
    <artifactId>jstl</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 二、整合 jsp
 | 
			
		||||
 | 
			
		||||
#### 2.1 导入整合的依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--整合 jsp 依赖包-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.apache.tomcat.embed</groupId>
 | 
			
		||||
    <artifactId>tomcat-embed-jasper</artifactId>
 | 
			
		||||
    <scope>provided</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--jsp jstl 标签支持-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>javax.servlet</groupId>
 | 
			
		||||
    <artifactId>jstl</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀 
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
spring:
 | 
			
		||||
  mvc:
 | 
			
		||||
    view:
 | 
			
		||||
      prefix: /WEB-INF/jsp/
 | 
			
		||||
      suffix: .jsp
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3  新建controller和show.jsp 测试整合是否成功
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("index")
 | 
			
		||||
public class JspController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping
 | 
			
		||||
    public String jsp(Model model){
 | 
			
		||||
        Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
 | 
			
		||||
        model.addAttribute("programmer",programmer);
 | 
			
		||||
        return "show";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```jsp
 | 
			
		||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <title>programmer</title>
 | 
			
		||||
    <link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<ul>
 | 
			
		||||
    <li>姓名: ${programmer.name}</li>
 | 
			
		||||
    <li>年龄: ${programmer.age}</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-jsp.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--整合 jsp 依赖包-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.apache.tomcat.embed</groupId>
 | 
			
		||||
    <artifactId>tomcat-embed-jasper</artifactId>
 | 
			
		||||
    <scope>provided</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--jsp jstl 标签支持-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>javax.servlet</groupId>
 | 
			
		||||
    <artifactId>jstl</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 二、整合 jsp
 | 
			
		||||
 | 
			
		||||
#### 2.1 导入整合的依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--整合 jsp 依赖包-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.apache.tomcat.embed</groupId>
 | 
			
		||||
    <artifactId>tomcat-embed-jasper</artifactId>
 | 
			
		||||
    <scope>provided</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--jsp jstl 标签支持-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>javax.servlet</groupId>
 | 
			
		||||
    <artifactId>jstl</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀 
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
spring:
 | 
			
		||||
  mvc:
 | 
			
		||||
    view:
 | 
			
		||||
      prefix: /WEB-INF/jsp/
 | 
			
		||||
      suffix: .jsp
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3  新建controller和show.jsp 测试整合是否成功
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("index")
 | 
			
		||||
public class JspController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping
 | 
			
		||||
    public String jsp(Model model){
 | 
			
		||||
        Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
 | 
			
		||||
        model.addAttribute("programmer",programmer);
 | 
			
		||||
        return "show";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```jsp
 | 
			
		||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <title>programmer</title>
 | 
			
		||||
    <link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<ul>
 | 
			
		||||
    <li>姓名: ${programmer.name}</li>
 | 
			
		||||
    <li>年龄: ${programmer.age}</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,373 +1,396 @@
 | 
			
		||||
# spring boot 整合 kafka
 | 
			
		||||
 | 
			
		||||
## 一、kafka的相关概念:
 | 
			
		||||
 | 
			
		||||
### 1.主题和分区
 | 
			
		||||
 | 
			
		||||
kafka  的消息通过主题进行分类。一个主题可以被分为若干个分区,一个分区就是一个提交日志。消息以追加的方式写入分区,然后以先入先出的顺序读取。kafka通过分区来实现数据的冗余和伸缩性,分区可以分布在不同的服务器上,也就是说一个主题可以横跨多个服务器,以此来提供比单个服务器更强大的性能(类比HDFS分布式文件系统)。
 | 
			
		||||
 | 
			
		||||
注意:由于一个主题包含多个分区,因此无法在整个主题范围内保证消息的顺序性,**但可以保证消息在单个分区内的顺序性**。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka主题和分区.png"/> </div>
 | 
			
		||||
 | 
			
		||||
### 2.分区复制
 | 
			
		||||
 | 
			
		||||
每个主题被分为若干个分区,每个分区有多个副本。那些副本被保存在 broker 上,每个 broker 可以保存成百上千个属于不同主题和分区的副本。副本有以下两种类型 :
 | 
			
		||||
 | 
			
		||||
- 首领副本 每个分区都有一个首领副本 。 为了保证一致性,所有生产者请求和消费者请求都会经过这个副本。
 | 
			
		||||
- 跟随者副本 首领以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,它们唯一的任务就是从首领那里复制消息,保持与首领一致的状态。如果首领发生崩渍,其中的一个跟随者会被提升为新首领。
 | 
			
		||||
 | 
			
		||||
### 3. 生产者
 | 
			
		||||
 | 
			
		||||
- 默认情况下生产者在把消息均衡地分布到在主题的所有分区上,而并不关心特定消息会被写到那个分区;
 | 
			
		||||
- 如果指定消息键,则通过对消息键的散列来实现分区;
 | 
			
		||||
- 也可以通过消息键和分区器来实现把消息直接写到指定的分区,这个需要自定义分区器,需要实现Partitioner 接口,并重写其中的partition方法。
 | 
			
		||||
 | 
			
		||||
### 4. 消费者
 | 
			
		||||
 | 
			
		||||
消费者是**消费者群组**的一部分。也就是说,会有一个或者多个消费者共同读取一个主题,群组保证每个分区只能被一个消费者使用。
 | 
			
		||||
 | 
			
		||||
**一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组里面的多个消费者读取。多个消费者群组可以共同读取同一个主题,彼此之间互不影响**。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka消费者.png"/> </div>
 | 
			
		||||
 | 
			
		||||
### 5.broker和集群
 | 
			
		||||
 | 
			
		||||
一个独立的kafka服务器被称为broker。broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。broker为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘的消息。
 | 
			
		||||
 | 
			
		||||
broker是集群的组成部分。每一个集群都有一个broker同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。控制器负责管理工作,包括将分区分配给broker和监控broker。**在集群中,一个分区从属一个broker,该broker被称为分区的首领**。一个分区可以分配给多个broker,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余,如果有一个broker失效,其他broker可以接管领导权。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka集群复制.png"/> </div>
 | 
			
		||||
 | 
			
		||||
更多kafka 的说明可以参考我的个人笔记:[《Kafka权威指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka/%E3%80%8AKafka%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#53-%E5%A4%8D%E5%88%B6)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
 本项目提供kafka发送简单消息、对象消息、和多消费者组消费消息三种情况下的sample。
 | 
			
		||||
 | 
			
		||||
1. kafkaSimpleConsumer 用于普通消息的监听;
 | 
			
		||||
2. kafkaBeanConsumer 用于对象消息监听;
 | 
			
		||||
3. kafkaGroupConsumer 用于多消费者组和多消费者对主题分区消息监听的情况。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-kafka.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.kafka</groupId>
 | 
			
		||||
    <artifactId>spring-kafka</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.kafka</groupId>
 | 
			
		||||
    <artifactId>spring-kafka-test</artifactId>
 | 
			
		||||
    <scope>test</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、 整合 kafka
 | 
			
		||||
 | 
			
		||||
#### 2.1 kafka基本配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  kafka:
 | 
			
		||||
    # 以逗号分隔的地址列表,用于建立与Kafka集群的初始连接(kafka 默认的端口号为9092)
 | 
			
		||||
    bootstrap-servers: 127.0.0.1:9092
 | 
			
		||||
    producer:
 | 
			
		||||
      # 发生错误后,消息重发的次数。
 | 
			
		||||
      retries: 0
 | 
			
		||||
      #当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
 | 
			
		||||
      batch-size: 16384
 | 
			
		||||
      # 设置生产者内存缓冲区的大小。
 | 
			
		||||
      buffer-memory: 33554432
 | 
			
		||||
      # 键的序列化方式
 | 
			
		||||
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
 | 
			
		||||
      # 值的序列化方式
 | 
			
		||||
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
 | 
			
		||||
      # acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
 | 
			
		||||
      # acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
 | 
			
		||||
      # acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
 | 
			
		||||
      acks: 1
 | 
			
		||||
    consumer:
 | 
			
		||||
      # 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式,如1S,1M,2H,5D
 | 
			
		||||
      auto-commit-interval: 1S
 | 
			
		||||
      # 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
 | 
			
		||||
      # latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
 | 
			
		||||
      # earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
 | 
			
		||||
      auto-offset-reset: earliest
 | 
			
		||||
      # 是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
 | 
			
		||||
      enable-auto-commit: true
 | 
			
		||||
      # 键的反序列化方式
 | 
			
		||||
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
 | 
			
		||||
      # 值的反序列化方式
 | 
			
		||||
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
 | 
			
		||||
    listener:
 | 
			
		||||
      # 在侦听器容器中运行的线程数。
 | 
			
		||||
      concurrency: 5
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
这里需要说明的是:
 | 
			
		||||
 | 
			
		||||
 在spring boot 2.X 版本 auto-commit-interval(自动提交的时间间隔)采用的是值的类型为Duration ,Duration 是 jdk 1.8 版本之后引入的类,在其源码中我们可以看到对于其字符串的表达需要符合一定的规范,即数字+单位,如下的写法1s ,1.5s, 0s, 0.001S ,1h, 2d 在yaml 中都是有效的。如果传入无效的字符串,则spring boot 在启动阶段解析配置文件的时候就会抛出异常。 
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public final class Duration
 | 
			
		||||
        implements TemporalAmount, Comparable<Duration>, Serializable {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The pattern for parsing.
 | 
			
		||||
     */
 | 
			
		||||
    private static final Pattern PATTERN =
 | 
			
		||||
            Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" +
 | 
			
		||||
                    "(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?", Pattern.CASE_INSENSITIVE);
 | 
			
		||||
   
 | 
			
		||||
    ........                 
 | 
			
		||||
 
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 KafkaTemplate实现消息发送
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class KafKaCustomrProducer {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private KafkaTemplate kafkaTemplate;
 | 
			
		||||
 | 
			
		||||
    public void sendMessage(String topic, Object object) {
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         * 这里的ListenableFuture类是spring对java原生Future的扩展增强,是一个泛型接口,用于监听异步方法的回调
 | 
			
		||||
         * 而对于kafka send 方法返回值而言,这里的泛型所代表的实际类型就是 SendResult<K, V>,而这里K,V的泛型实际上
 | 
			
		||||
         * 被用于ProducerRecord<K, V> producerRecord,即生产者发送消息的key,value 类型
 | 
			
		||||
         */
 | 
			
		||||
        ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, object);
 | 
			
		||||
 | 
			
		||||
        future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onFailure(Throwable throwable) {
 | 
			
		||||
                log.info("发送消息失败:" + throwable.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onSuccess(SendResult<String, Object> sendResult) {
 | 
			
		||||
                System.out.println("发送结果:" + sendResult.toString());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3  @KafkaListener注解实现消息的监听
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class KafkaSimpleConsumer {
 | 
			
		||||
 | 
			
		||||
    // 简单消费者
 | 
			
		||||
    @KafkaListener(groupId = "simpleGroup", topics = Topic.SIMPLE)
 | 
			
		||||
    public void consumer1_1(ConsumerRecord<String, Object> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) {
 | 
			
		||||
        System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic);
 | 
			
		||||
        /*
 | 
			
		||||
         * 如果需要手工提交异步 consumer.commitSync();
 | 
			
		||||
         * 手工同步提交 consumer.commitAsync()
 | 
			
		||||
         */
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.4 测试整合结果
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RestController
 | 
			
		||||
public class SendMsgController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private KafKaCustomrProducer producer;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private KafkaTemplate kafkaTemplate;
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 发送消息体为基本类型的消息
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("sendSimple")
 | 
			
		||||
    public void sendSimple() {
 | 
			
		||||
        producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、关于多消费者组的测试
 | 
			
		||||
 | 
			
		||||
#### 3.1  创建多分区主题
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : kafka配置类
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
public class KafkaConfig {
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public NewTopic groupTopic() {
 | 
			
		||||
        // 指定主题名称,分区数量,和复制因子
 | 
			
		||||
        return new NewTopic(Topic.GROUP, 10, (short) 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 多消费者组对同一主题的监听
 | 
			
		||||
 | 
			
		||||
1. 消费者1-1 监听主题的 0、1 分区
 | 
			
		||||
2. 消费者1-2 监听主题的 2、3 分区
 | 
			
		||||
3. 消费者1-3 监听主题的 0、1 分区
 | 
			
		||||
4. 消费者2-1 监听主题的所有分区
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : kafka 消费者组
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class KafkaGroupConsumer {
 | 
			
		||||
 | 
			
		||||
    // 分组1 中的消费者1
 | 
			
		||||
    @KafkaListener(id = "consumer1-1", groupId = "group1", topicPartitions =
 | 
			
		||||
            {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
 | 
			
		||||
            })
 | 
			
		||||
    public void consumer1_1(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.out.println("consumer1-1 收到消息:" + record.value());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 分组1 中的消费者2
 | 
			
		||||
    @KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions =
 | 
			
		||||
            {@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"})
 | 
			
		||||
            })
 | 
			
		||||
    public void consumer1_2(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.out.println("consumer1-2 收到消息:" + record.value());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 分组1 中的消费者3
 | 
			
		||||
    @KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions =
 | 
			
		||||
            {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
 | 
			
		||||
            })
 | 
			
		||||
    public void consumer1_3(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.out.println("consumer1-3 收到消息:" + record.value());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 分组2 中的消费者
 | 
			
		||||
    @KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP)
 | 
			
		||||
    public void consumer2_1(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.err.println("consumer2-1 收到消息:" + record.value());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 发送消息时候指定主题的具体分区
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/***
 | 
			
		||||
 * 多消费者组、组中多消费者对同一主题的消费情况
 | 
			
		||||
 */
 | 
			
		||||
@GetMapping("sendGroup")
 | 
			
		||||
public void sendGroup() {
 | 
			
		||||
    for (int i = 0; i < 4; i++) {
 | 
			
		||||
        // 第二个参数指定分区,第三个参数指定消息键 分区优先
 | 
			
		||||
        ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i);
 | 
			
		||||
        future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onFailure(Throwable throwable) {
 | 
			
		||||
                log.info("发送消息失败:" + throwable.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onSuccess(SendResult<String, Object> sendResult) {
 | 
			
		||||
                System.out.println("发送结果:" + sendResult.toString());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
测试结果:
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
# 主要看每次发送结果中的 partition 属性,代表四次消息分别发送到了主题的0,1,2,3分区
 | 
			
		||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=1, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 1, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-1@13]
 | 
			
		||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=0, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 0, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-0@19]
 | 
			
		||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=3, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 3, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-3@13]
 | 
			
		||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=2, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 2, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-2@13]
 | 
			
		||||
# 消费者组2 接收情况
 | 
			
		||||
consumer2-1 收到消息:hello group 1
 | 
			
		||||
consumer2-1 收到消息:hello group 0
 | 
			
		||||
consumer2-1 收到消息:hello group 2
 | 
			
		||||
consumer2-1 收到消息:hello group 3
 | 
			
		||||
# 消费者1-1接收情况
 | 
			
		||||
consumer1-1 收到消息:hello group 1
 | 
			
		||||
consumer1-1 收到消息:hello group 0
 | 
			
		||||
# 消费者1-3接收情况
 | 
			
		||||
consumer1-3 收到消息:hello group 1
 | 
			
		||||
consumer1-3 收到消息:hello group 0
 | 
			
		||||
# 消费者1-2接收情况
 | 
			
		||||
consumer1-2 收到消息:hello group 3
 | 
			
		||||
consumer1-2 收到消息:hello group 2
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.4 测试结果
 | 
			
		||||
 | 
			
		||||
1. 和kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响;
 | 
			
		||||
2. 和kafka原本机制不一样的是,这里我们消费者1-1和消费1-3共同属于同一个消费者组,并且监听同样的分区,按照原本kafka的机制,群组保证每个分区只能被同一个消费者组的一个消费者使用,但是按照spring的声明方式实现的消息监听,这里被两个消费者都监听到了。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、序列化与反序列化
 | 
			
		||||
 | 
			
		||||
用例采用的是第三方fastjson将实体类序列化为json后发送。实现如下:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/***
 | 
			
		||||
 * 发送消息体为bean的消息
 | 
			
		||||
 */
 | 
			
		||||
@GetMapping("sendBean")
 | 
			
		||||
public void sendBean() {
 | 
			
		||||
    Programmer programmer = new Programmer("xiaoming", 12, 21212.33f, new Date());
 | 
			
		||||
    producer.sendMessage(Topic.BEAN, JSON.toJSON(programmer).toString());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class KafkaBeanConsumer {
 | 
			
		||||
 | 
			
		||||
    @KafkaListener(groupId = "beanGroup",topics = Topic.BEAN)
 | 
			
		||||
    public void consumer(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.out.println("消费者收到消息:" + JSON.parseObject(record.value().toString(), Programmer.class));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# spring boot 整合 kafka
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一kafka的相关概念">一、kafka的相关概念:</a><br/>
 | 
			
		||||
    <a href="#1主题和分区">1.主题和分区</a><br/>
 | 
			
		||||
    <a href="#2分区复制">2.分区复制</a><br/>
 | 
			
		||||
    <a href="#3-生产者">3. 生产者</a><br/>
 | 
			
		||||
    <a href="#4-消费者">4. 消费者</a><br/>
 | 
			
		||||
    <a href="#5broker和集群">5.broker和集群</a><br/>
 | 
			
		||||
<a href="#二项目说明">二、项目说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
 | 
			
		||||
        <a href="#12-主要依赖">1.2 主要依赖</a><br/>
 | 
			
		||||
<a href="#二-整合-kafka">二、 整合 kafka</a><br/>
 | 
			
		||||
        <a href="#21-kafka基本配置">2.1 kafka基本配置</a><br/>
 | 
			
		||||
        <a href="#22-KafkaTemplate实现消息发送">2.2 KafkaTemplate实现消息发送</a><br/>
 | 
			
		||||
        <a href="#23--@KafkaListener注解实现消息的监听">2.3  @KafkaListener注解实现消息的监听</a><br/>
 | 
			
		||||
        <a href="#24-测试整合结果">2.4 测试整合结果</a><br/>
 | 
			
		||||
<a href="#三关于多消费者组的测试">三、关于多消费者组的测试</a><br/>
 | 
			
		||||
        <a href="#31--创建多分区主题">3.1  创建多分区主题</a><br/>
 | 
			
		||||
        <a href="#32-多消费者组对同一主题的监听">3.2 多消费者组对同一主题的监听</a><br/>
 | 
			
		||||
        <a href="#32-发送消息时候指定主题的具体分区">3.2 发送消息时候指定主题的具体分区</a><br/>
 | 
			
		||||
        <a href="#34-测试结果">3.4 测试结果</a><br/>
 | 
			
		||||
<a href="#四序列化与反序列化">四、序列化与反序列化</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、kafka的相关概念:
 | 
			
		||||
 | 
			
		||||
### 1.主题和分区
 | 
			
		||||
 | 
			
		||||
kafka  的消息通过主题进行分类。一个主题可以被分为若干个分区,一个分区就是一个提交日志。消息以追加的方式写入分区,然后以先入先出的顺序读取。kafka通过分区来实现数据的冗余和伸缩性,分区可以分布在不同的服务器上,也就是说一个主题可以横跨多个服务器,以此来提供比单个服务器更强大的性能(类比HDFS分布式文件系统)。
 | 
			
		||||
 | 
			
		||||
注意:由于一个主题包含多个分区,因此无法在整个主题范围内保证消息的顺序性,**但可以保证消息在单个分区内的顺序性**。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka主题和分区.png"/> </div>
 | 
			
		||||
 | 
			
		||||
### 2.分区复制
 | 
			
		||||
 | 
			
		||||
每个主题被分为若干个分区,每个分区有多个副本。那些副本被保存在 broker 上,每个 broker 可以保存成百上千个属于不同主题和分区的副本。副本有以下两种类型 :
 | 
			
		||||
 | 
			
		||||
- 首领副本 每个分区都有一个首领副本 。 为了保证一致性,所有生产者请求和消费者请求都会经过这个副本。
 | 
			
		||||
- 跟随者副本 首领以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,它们唯一的任务就是从首领那里复制消息,保持与首领一致的状态。如果首领发生崩渍,其中的一个跟随者会被提升为新首领。
 | 
			
		||||
 | 
			
		||||
### 3. 生产者
 | 
			
		||||
 | 
			
		||||
- 默认情况下生产者在把消息均衡地分布到在主题的所有分区上,而并不关心特定消息会被写到那个分区;
 | 
			
		||||
- 如果指定消息键,则通过对消息键的散列来实现分区;
 | 
			
		||||
- 也可以通过消息键和分区器来实现把消息直接写到指定的分区,这个需要自定义分区器,需要实现Partitioner 接口,并重写其中的partition方法。
 | 
			
		||||
 | 
			
		||||
### 4. 消费者
 | 
			
		||||
 | 
			
		||||
消费者是**消费者群组**的一部分。也就是说,会有一个或者多个消费者共同读取一个主题,群组保证每个分区只能被一个消费者使用。
 | 
			
		||||
 | 
			
		||||
**一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组里面的多个消费者读取。多个消费者群组可以共同读取同一个主题,彼此之间互不影响**。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka消费者.png"/> </div>
 | 
			
		||||
 | 
			
		||||
### 5.broker和集群
 | 
			
		||||
 | 
			
		||||
一个独立的kafka服务器被称为broker。broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。broker为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘的消息。
 | 
			
		||||
 | 
			
		||||
broker是集群的组成部分。每一个集群都有一个broker同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。控制器负责管理工作,包括将分区分配给broker和监控broker。**在集群中,一个分区从属一个broker,该broker被称为分区的首领**。一个分区可以分配给多个broker,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余,如果有一个broker失效,其他broker可以接管领导权。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka集群复制.png"/> </div>
 | 
			
		||||
 | 
			
		||||
更多kafka 的说明可以参考我的个人笔记:[《Kafka权威指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka/%E3%80%8AKafka%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#53-%E5%A4%8D%E5%88%B6)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
 本项目提供kafka发送简单消息、对象消息、和多消费者组消费消息三种情况下的sample。
 | 
			
		||||
 | 
			
		||||
1. kafkaSimpleConsumer 用于普通消息的监听;
 | 
			
		||||
2. kafkaBeanConsumer 用于对象消息监听;
 | 
			
		||||
3. kafkaGroupConsumer 用于多消费者组和多消费者对主题分区消息监听的情况。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-kafka.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.kafka</groupId>
 | 
			
		||||
    <artifactId>spring-kafka</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.kafka</groupId>
 | 
			
		||||
    <artifactId>spring-kafka-test</artifactId>
 | 
			
		||||
    <scope>test</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、 整合 kafka
 | 
			
		||||
 | 
			
		||||
#### 2.1 kafka基本配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  kafka:
 | 
			
		||||
    # 以逗号分隔的地址列表,用于建立与Kafka集群的初始连接(kafka 默认的端口号为9092)
 | 
			
		||||
    bootstrap-servers: 127.0.0.1:9092
 | 
			
		||||
    producer:
 | 
			
		||||
      # 发生错误后,消息重发的次数。
 | 
			
		||||
      retries: 0
 | 
			
		||||
      #当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
 | 
			
		||||
      batch-size: 16384
 | 
			
		||||
      # 设置生产者内存缓冲区的大小。
 | 
			
		||||
      buffer-memory: 33554432
 | 
			
		||||
      # 键的序列化方式
 | 
			
		||||
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
 | 
			
		||||
      # 值的序列化方式
 | 
			
		||||
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
 | 
			
		||||
      # acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
 | 
			
		||||
      # acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
 | 
			
		||||
      # acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
 | 
			
		||||
      acks: 1
 | 
			
		||||
    consumer:
 | 
			
		||||
      # 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式,如1S,1M,2H,5D
 | 
			
		||||
      auto-commit-interval: 1S
 | 
			
		||||
      # 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
 | 
			
		||||
      # latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
 | 
			
		||||
      # earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
 | 
			
		||||
      auto-offset-reset: earliest
 | 
			
		||||
      # 是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
 | 
			
		||||
      enable-auto-commit: true
 | 
			
		||||
      # 键的反序列化方式
 | 
			
		||||
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
 | 
			
		||||
      # 值的反序列化方式
 | 
			
		||||
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
 | 
			
		||||
    listener:
 | 
			
		||||
      # 在侦听器容器中运行的线程数。
 | 
			
		||||
      concurrency: 5
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
这里需要说明的是:
 | 
			
		||||
 | 
			
		||||
 在spring boot 2.X 版本 auto-commit-interval(自动提交的时间间隔)采用的是值的类型为Duration ,Duration 是 jdk 1.8 版本之后引入的类,在其源码中我们可以看到对于其字符串的表达需要符合一定的规范,即数字+单位,如下的写法1s ,1.5s, 0s, 0.001S ,1h, 2d 在yaml 中都是有效的。如果传入无效的字符串,则spring boot 在启动阶段解析配置文件的时候就会抛出异常。 
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public final class Duration
 | 
			
		||||
        implements TemporalAmount, Comparable<Duration>, Serializable {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The pattern for parsing.
 | 
			
		||||
     */
 | 
			
		||||
    private static final Pattern PATTERN =
 | 
			
		||||
            Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" +
 | 
			
		||||
                    "(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?", Pattern.CASE_INSENSITIVE);
 | 
			
		||||
   
 | 
			
		||||
    ........                 
 | 
			
		||||
 
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 KafkaTemplate实现消息发送
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class KafKaCustomrProducer {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private KafkaTemplate kafkaTemplate;
 | 
			
		||||
 | 
			
		||||
    public void sendMessage(String topic, Object object) {
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         * 这里的ListenableFuture类是spring对java原生Future的扩展增强,是一个泛型接口,用于监听异步方法的回调
 | 
			
		||||
         * 而对于kafka send 方法返回值而言,这里的泛型所代表的实际类型就是 SendResult<K, V>,而这里K,V的泛型实际上
 | 
			
		||||
         * 被用于ProducerRecord<K, V> producerRecord,即生产者发送消息的key,value 类型
 | 
			
		||||
         */
 | 
			
		||||
        ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, object);
 | 
			
		||||
 | 
			
		||||
        future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onFailure(Throwable throwable) {
 | 
			
		||||
                log.info("发送消息失败:" + throwable.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onSuccess(SendResult<String, Object> sendResult) {
 | 
			
		||||
                System.out.println("发送结果:" + sendResult.toString());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3  @KafkaListener注解实现消息的监听
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class KafkaSimpleConsumer {
 | 
			
		||||
 | 
			
		||||
    // 简单消费者
 | 
			
		||||
    @KafkaListener(groupId = "simpleGroup", topics = Topic.SIMPLE)
 | 
			
		||||
    public void consumer1_1(ConsumerRecord<String, Object> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) {
 | 
			
		||||
        System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic);
 | 
			
		||||
        /*
 | 
			
		||||
         * 如果需要手工提交异步 consumer.commitSync();
 | 
			
		||||
         * 手工同步提交 consumer.commitAsync()
 | 
			
		||||
         */
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.4 测试整合结果
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RestController
 | 
			
		||||
public class SendMsgController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private KafKaCustomrProducer producer;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private KafkaTemplate kafkaTemplate;
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 发送消息体为基本类型的消息
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("sendSimple")
 | 
			
		||||
    public void sendSimple() {
 | 
			
		||||
        producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、关于多消费者组的测试
 | 
			
		||||
 | 
			
		||||
#### 3.1  创建多分区主题
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : kafka配置类
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
public class KafkaConfig {
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public NewTopic groupTopic() {
 | 
			
		||||
        // 指定主题名称,分区数量,和复制因子
 | 
			
		||||
        return new NewTopic(Topic.GROUP, 10, (short) 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 多消费者组对同一主题的监听
 | 
			
		||||
 | 
			
		||||
1. 消费者1-1 监听主题的 0、1 分区
 | 
			
		||||
2. 消费者1-2 监听主题的 2、3 分区
 | 
			
		||||
3. 消费者1-3 监听主题的 0、1 分区
 | 
			
		||||
4. 消费者2-1 监听主题的所有分区
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : kafka 消费者组
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class KafkaGroupConsumer {
 | 
			
		||||
 | 
			
		||||
    // 分组1 中的消费者1
 | 
			
		||||
    @KafkaListener(id = "consumer1-1", groupId = "group1", topicPartitions =
 | 
			
		||||
            {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
 | 
			
		||||
            })
 | 
			
		||||
    public void consumer1_1(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.out.println("consumer1-1 收到消息:" + record.value());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 分组1 中的消费者2
 | 
			
		||||
    @KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions =
 | 
			
		||||
            {@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"})
 | 
			
		||||
            })
 | 
			
		||||
    public void consumer1_2(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.out.println("consumer1-2 收到消息:" + record.value());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 分组1 中的消费者3
 | 
			
		||||
    @KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions =
 | 
			
		||||
            {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
 | 
			
		||||
            })
 | 
			
		||||
    public void consumer1_3(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.out.println("consumer1-3 收到消息:" + record.value());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 分组2 中的消费者
 | 
			
		||||
    @KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP)
 | 
			
		||||
    public void consumer2_1(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.err.println("consumer2-1 收到消息:" + record.value());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 发送消息时候指定主题的具体分区
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/***
 | 
			
		||||
 * 多消费者组、组中多消费者对同一主题的消费情况
 | 
			
		||||
 */
 | 
			
		||||
@GetMapping("sendGroup")
 | 
			
		||||
public void sendGroup() {
 | 
			
		||||
    for (int i = 0; i < 4; i++) {
 | 
			
		||||
        // 第二个参数指定分区,第三个参数指定消息键 分区优先
 | 
			
		||||
        ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i);
 | 
			
		||||
        future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onFailure(Throwable throwable) {
 | 
			
		||||
                log.info("发送消息失败:" + throwable.getMessage());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void onSuccess(SendResult<String, Object> sendResult) {
 | 
			
		||||
                System.out.println("发送结果:" + sendResult.toString());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
测试结果:
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
# 主要看每次发送结果中的 partition 属性,代表四次消息分别发送到了主题的0,1,2,3分区
 | 
			
		||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=1, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 1, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-1@13]
 | 
			
		||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=0, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 0, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-0@19]
 | 
			
		||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=3, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 3, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-3@13]
 | 
			
		||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=2, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 2, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-2@13]
 | 
			
		||||
# 消费者组2 接收情况
 | 
			
		||||
consumer2-1 收到消息:hello group 1
 | 
			
		||||
consumer2-1 收到消息:hello group 0
 | 
			
		||||
consumer2-1 收到消息:hello group 2
 | 
			
		||||
consumer2-1 收到消息:hello group 3
 | 
			
		||||
# 消费者1-1接收情况
 | 
			
		||||
consumer1-1 收到消息:hello group 1
 | 
			
		||||
consumer1-1 收到消息:hello group 0
 | 
			
		||||
# 消费者1-3接收情况
 | 
			
		||||
consumer1-3 收到消息:hello group 1
 | 
			
		||||
consumer1-3 收到消息:hello group 0
 | 
			
		||||
# 消费者1-2接收情况
 | 
			
		||||
consumer1-2 收到消息:hello group 3
 | 
			
		||||
consumer1-2 收到消息:hello group 2
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.4 测试结果
 | 
			
		||||
 | 
			
		||||
1. 和kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响;
 | 
			
		||||
2. 和kafka原本机制不一样的是,这里我们消费者1-1和消费1-3共同属于同一个消费者组,并且监听同样的分区,按照原本kafka的机制,群组保证每个分区只能被同一个消费者组的一个消费者使用,但是按照spring的声明方式实现的消息监听,这里被两个消费者都监听到了。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、序列化与反序列化
 | 
			
		||||
 | 
			
		||||
用例采用的是第三方fastjson将实体类序列化为json后发送。实现如下:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/***
 | 
			
		||||
 * 发送消息体为bean的消息
 | 
			
		||||
 */
 | 
			
		||||
@GetMapping("sendBean")
 | 
			
		||||
public void sendBean() {
 | 
			
		||||
    Programmer programmer = new Programmer("xiaoming", 12, 21212.33f, new Date());
 | 
			
		||||
    producer.sendMessage(Topic.BEAN, JSON.toJSON(programmer).toString());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class KafkaBeanConsumer {
 | 
			
		||||
 | 
			
		||||
    @KafkaListener(groupId = "beanGroup",topics = Topic.BEAN)
 | 
			
		||||
    public void consumer(ConsumerRecord<String, Object> record) {
 | 
			
		||||
        System.out.println("消费者收到消息:" + JSON.parseObject(record.value().toString(), Programmer.class));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,148 +1,161 @@
 | 
			
		||||
# spring boot 整合 mecached
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
### 1.1  XMemcached客户端说明
 | 
			
		||||
 | 
			
		||||
spring boot 官方并没有提供关于 memcached 的starter,所以我们这里还是采用XMemcached作为客户端进行整合。 XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
 | 
			
		||||
 | 
			
		||||
### 1.2 项目结构说明
 | 
			
		||||
 | 
			
		||||
memcached的整合配置位于config文件夹下。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-memcached.png"/> </div>
 | 
			
		||||
 | 
			
		||||
### 1.3 主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
 <!--memcached java 客户端-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>com.googlecode.xmemcached</groupId>
 | 
			
		||||
    <artifactId>xmemcached</artifactId>
 | 
			
		||||
    <version>2.4.5</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
# spring boot 整合 mecached
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
    <a href="#11--XMemcached客户端说明">1.1  XMemcached客户端说明</a><br/>
 | 
			
		||||
    <a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
 | 
			
		||||
    <a href="#13-主要依赖">1.3 主要依赖</a><br/>
 | 
			
		||||
<a href="#二spring-boot-整合-memcached">二、spring boot 整合 memcached</a><br/>
 | 
			
		||||
        <a href="#21-单机配置">2.1 单机配置</a><br/>
 | 
			
		||||
        <a href="#22-集群配置">2.2 集群配置</a><br/>
 | 
			
		||||
        <a href="#23-存储基本类型测试用例">2.3 存储基本类型测试用例</a><br/>
 | 
			
		||||
        <a href="#25-存储实体对象测试用例">2.5 存储实体对象测试用例</a><br/>
 | 
			
		||||
<a href="#附memcached-基本命令">附:memcached 基本命令</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、spring boot 整合 memcached
 | 
			
		||||
 | 
			
		||||
#### 2.1 单机配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Bean
 | 
			
		||||
public MemcachedClient memcachedClient() {
 | 
			
		||||
    XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
 | 
			
		||||
    MemcachedClient memcachedClient = null;
 | 
			
		||||
try {
 | 
			
		||||
	memcachedClient = builder.build();
 | 
			
		||||
	} catch (IOException e) {
 | 
			
		||||
	e.printStackTrace();
 | 
			
		||||
}
 | 
			
		||||
	return memcachedClient;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 集群配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Bean
 | 
			
		||||
public MemcachedClient memcachedClientForCluster() {
 | 
			
		||||
 | 
			
		||||
    List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
 | 
			
		||||
    addressList.add(new InetSocketAddress("192.168.200.201", 11211));
 | 
			
		||||
    addressList.add(new InetSocketAddress("192.168.200.201", 11212));
 | 
			
		||||
    // 赋予权重
 | 
			
		||||
    int[] weights = {1, 2};
 | 
			
		||||
    XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
 | 
			
		||||
    // 设置连接池大小
 | 
			
		||||
    builder.setConnectionPoolSize(10);
 | 
			
		||||
    // 协议工厂
 | 
			
		||||
    builder.setCommandFactory(new TextCommandFactory());
 | 
			
		||||
    // 分布策略,一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
 | 
			
		||||
    builder.setSessionLocator(new KetamaMemcachedSessionLocator());
 | 
			
		||||
    // 设置序列化器
 | 
			
		||||
    builder.setTranscoder(new SerializingTranscoder());
 | 
			
		||||
    MemcachedClient memcachedClient = null;
 | 
			
		||||
    try {
 | 
			
		||||
        memcachedClient = builder.build();
 | 
			
		||||
    } catch (IOException e) {
 | 
			
		||||
        e.printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
    return memcachedClient;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 存储基本类型测试用例
 | 
			
		||||
 | 
			
		||||
xmemcached单机版本和集群版本注入的实例是相同的。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : Memcached 操作基本对象
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MemSamples {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private MemcachedClient memcachedClient;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void operate() throws InterruptedException, MemcachedException, TimeoutException {
 | 
			
		||||
        memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
 | 
			
		||||
        String value = memcachedClient.get("hello");
 | 
			
		||||
        System.out.println("hello=" + value);
 | 
			
		||||
        memcachedClient.delete("hello");
 | 
			
		||||
        value = memcachedClient.get("hello");
 | 
			
		||||
        System.out.println("hello=" + value);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.5 存储实体对象测试用例
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description :Memcached 序列化与反序列化
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MemObjectSamples {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private MemcachedClient memcachedClient;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void operate() throws InterruptedException, MemcachedException, TimeoutException {
 | 
			
		||||
        memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
 | 
			
		||||
        Programmer programmer = memcachedClient.get("programmer");
 | 
			
		||||
        System.out.println("hello ," + programmer.getName());
 | 
			
		||||
        memcachedClient.delete("programmer");
 | 
			
		||||
        programmer = memcachedClient.get("programmer");
 | 
			
		||||
        Assert.assertNull(programmer);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 附:memcached 基本命令
 | 
			
		||||
 | 
			
		||||
| 命令            | 格式                                               | 说明                                  |
 | 
			
		||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
 | 
			
		||||
| 新增 set        | set  key  flags   exTime  length -> value          | 无论什么情况,都可以插入              |
 | 
			
		||||
| 新增 add        | add key  flags   exTime  length -> value           | 只有当key不存在的情况下,才可以插入   |
 | 
			
		||||
| 替换 replace    | replace  key  flags   exTime  length -> value      | 只修改已存在key的value值              |
 | 
			
		||||
| 追加内容append  | append  key  flags   exTime  length -> value       | length表示追加的长度而不是总长度      |
 | 
			
		||||
| 前面追加prepend | prepend  key  flags   exTime  length -> value      | length表示追加的长度而不是总长度      |
 | 
			
		||||
| 查询操作 get    | get  key                                           |                                       |
 | 
			
		||||
| 检查更新 cas    | cas  key  flags  exTime  length  version  -> value | 版本正确才更新                        |
 | 
			
		||||
| 详细获取 gets   | gets   key                                         | 返回的最后一个数代表 key 的 CAS 令牌  |
 | 
			
		||||
| 删除 delete     | delete   key                                       | 将数据打一个删除标记                  |
 | 
			
		||||
| 自增 incr       | incr  key  增加偏移量                              | incr和decr只能操作能转换为数字的Value |
 | 
			
		||||
| 自减 decr       | decr  key  减少偏移量                              | desr不能将数字减少至0以下             |
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
### 1.1  XMemcached客户端说明
 | 
			
		||||
 | 
			
		||||
spring boot 官方并没有提供关于 memcached 的starter,所以我们这里还是采用XMemcached作为客户端进行整合。 XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
 | 
			
		||||
 | 
			
		||||
### 1.2 项目结构说明
 | 
			
		||||
 | 
			
		||||
memcached的整合配置位于config文件夹下。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-memcached.png"/> </div>
 | 
			
		||||
 | 
			
		||||
### 1.3 主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
 <!--memcached java 客户端-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>com.googlecode.xmemcached</groupId>
 | 
			
		||||
    <artifactId>xmemcached</artifactId>
 | 
			
		||||
    <version>2.4.5</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、spring boot 整合 memcached
 | 
			
		||||
 | 
			
		||||
#### 2.1 单机配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Bean
 | 
			
		||||
public MemcachedClient memcachedClient() {
 | 
			
		||||
    XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
 | 
			
		||||
    MemcachedClient memcachedClient = null;
 | 
			
		||||
try {
 | 
			
		||||
	memcachedClient = builder.build();
 | 
			
		||||
	} catch (IOException e) {
 | 
			
		||||
	e.printStackTrace();
 | 
			
		||||
}
 | 
			
		||||
	return memcachedClient;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 集群配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Bean
 | 
			
		||||
public MemcachedClient memcachedClientForCluster() {
 | 
			
		||||
 | 
			
		||||
    List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
 | 
			
		||||
    addressList.add(new InetSocketAddress("192.168.200.201", 11211));
 | 
			
		||||
    addressList.add(new InetSocketAddress("192.168.200.201", 11212));
 | 
			
		||||
    // 赋予权重
 | 
			
		||||
    int[] weights = {1, 2};
 | 
			
		||||
    XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
 | 
			
		||||
    // 设置连接池大小
 | 
			
		||||
    builder.setConnectionPoolSize(10);
 | 
			
		||||
    // 协议工厂
 | 
			
		||||
    builder.setCommandFactory(new TextCommandFactory());
 | 
			
		||||
    // 分布策略,一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
 | 
			
		||||
    builder.setSessionLocator(new KetamaMemcachedSessionLocator());
 | 
			
		||||
    // 设置序列化器
 | 
			
		||||
    builder.setTranscoder(new SerializingTranscoder());
 | 
			
		||||
    MemcachedClient memcachedClient = null;
 | 
			
		||||
    try {
 | 
			
		||||
        memcachedClient = builder.build();
 | 
			
		||||
    } catch (IOException e) {
 | 
			
		||||
        e.printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
    return memcachedClient;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 存储基本类型测试用例
 | 
			
		||||
 | 
			
		||||
xmemcached单机版本和集群版本注入的实例是相同的。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : Memcached 操作基本对象
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MemSamples {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private MemcachedClient memcachedClient;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void operate() throws InterruptedException, MemcachedException, TimeoutException {
 | 
			
		||||
        memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
 | 
			
		||||
        String value = memcachedClient.get("hello");
 | 
			
		||||
        System.out.println("hello=" + value);
 | 
			
		||||
        memcachedClient.delete("hello");
 | 
			
		||||
        value = memcachedClient.get("hello");
 | 
			
		||||
        System.out.println("hello=" + value);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.5 存储实体对象测试用例
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description :Memcached 序列化与反序列化
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MemObjectSamples {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private MemcachedClient memcachedClient;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void operate() throws InterruptedException, MemcachedException, TimeoutException {
 | 
			
		||||
        memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
 | 
			
		||||
        Programmer programmer = memcachedClient.get("programmer");
 | 
			
		||||
        System.out.println("hello ," + programmer.getName());
 | 
			
		||||
        memcachedClient.delete("programmer");
 | 
			
		||||
        programmer = memcachedClient.get("programmer");
 | 
			
		||||
        Assert.assertNull(programmer);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 附:memcached 基本命令
 | 
			
		||||
 | 
			
		||||
| 命令            | 格式                                               | 说明                                  |
 | 
			
		||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
 | 
			
		||||
| 新增 set        | set  key  flags   exTime  length -> value          | 无论什么情况,都可以插入              |
 | 
			
		||||
| 新增 add        | add key  flags   exTime  length -> value           | 只有当key不存在的情况下,才可以插入   |
 | 
			
		||||
| 替换 replace    | replace  key  flags   exTime  length -> value      | 只修改已存在key的value值              |
 | 
			
		||||
| 追加内容append  | append  key  flags   exTime  length -> value       | length表示追加的长度而不是总长度      |
 | 
			
		||||
| 前面追加prepend | prepend  key  flags   exTime  length -> value      | length表示追加的长度而不是总长度      |
 | 
			
		||||
| 查询操作 get    | get  key                                           |                                       |
 | 
			
		||||
| 检查更新 cas    | cas  key  flags  exTime  length  version  -> value | 版本正确才更新                        |
 | 
			
		||||
| 详细获取 gets   | gets   key                                         | 返回的最后一个数代表 key 的 CAS 令牌  |
 | 
			
		||||
| 删除 delete     | delete   key                                       | 将数据打一个删除标记                  |
 | 
			
		||||
| 自增 incr       | incr  key  增加偏移量                              | incr和decr只能操作能转换为数字的Value |
 | 
			
		||||
| 自减 decr       | decr  key  减少偏移量                              | desr不能将数字减少至0以下             |
 | 
			
		||||
@@ -1,182 +1,192 @@
 | 
			
		||||
# spring boot 整合 mongodb
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 用例结构
 | 
			
		||||
 | 
			
		||||
1. 本用例提供mongdb的简单整合用例;
 | 
			
		||||
2. 提供用MongoTemplate的方式操作mongdb,见测试用例MongoOriginalTests.java
 | 
			
		||||
3. 提供基于spring data jpa 的方式操作mongodb(推荐),见测试用例MongoJPATests.java
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mongodb.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
# spring boot 整合 mongodb
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
        <a href="#11-用例结构">1.1 用例结构</a><br/>
 | 
			
		||||
        <a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
 | 
			
		||||
<a href="#二整合-mongodb">二、整合 mongodb</a><br/>
 | 
			
		||||
        <a href="#21-在applicationyml-中配置mongodb数据源">2.1 在application.yml 中配置mongodb数据源</a><br/>
 | 
			
		||||
        <a href="#22--基于MongoTemplate实现对mongodb的操作">2.2  基于MongoTemplate实现对mongodb的操作</a><br/>
 | 
			
		||||
        <a href="#23-使用-data-jpa-方式操作mongodb-推荐使用">2.3 使用 data jpa 方式操作mongodb (推荐使用)</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、整合 mongodb
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置mongodb数据源
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  data:
 | 
			
		||||
    mongodb:
 | 
			
		||||
      database: spring
 | 
			
		||||
      uri: mongodb://192.168.0.108:27017
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2  基于MongoTemplate实现对mongodb的操作
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MongoOriginalTests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private MongoTemplate mongoTemplate;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void insert() {
 | 
			
		||||
        // 单条插入
 | 
			
		||||
        mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
 | 
			
		||||
        List<Programmer> programmers = new ArrayList<Programmer>();
 | 
			
		||||
        // 批量插入
 | 
			
		||||
        programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
 | 
			
		||||
        mongoTemplate.insert(programmers, Programmer.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 条件查询
 | 
			
		||||
    @Test
 | 
			
		||||
    public void select() {
 | 
			
		||||
        Criteria criteria = new Criteria();
 | 
			
		||||
        criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
 | 
			
		||||
        Query query = new Query(criteria);
 | 
			
		||||
        Programmer one = mongoTemplate.findOne(query, Programmer.class);
 | 
			
		||||
        System.out.println(one);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // 更新数据
 | 
			
		||||
    @Test
 | 
			
		||||
    public void MUpdate() {
 | 
			
		||||
        UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
 | 
			
		||||
        System.out.println("更新记录数:" + updateResult.getModifiedCount());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 删除指定数据
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
 | 
			
		||||
        System.out.println("影响记录数:" + result.getDeletedCount());
 | 
			
		||||
        System.out.println("是否成功:" + result.wasAcknowledged());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 使用 data jpa 方式操作mongodb (推荐使用)
 | 
			
		||||
 | 
			
		||||
1.新建查询结构,查询方法按照支持的关键字命名
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public interface ProgrammerRepository extends MongoRepository<Programmer, String> {
 | 
			
		||||
 | 
			
		||||
    void deleteAllByName(String name);
 | 
			
		||||
 | 
			
		||||
    Programmer findAllByName(String names);
 | 
			
		||||
 | 
			
		||||
    Programmer findByNameAndAge(String name, int age);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2.测试
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MongoJPATests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgrammerRepository repository;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void insert() {
 | 
			
		||||
        // 单条插入
 | 
			
		||||
        repository.save(new Programmer("python", 23, 21832.34f, new Date()));
 | 
			
		||||
        // 批量插入
 | 
			
		||||
        List<Programmer> programmers = new ArrayList<Programmer>();
 | 
			
		||||
        programmers.add(new Programmer("java", 21, 52200.21f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
 | 
			
		||||
        repository.saveAll(programmers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 条件查询
 | 
			
		||||
    @Test
 | 
			
		||||
    public void select() {
 | 
			
		||||
        Programmer java = repository.findByNameAndAge("java", 21);
 | 
			
		||||
        Assert.assertEquals(java.getSalary(), 52200.21f, 0.01);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // 更新数据
 | 
			
		||||
    @Test
 | 
			
		||||
    public void MUpdate() {
 | 
			
		||||
        repository.save(new Programmer("Go", 8, 500.21f, new Date()));
 | 
			
		||||
        Programmer go = repository.findAllByName("Go");
 | 
			
		||||
        Assert.assertEquals(go.getAge(), 8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 删除指定数据
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        repository.deleteAllByName("python");
 | 
			
		||||
        Optional<Programmer> python = repository.findById("python");
 | 
			
		||||
        Assert.assertFalse(python.isPresent());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
查询方法支持的关键字如下,更多命名规范可以参见Spring Data MongoDB官方文档[Query Methods](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongodb.repositories.queries):
 | 
			
		||||
 | 
			
		||||
| Keyword                              | Sample                                                       | Logical result                                               |
 | 
			
		||||
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| `After`                              | `findByBirthdateAfter(Date date)`                            | `{"birthdate" : {"$gt" : date}}`                             |
 | 
			
		||||
| `GreaterThan`                        | `findByAgeGreaterThan(int age)`                              | `{"age" : {"$gt" : age}}`                                    |
 | 
			
		||||
| `GreaterThanEqual`                   | `findByAgeGreaterThanEqual(int age)`                         | `{"age" : {"$gte" : age}}`                                   |
 | 
			
		||||
| `Before`                             | `findByBirthdateBefore(Date date)`                           | `{"birthdate" : {"$lt" : date}}`                             |
 | 
			
		||||
| `LessThan`                           | `findByAgeLessThan(int age)`                                 | `{"age" : {"$lt" : age}}`                                    |
 | 
			
		||||
| `LessThanEqual`                      | `findByAgeLessThanEqual(int age)`                            | `{"age" : {"$lte" : age}}`                                   |
 | 
			
		||||
| `Between`                            | `findByAgeBetween(int from, int to)`                         | `{"age" : {"$gt" : from, "$lt" : to}}`                       |
 | 
			
		||||
| `In`                                 | `findByAgeIn(Collection ages)`                               | `{"age" : {"$in" : [ages…]}}`                                |
 | 
			
		||||
| `NotIn`                              | `findByAgeNotIn(Collection ages)`                            | `{"age" : {"$nin" : [ages…]}}`                               |
 | 
			
		||||
| `IsNotNull`, `NotNull`               | `findByFirstnameNotNull()`                                   | `{"firstname" : {"$ne" : null}}`                             |
 | 
			
		||||
| `IsNull`, `Null`                     | `findByFirstnameNull()`                                      | `{"firstname" : null}`                                       |
 | 
			
		||||
| `Like`, `StartingWith`, `EndingWith` | `findByFirstnameLike(String name)`                           | `{"firstname" : name} (name as regex)`                       |
 | 
			
		||||
| `NotLike`, `IsNotLike`               | `findByFirstnameNotLike(String name)`                        | `{"firstname" : { "$not" : name }} (name as regex)`          |
 | 
			
		||||
| `Containing` on String               | `findByFirstnameContaining(String name)`                     | `{"firstname" : name} (name as regex)`                       |
 | 
			
		||||
| `NotContaining` on String            | `findByFirstnameNotContaining(String name)`                  | `{"firstname" : { "$not" : name}} (name as regex)`           |
 | 
			
		||||
| `Containing` on Collection           | `findByAddressesContaining(Address address)`                 | `{"addresses" : { "$in" : address}}`                         |
 | 
			
		||||
| `NotContaining` on Collection        | `findByAddressesNotContaining(Address address)`              | `{"addresses" : { "$not" : { "$in" : address}}}`             |
 | 
			
		||||
| `Regex`                              | `findByFirstnameRegex(String firstname)`                     | `{"firstname" : {"$regex" : firstname }}`                    |
 | 
			
		||||
| `(No keyword)`                       | `findByFirstname(String name)`                               | `{"firstname" : name}`                                       |
 | 
			
		||||
| `Not`                                | `findByFirstnameNot(String name)`                            | `{"firstname" : {"$ne" : name}}`                             |
 | 
			
		||||
| `Near`                               | `findByLocationNear(Point point)`                            | `{"location" : {"$near" : [x,y]}}`                           |
 | 
			
		||||
| `Near`                               | `findByLocationNear(Point point, Distance max)`              | `{"location" : {"$near" : [x,y], "$maxDistance" : max}}`     |
 | 
			
		||||
| `Near`                               | `findByLocationNear(Point point, Distance min, Distance max)` | `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}` |
 | 
			
		||||
| `Within`                             | `findByLocationWithin(Circle circle)`                        | `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}` |
 | 
			
		||||
| `Within`                             | `findByLocationWithin(Box box)`                              | `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}` |
 | 
			
		||||
| `IsTrue`, `True`                     | `findByActiveIsTrue()`                                       | `{"active" : true}`                                          |
 | 
			
		||||
| `IsFalse`, `False`                   | `findByActiveIsFalse()`                                      | `{"active" : false}`                                         |
 | 
			
		||||
| `Exists`                             | `findByLocationExists(boolean exists)`                       | `{"location" : {"$exists" : exists }}`                       |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 用例结构
 | 
			
		||||
 | 
			
		||||
1. 本用例提供mongdb的简单整合用例;
 | 
			
		||||
2. 提供用MongoTemplate的方式操作mongdb,见测试用例MongoOriginalTests.java
 | 
			
		||||
3. 提供基于spring data jpa 的方式操作mongodb(推荐),见测试用例MongoJPATests.java
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mongodb.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、整合 mongodb
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置mongodb数据源
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  data:
 | 
			
		||||
    mongodb:
 | 
			
		||||
      database: spring
 | 
			
		||||
      uri: mongodb://192.168.0.108:27017
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2  基于MongoTemplate实现对mongodb的操作
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MongoOriginalTests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private MongoTemplate mongoTemplate;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void insert() {
 | 
			
		||||
        // 单条插入
 | 
			
		||||
        mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
 | 
			
		||||
        List<Programmer> programmers = new ArrayList<Programmer>();
 | 
			
		||||
        // 批量插入
 | 
			
		||||
        programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
 | 
			
		||||
        mongoTemplate.insert(programmers, Programmer.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 条件查询
 | 
			
		||||
    @Test
 | 
			
		||||
    public void select() {
 | 
			
		||||
        Criteria criteria = new Criteria();
 | 
			
		||||
        criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
 | 
			
		||||
        Query query = new Query(criteria);
 | 
			
		||||
        Programmer one = mongoTemplate.findOne(query, Programmer.class);
 | 
			
		||||
        System.out.println(one);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // 更新数据
 | 
			
		||||
    @Test
 | 
			
		||||
    public void MUpdate() {
 | 
			
		||||
        UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
 | 
			
		||||
        System.out.println("更新记录数:" + updateResult.getModifiedCount());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 删除指定数据
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
 | 
			
		||||
        System.out.println("影响记录数:" + result.getDeletedCount());
 | 
			
		||||
        System.out.println("是否成功:" + result.wasAcknowledged());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 使用 data jpa 方式操作mongodb (推荐使用)
 | 
			
		||||
 | 
			
		||||
1.新建查询结构,查询方法按照支持的关键字命名
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public interface ProgrammerRepository extends MongoRepository<Programmer, String> {
 | 
			
		||||
 | 
			
		||||
    void deleteAllByName(String name);
 | 
			
		||||
 | 
			
		||||
    Programmer findAllByName(String names);
 | 
			
		||||
 | 
			
		||||
    Programmer findByNameAndAge(String name, int age);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2.测试
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MongoJPATests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgrammerRepository repository;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void insert() {
 | 
			
		||||
        // 单条插入
 | 
			
		||||
        repository.save(new Programmer("python", 23, 21832.34f, new Date()));
 | 
			
		||||
        // 批量插入
 | 
			
		||||
        List<Programmer> programmers = new ArrayList<Programmer>();
 | 
			
		||||
        programmers.add(new Programmer("java", 21, 52200.21f, new Date()));
 | 
			
		||||
        programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
 | 
			
		||||
        repository.saveAll(programmers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 条件查询
 | 
			
		||||
    @Test
 | 
			
		||||
    public void select() {
 | 
			
		||||
        Programmer java = repository.findByNameAndAge("java", 21);
 | 
			
		||||
        Assert.assertEquals(java.getSalary(), 52200.21f, 0.01);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // 更新数据
 | 
			
		||||
    @Test
 | 
			
		||||
    public void MUpdate() {
 | 
			
		||||
        repository.save(new Programmer("Go", 8, 500.21f, new Date()));
 | 
			
		||||
        Programmer go = repository.findAllByName("Go");
 | 
			
		||||
        Assert.assertEquals(go.getAge(), 8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 删除指定数据
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        repository.deleteAllByName("python");
 | 
			
		||||
        Optional<Programmer> python = repository.findById("python");
 | 
			
		||||
        Assert.assertFalse(python.isPresent());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
查询方法支持的关键字如下,更多命名规范可以参见Spring Data MongoDB官方文档[Query Methods](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongodb.repositories.queries):
 | 
			
		||||
 | 
			
		||||
| Keyword                              | Sample                                                       | Logical result                                               |
 | 
			
		||||
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| `After`                              | `findByBirthdateAfter(Date date)`                            | `{"birthdate" : {"$gt" : date}}`                             |
 | 
			
		||||
| `GreaterThan`                        | `findByAgeGreaterThan(int age)`                              | `{"age" : {"$gt" : age}}`                                    |
 | 
			
		||||
| `GreaterThanEqual`                   | `findByAgeGreaterThanEqual(int age)`                         | `{"age" : {"$gte" : age}}`                                   |
 | 
			
		||||
| `Before`                             | `findByBirthdateBefore(Date date)`                           | `{"birthdate" : {"$lt" : date}}`                             |
 | 
			
		||||
| `LessThan`                           | `findByAgeLessThan(int age)`                                 | `{"age" : {"$lt" : age}}`                                    |
 | 
			
		||||
| `LessThanEqual`                      | `findByAgeLessThanEqual(int age)`                            | `{"age" : {"$lte" : age}}`                                   |
 | 
			
		||||
| `Between`                            | `findByAgeBetween(int from, int to)`                         | `{"age" : {"$gt" : from, "$lt" : to}}`                       |
 | 
			
		||||
| `In`                                 | `findByAgeIn(Collection ages)`                               | `{"age" : {"$in" : [ages…]}}`                                |
 | 
			
		||||
| `NotIn`                              | `findByAgeNotIn(Collection ages)`                            | `{"age" : {"$nin" : [ages…]}}`                               |
 | 
			
		||||
| `IsNotNull`, `NotNull`               | `findByFirstnameNotNull()`                                   | `{"firstname" : {"$ne" : null}}`                             |
 | 
			
		||||
| `IsNull`, `Null`                     | `findByFirstnameNull()`                                      | `{"firstname" : null}`                                       |
 | 
			
		||||
| `Like`, `StartingWith`, `EndingWith` | `findByFirstnameLike(String name)`                           | `{"firstname" : name} (name as regex)`                       |
 | 
			
		||||
| `NotLike`, `IsNotLike`               | `findByFirstnameNotLike(String name)`                        | `{"firstname" : { "$not" : name }} (name as regex)`          |
 | 
			
		||||
| `Containing` on String               | `findByFirstnameContaining(String name)`                     | `{"firstname" : name} (name as regex)`                       |
 | 
			
		||||
| `NotContaining` on String            | `findByFirstnameNotContaining(String name)`                  | `{"firstname" : { "$not" : name}} (name as regex)`           |
 | 
			
		||||
| `Containing` on Collection           | `findByAddressesContaining(Address address)`                 | `{"addresses" : { "$in" : address}}`                         |
 | 
			
		||||
| `NotContaining` on Collection        | `findByAddressesNotContaining(Address address)`              | `{"addresses" : { "$not" : { "$in" : address}}}`             |
 | 
			
		||||
| `Regex`                              | `findByFirstnameRegex(String firstname)`                     | `{"firstname" : {"$regex" : firstname }}`                    |
 | 
			
		||||
| `(No keyword)`                       | `findByFirstname(String name)`                               | `{"firstname" : name}`                                       |
 | 
			
		||||
| `Not`                                | `findByFirstnameNot(String name)`                            | `{"firstname" : {"$ne" : name}}`                             |
 | 
			
		||||
| `Near`                               | `findByLocationNear(Point point)`                            | `{"location" : {"$near" : [x,y]}}`                           |
 | 
			
		||||
| `Near`                               | `findByLocationNear(Point point, Distance max)`              | `{"location" : {"$near" : [x,y], "$maxDistance" : max}}`     |
 | 
			
		||||
| `Near`                               | `findByLocationNear(Point point, Distance min, Distance max)` | `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}` |
 | 
			
		||||
| `Within`                             | `findByLocationWithin(Circle circle)`                        | `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}` |
 | 
			
		||||
| `Within`                             | `findByLocationWithin(Box box)`                              | `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}` |
 | 
			
		||||
| `IsTrue`, `True`                     | `findByActiveIsTrue()`                                       | `{"active" : true}`                                          |
 | 
			
		||||
| `IsFalse`, `False`                   | `findByActiveIsFalse()`                                      | `{"active" : false}`                                         |
 | 
			
		||||
| `Exists`                             | `findByLocationExists(boolean exists)`                       | `{"location" : {"$exists" : exists }}`                       |
 | 
			
		||||
 
 | 
			
		||||
@@ -1,249 +1,259 @@
 | 
			
		||||
# spring boot 整合 mybatis
 | 
			
		||||
# spring boot 整合 mybatis
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构">1.1 项目结构</a><br/>
 | 
			
		||||
        <a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
 | 
			
		||||
<a href="#二整合-mybatis">二、整合 mybatis</a><br/>
 | 
			
		||||
        <a href="#21-在applicationyml-中配置数据源">2.1 在application.yml 中配置数据源</a><br/>
 | 
			
		||||
        <a href="#22--xml方式的sql语句">2.2  xml方式的sql语句</a><br/>
 | 
			
		||||
        <a href="#23-注解方式的sql语句">2.3 注解方式的sql语句</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下;
 | 
			
		||||
 | 
			
		||||
2. 关于mybatis sql的写法提供两种方式:
 | 
			
		||||
 | 
			
		||||
   xml 写法:对应的类为ProgrammerMapper.java 和 programmerMapper.xml,用MybatisXmlTest进行测试;
 | 
			
		||||
 | 
			
		||||
   注解写法:对应的类为Programmer.java ,用MybatisAnnotationTest进行测试。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mybatis.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
需要说明的是按照spring 官方对应自定义的starter 命名规范的推荐:
 | 
			
		||||
 | 
			
		||||
- 官方的starter命名:spring-boot-starter-XXXX
 | 
			
		||||
- 其他第三方starter命名:XXXX-spring-boot-starte
 | 
			
		||||
 | 
			
		||||
所以mybatis的starter命名为mybatis-spring-boot-starter,如果有自定义starter需求,也需要按照此命名规则进行命名。
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--spring 1.5 x 以上版本对应 mybatis 1.3.x (1.3.1)
 | 
			
		||||
        关于更多spring-boot 与 mybatis 的版本对应可以参见 <a href="http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/">-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.mybatis.spring.boot</groupId>
 | 
			
		||||
    <artifactId>mybatis-spring-boot-starter</artifactId>
 | 
			
		||||
    <version>1.3.2</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--引入mysql驱动-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>mysql</groupId>
 | 
			
		||||
    <artifactId>mysql-connector-java</artifactId>
 | 
			
		||||
    <version>8.0.13</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.projectlombok</groupId>
 | 
			
		||||
    <artifactId>lombok</artifactId>
 | 
			
		||||
    <optional>true</optional>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
    <scope>test</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
spring boot 与 mybatis 版本的对应关系:
 | 
			
		||||
 | 
			
		||||
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot   |
 | 
			
		||||
| --------------------------- | ------------------------------------------------------------ | ------------- |
 | 
			
		||||
| **1.3.x (1.3.1)**           | 1.3 or higher                                                | 1.5 or higher |
 | 
			
		||||
| **1.2.x (1.2.1)**           | 1.3 or higher                                                | 1.4 or higher |
 | 
			
		||||
| **1.1.x (1.1.1)**           | 1.3 or higher                                                | 1.3 or higher |
 | 
			
		||||
| **1.0.x (1.0.2)**           | 1.2 or higher                                                | 1.3 or higher |
 | 
			
		||||
 | 
			
		||||
## 二、整合 mybatis
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置数据源
 | 
			
		||||
 | 
			
		||||
spring boot 2.x 版本默认采用Hikari作为数据库连接池,Hikari是目前java平台性能最好的连接池,性能好于druid。
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  datasource:
 | 
			
		||||
    url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
 | 
			
		||||
    username: root
 | 
			
		||||
    password: root
 | 
			
		||||
    driver-class-name: com.mysql.cj.jdbc.Driver
 | 
			
		||||
 | 
			
		||||
    # 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
 | 
			
		||||
    # spring-boot 2 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
 | 
			
		||||
    type: com.zaxxer.hikari.HikariDataSource
 | 
			
		||||
    hikari:
 | 
			
		||||
      # 池中维护的最小空闲连接数
 | 
			
		||||
      minimum-idle: 10
 | 
			
		||||
      # 池中最大连接数,包括闲置和使用中的连接
 | 
			
		||||
      maximum-pool-size: 20
 | 
			
		||||
      # 此属性控制从池返回的连接的默认自动提交行为。默认为true
 | 
			
		||||
      auto-commit: true
 | 
			
		||||
      # 允许最长空闲时间
 | 
			
		||||
      idle-timeout: 30000
 | 
			
		||||
      # 此属性表示连接池的用户定义名称,主要显示在日志记录和JMX管理控制台中,以标识池和池配置。 默认值:自动生成
 | 
			
		||||
      pool-name: custom-hikari
 | 
			
		||||
      #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
 | 
			
		||||
      max-lifetime: 1800000
 | 
			
		||||
      # 数据库连接超时时间,默认30秒,即30000
 | 
			
		||||
      connection-timeout: 30000
 | 
			
		||||
      # 连接测试sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成  select 1 from dual
 | 
			
		||||
      connection-test-query: SELECT 1
 | 
			
		||||
 | 
			
		||||
# mybatis 相关配置
 | 
			
		||||
mybatis:
 | 
			
		||||
    # 指定 sql xml 文件的位置
 | 
			
		||||
    mapper-locations: classpath*:mappers/*.xml
 | 
			
		||||
    configuration:
 | 
			
		||||
      # 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
 | 
			
		||||
      # oracle数据库建议配置为JdbcType.NULL, 默认是Other
 | 
			
		||||
      jdbc-type-for-null: 'null'
 | 
			
		||||
      # 是否打印sql语句 调试的时候可以开启
 | 
			
		||||
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2  xml方式的sql语句
 | 
			
		||||
 | 
			
		||||
新建 ProgrammerMapper.java 和 programmerMapper.xml,及其测试类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface ProgrammerMapper {
 | 
			
		||||
 | 
			
		||||
    void save(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    Programmer selectById(int id);
 | 
			
		||||
 | 
			
		||||
    int modify(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    void delete(int id);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0"  encoding="utf-8"?>
 | 
			
		||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 | 
			
		||||
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
 | 
			
		||||
<mapper namespace="com.heibaiying.springboot.dao.ProgrammerMapper">
 | 
			
		||||
 | 
			
		||||
    <insert id="save">
 | 
			
		||||
        insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})
 | 
			
		||||
    </insert>
 | 
			
		||||
 | 
			
		||||
    <select id="selectById" resultType="com.heibaiying.springboot.bean.Programmer">
 | 
			
		||||
      select * from programmer where name = #{id}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <update id="modify">
 | 
			
		||||
        update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}
 | 
			
		||||
    </update>
 | 
			
		||||
 | 
			
		||||
    <delete id="delete">
 | 
			
		||||
        delete from programmer where id = #{id}
 | 
			
		||||
    </delete>
 | 
			
		||||
 | 
			
		||||
</mapper>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
测试类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/***
 | 
			
		||||
 * @description: xml Sql测试类
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MybatisXmlTest {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgrammerMapper mapper;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void save() {
 | 
			
		||||
        mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
 | 
			
		||||
        mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void modify() {
 | 
			
		||||
        mapper.modify(new Programmer(1, "xiaohong", 112, 347.34f, new Date()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void selectByCondition() {
 | 
			
		||||
        Programmer programmers = mapper.selectById(1);
 | 
			
		||||
        System.out.println(programmers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        mapper.delete(2);
 | 
			
		||||
        Programmer programmers = mapper.selectById(2);
 | 
			
		||||
        Assert.assertNull(programmers);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 注解方式的sql语句
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface ProgrammerDao {
 | 
			
		||||
 | 
			
		||||
    @Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
 | 
			
		||||
    void save(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    @Select("select * from programmer where name = #{id}")
 | 
			
		||||
    Programmer selectById(int id);
 | 
			
		||||
 | 
			
		||||
    @Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
 | 
			
		||||
    int modify(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    @Delete(" delete from programmer where id = #{id}")
 | 
			
		||||
    void delete(int id);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
测试类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/***
 | 
			
		||||
 * @description: 注解Sql测试类
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MybatisAnnotationTest {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgrammerDao programmerDao;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void save() {
 | 
			
		||||
        programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
 | 
			
		||||
        programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void modify() {
 | 
			
		||||
        programmerDao.modify(new Programmer(1, "xiaolan", 21, 347.34f, new Date()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void selectByCondition() {
 | 
			
		||||
        Programmer programmers = programmerDao.selectById(1);
 | 
			
		||||
        System.out.println(programmers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        programmerDao.delete(3);
 | 
			
		||||
        Programmer programmers = programmerDao.selectById(3);
 | 
			
		||||
        Assert.assertNull(programmers);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下;
 | 
			
		||||
 | 
			
		||||
2. 关于mybatis sql的写法提供两种方式:
 | 
			
		||||
 | 
			
		||||
   xml 写法:对应的类为ProgrammerMapper.java 和 programmerMapper.xml,用MybatisXmlTest进行测试;
 | 
			
		||||
 | 
			
		||||
   注解写法:对应的类为Programmer.java ,用MybatisAnnotationTest进行测试。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mybatis.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
需要说明的是按照spring 官方对应自定义的starter 命名规范的推荐:
 | 
			
		||||
 | 
			
		||||
- 官方的starter命名:spring-boot-starter-XXXX
 | 
			
		||||
- 其他第三方starter命名:XXXX-spring-boot-starte
 | 
			
		||||
 | 
			
		||||
所以mybatis的starter命名为mybatis-spring-boot-starter,如果有自定义starter需求,也需要按照此命名规则进行命名。
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--spring 1.5 x 以上版本对应 mybatis 1.3.x (1.3.1)
 | 
			
		||||
        关于更多spring-boot 与 mybatis 的版本对应可以参见 <a href="http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/">-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.mybatis.spring.boot</groupId>
 | 
			
		||||
    <artifactId>mybatis-spring-boot-starter</artifactId>
 | 
			
		||||
    <version>1.3.2</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--引入mysql驱动-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>mysql</groupId>
 | 
			
		||||
    <artifactId>mysql-connector-java</artifactId>
 | 
			
		||||
    <version>8.0.13</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.projectlombok</groupId>
 | 
			
		||||
    <artifactId>lombok</artifactId>
 | 
			
		||||
    <optional>true</optional>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
    <scope>test</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
spring boot 与 mybatis 版本的对应关系:
 | 
			
		||||
 | 
			
		||||
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot   |
 | 
			
		||||
| --------------------------- | ------------------------------------------------------------ | ------------- |
 | 
			
		||||
| **1.3.x (1.3.1)**           | 1.3 or higher                                                | 1.5 or higher |
 | 
			
		||||
| **1.2.x (1.2.1)**           | 1.3 or higher                                                | 1.4 or higher |
 | 
			
		||||
| **1.1.x (1.1.1)**           | 1.3 or higher                                                | 1.3 or higher |
 | 
			
		||||
| **1.0.x (1.0.2)**           | 1.2 or higher                                                | 1.3 or higher |
 | 
			
		||||
 | 
			
		||||
## 二、整合 mybatis
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置数据源
 | 
			
		||||
 | 
			
		||||
spring boot 2.x 版本默认采用Hikari作为数据库连接池,Hikari是目前java平台性能最好的连接池,性能好于druid。
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  datasource:
 | 
			
		||||
    url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
 | 
			
		||||
    username: root
 | 
			
		||||
    password: root
 | 
			
		||||
    driver-class-name: com.mysql.cj.jdbc.Driver
 | 
			
		||||
 | 
			
		||||
    # 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
 | 
			
		||||
    # spring-boot 2 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
 | 
			
		||||
    type: com.zaxxer.hikari.HikariDataSource
 | 
			
		||||
    hikari:
 | 
			
		||||
      # 池中维护的最小空闲连接数
 | 
			
		||||
      minimum-idle: 10
 | 
			
		||||
      # 池中最大连接数,包括闲置和使用中的连接
 | 
			
		||||
      maximum-pool-size: 20
 | 
			
		||||
      # 此属性控制从池返回的连接的默认自动提交行为。默认为true
 | 
			
		||||
      auto-commit: true
 | 
			
		||||
      # 允许最长空闲时间
 | 
			
		||||
      idle-timeout: 30000
 | 
			
		||||
      # 此属性表示连接池的用户定义名称,主要显示在日志记录和JMX管理控制台中,以标识池和池配置。 默认值:自动生成
 | 
			
		||||
      pool-name: custom-hikari
 | 
			
		||||
      #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
 | 
			
		||||
      max-lifetime: 1800000
 | 
			
		||||
      # 数据库连接超时时间,默认30秒,即30000
 | 
			
		||||
      connection-timeout: 30000
 | 
			
		||||
      # 连接测试sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成  select 1 from dual
 | 
			
		||||
      connection-test-query: SELECT 1
 | 
			
		||||
 | 
			
		||||
# mybatis 相关配置
 | 
			
		||||
mybatis:
 | 
			
		||||
    # 指定 sql xml 文件的位置
 | 
			
		||||
    mapper-locations: classpath*:mappers/*.xml
 | 
			
		||||
    configuration:
 | 
			
		||||
      # 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
 | 
			
		||||
      # oracle数据库建议配置为JdbcType.NULL, 默认是Other
 | 
			
		||||
      jdbc-type-for-null: 'null'
 | 
			
		||||
      # 是否打印sql语句 调试的时候可以开启
 | 
			
		||||
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2  xml方式的sql语句
 | 
			
		||||
 | 
			
		||||
新建 ProgrammerMapper.java 和 programmerMapper.xml,及其测试类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface ProgrammerMapper {
 | 
			
		||||
 | 
			
		||||
    void save(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    Programmer selectById(int id);
 | 
			
		||||
 | 
			
		||||
    int modify(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    void delete(int id);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0"  encoding="utf-8"?>
 | 
			
		||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 | 
			
		||||
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
 | 
			
		||||
<mapper namespace="com.heibaiying.springboot.dao.ProgrammerMapper">
 | 
			
		||||
 | 
			
		||||
    <insert id="save">
 | 
			
		||||
        insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})
 | 
			
		||||
    </insert>
 | 
			
		||||
 | 
			
		||||
    <select id="selectById" resultType="com.heibaiying.springboot.bean.Programmer">
 | 
			
		||||
      select * from programmer where name = #{id}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <update id="modify">
 | 
			
		||||
        update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}
 | 
			
		||||
    </update>
 | 
			
		||||
 | 
			
		||||
    <delete id="delete">
 | 
			
		||||
        delete from programmer where id = #{id}
 | 
			
		||||
    </delete>
 | 
			
		||||
 | 
			
		||||
</mapper>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
测试类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/***
 | 
			
		||||
 * @description: xml Sql测试类
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MybatisXmlTest {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgrammerMapper mapper;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void save() {
 | 
			
		||||
        mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
 | 
			
		||||
        mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void modify() {
 | 
			
		||||
        mapper.modify(new Programmer(1, "xiaohong", 112, 347.34f, new Date()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void selectByCondition() {
 | 
			
		||||
        Programmer programmers = mapper.selectById(1);
 | 
			
		||||
        System.out.println(programmers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        mapper.delete(2);
 | 
			
		||||
        Programmer programmers = mapper.selectById(2);
 | 
			
		||||
        Assert.assertNull(programmers);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 注解方式的sql语句
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface ProgrammerDao {
 | 
			
		||||
 | 
			
		||||
    @Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
 | 
			
		||||
    void save(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    @Select("select * from programmer where name = #{id}")
 | 
			
		||||
    Programmer selectById(int id);
 | 
			
		||||
 | 
			
		||||
    @Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
 | 
			
		||||
    int modify(Programmer programmer);
 | 
			
		||||
 | 
			
		||||
    @Delete(" delete from programmer where id = #{id}")
 | 
			
		||||
    void delete(int id);
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
测试类
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/***
 | 
			
		||||
 * @description: 注解Sql测试类
 | 
			
		||||
 */
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class MybatisAnnotationTest {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private ProgrammerDao programmerDao;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void save() {
 | 
			
		||||
        programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
 | 
			
		||||
        programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void modify() {
 | 
			
		||||
        programmerDao.modify(new Programmer(1, "xiaolan", 21, 347.34f, new Date()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void selectByCondition() {
 | 
			
		||||
        Programmer programmers = programmerDao.selectById(1);
 | 
			
		||||
        System.out.println(programmers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void delete() {
 | 
			
		||||
        programmerDao.delete(3);
 | 
			
		||||
        Programmer programmers = programmerDao.selectById(3);
 | 
			
		||||
        Assert.assertNull(programmers);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,390 +1,404 @@
 | 
			
		||||
# spring boot 整合 rabbitmq
 | 
			
		||||
 | 
			
		||||
## 一、 项目结构说明
 | 
			
		||||
 | 
			
		||||
1.1  之前关于spring 整合 rabbitmq 我们采用的是单项目的方式,为了使得用例更具有实际意义,这里采用maven多模块的构建方式,在spring-boot-rabbitmq下构建三个子模块:
 | 
			
		||||
 | 
			
		||||
1. rabbitmq-common 是公共模块,用于存放公共的接口、配置和bean,被rabbitmq-producer和rabbitmq-consumer在pom.xml中引用;
 | 
			
		||||
2. rabbitmq-producer 是消息的生产者模块;
 | 
			
		||||
3. rabbitmq-consumer是消息的消费者模块。
 | 
			
		||||
 | 
			
		||||
1.2  关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-rabbitmq.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、关键依赖
 | 
			
		||||
 | 
			
		||||
在父工程的项目中统一导入依赖rabbitmq的starter(spring-boot-starter-amqp),父工程的pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <packaging>pom</packaging>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
        <module>rabbitmq-consumer</module>
 | 
			
		||||
        <module>rabbitmq-producer</module>
 | 
			
		||||
        <module>rabbitmq-common</module>
 | 
			
		||||
    </modules>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-parent</artifactId>
 | 
			
		||||
        <version>2.1.1.RELEASE</version>
 | 
			
		||||
        <relativePath/> <!-- lookup parent from repository -->
 | 
			
		||||
    </parent>
 | 
			
		||||
    <groupId>com.heibaiying</groupId>
 | 
			
		||||
    <artifactId>spring-boot-rabbitmq</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>spring-boot-rabbitmq</name>
 | 
			
		||||
    <description>RabbitMQ project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-amqp</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.projectlombok</groupId>
 | 
			
		||||
            <artifactId>lombok</artifactId>
 | 
			
		||||
            <optional>true</optional>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
    
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、公共模块(rabbitmq-common)
 | 
			
		||||
 | 
			
		||||
- bean 下为公共的实体类。
 | 
			
		||||
- constant 下为公共配置,用静态常量引用。(这里我使用静态常量是为了方便引用,实际中也可以按照情况,抽取为公共配置文件)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-common.png"/> </div>
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
package com.heibaiying.constant;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : rabbit 公用配置信息
 | 
			
		||||
 */
 | 
			
		||||
public class RabbitInfo {
 | 
			
		||||
 | 
			
		||||
    // queue 配置
 | 
			
		||||
    public static final String QUEUE_NAME = "spring.boot.simple.queue";
 | 
			
		||||
    public static final String QUEUE_DURABLE = "true";
 | 
			
		||||
 | 
			
		||||
    // exchange 配置
 | 
			
		||||
    public static final String EXCHANGE_NAME = "spring.boot.simple.exchange";
 | 
			
		||||
    public static final String EXCHANGE_TYPE = "topic";
 | 
			
		||||
 | 
			
		||||
    // routing key
 | 
			
		||||
    public static final String ROUTING_KEY = "springboot.simple.*";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、服务消费者(rabbitmq-consumer)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-consumer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 4.1 消息消费者配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  rabbitmq:
 | 
			
		||||
    addresses: 127.0.0.1:5672
 | 
			
		||||
    # RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
 | 
			
		||||
    # 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
 | 
			
		||||
    username: guest
 | 
			
		||||
    password: guest
 | 
			
		||||
    virtual-host: /
 | 
			
		||||
    listener:
 | 
			
		||||
      simple:
 | 
			
		||||
        # 为了保证信息能够被正确消费,建议签收模式设置为手工签收,并在代码中实现手工签收
 | 
			
		||||
        acknowledge-mode: manual
 | 
			
		||||
        # 侦听器调用者线程的最小数量
 | 
			
		||||
        concurrency: 10
 | 
			
		||||
        # 侦听器调用者线程的最大数量
 | 
			
		||||
        max-concurrency: 50
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者
 | 
			
		||||
 | 
			
		||||
1. 使用注解创建的交换机、队列、和绑定关系会在项目初始化的时候自动创建,但是不会重复创建;
 | 
			
		||||
2. 这里我们创建两个消息监听器,分别演示消息是基本类型和消息是对象时的配置区别。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 消息是对象的消费者
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class RabbitmqBeanConsumer {
 | 
			
		||||
 | 
			
		||||
    @RabbitListener(bindings = @QueueBinding(
 | 
			
		||||
            value = @Queue(value = RabbitBeanInfo.QUEUE_NAME, durable = RabbitBeanInfo.QUEUE_DURABLE),
 | 
			
		||||
            exchange = @Exchange(value = RabbitBeanInfo.EXCHANGE_NAME, type = RabbitBeanInfo.EXCHANGE_TYPE),
 | 
			
		||||
            key = RabbitBeanInfo.ROUTING_KEY)
 | 
			
		||||
    )
 | 
			
		||||
    @RabbitHandler
 | 
			
		||||
    public void onMessage(@Payload Programmer programmer, @Headers Map<String, Object> headers, Channel channel) throws Exception {
 | 
			
		||||
        log.info("programmer:{} ", programmer);
 | 
			
		||||
        Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
 | 
			
		||||
        channel.basicAck(deliveryTag, false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class RabbitmqConsumer {
 | 
			
		||||
 | 
			
		||||
    @RabbitListener(bindings = @QueueBinding(
 | 
			
		||||
            value = @Queue(value = RabbitInfo.QUEUE_NAME, durable = RabbitInfo.QUEUE_DURABLE),
 | 
			
		||||
            exchange = @Exchange(value = RabbitInfo.EXCHANGE_NAME, type = RabbitInfo.EXCHANGE_TYPE),
 | 
			
		||||
            key = RabbitInfo.ROUTING_KEY)
 | 
			
		||||
    )
 | 
			
		||||
    @RabbitHandler
 | 
			
		||||
    public void onMessage(Message message, Channel channel) throws Exception {
 | 
			
		||||
        MessageHeaders headers = message.getHeaders();
 | 
			
		||||
        // 获取消息头信息和消息体
 | 
			
		||||
        log.info("msgInfo:{} ; payload:{} ", headers.get("msgInfo"), message.getPayload());
 | 
			
		||||
        //  DELIVERY_TAG 代表 RabbitMQ 向该Channel投递的这条消息的唯一标识ID,是一个单调递增的正整数
 | 
			
		||||
        Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
 | 
			
		||||
        // 第二个参数代表是否一次签收多条,当该参数为 true 时,则可以一次性确认 DELIVERY_TAG 小于等于传入值的所有消息
 | 
			
		||||
        channel.basicAck(deliveryTag, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、 消息生产者(rabbitmq-producer)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-producer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 5.1 消息生产者配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  rabbitmq:
 | 
			
		||||
    addresses: 127.0.0.1:5672
 | 
			
		||||
    # RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
 | 
			
		||||
    # 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
 | 
			
		||||
    username: guest
 | 
			
		||||
    password: guest
 | 
			
		||||
    virtual-host: /
 | 
			
		||||
    # 是否启用发布者确认 具体确认回调实现见代码
 | 
			
		||||
    publisher-confirms: true
 | 
			
		||||
    # 是否启用发布者返回 具体返回回调实现见代码
 | 
			
		||||
    publisher-returns: true
 | 
			
		||||
    # 是否启用强制消息 保证消息的有效监听
 | 
			
		||||
    template.mandatory: true
 | 
			
		||||
 | 
			
		||||
server:
 | 
			
		||||
  port: 8090
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.2  创建消息生产者
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 消息生产者
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class RabbitmqProducer {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RabbitTemplate rabbitTemplate;
 | 
			
		||||
 | 
			
		||||
    public void sendSimpleMessage(Map<String, Object> headers, Object message,
 | 
			
		||||
                                  String messageId, String exchangeName, String key) {
 | 
			
		||||
        // 自定义消息头
 | 
			
		||||
        MessageHeaders messageHeaders = new MessageHeaders(headers);
 | 
			
		||||
        // 创建消息
 | 
			
		||||
        Message<Object> msg = MessageBuilder.createMessage(message, messageHeaders);
 | 
			
		||||
        /* 确认的回调 确认消息是否到达 Broker 服务器 其实就是是否到达交换器
 | 
			
		||||
           如果发送时候指定的交换器不存在 ack就是false 代表消息不可达 */
 | 
			
		||||
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
 | 
			
		||||
            log.info("correlationData:{} , ack:{}", correlationData.getId(), ack);
 | 
			
		||||
            if (!ack) {
 | 
			
		||||
                System.out.println("进行对应的消息补偿机制");
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        /* 消息失败的回调
 | 
			
		||||
         * 例如消息已经到达交换器上,但路由键匹配任何绑定到该交换器的队列,会触发这个回调,此时 replyText: NO_ROUTE
 | 
			
		||||
         */
 | 
			
		||||
        rabbitTemplate.setReturnCallback((message1, replyCode, replyText, exchange, routingKey) -> {
 | 
			
		||||
            log.info("message:{}; replyCode: {}; replyText: {} ; exchange:{} ; routingKey:{}",
 | 
			
		||||
                    message1, replyCode, replyText, exchange, routingKey);
 | 
			
		||||
        });
 | 
			
		||||
        // 在实际中ID 应该是全局唯一 能够唯一标识消息 消息不可达的时候触发ConfirmCallback回调方法时可以获取该值,进行对应的错误处理
 | 
			
		||||
        CorrelationData correlationData = new CorrelationData(messageId);
 | 
			
		||||
        rabbitTemplate.convertAndSend(exchangeName, key, msg, correlationData);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.3  以单元测试的方式发送消息
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class RabbitmqProducerTests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RabbitmqProducer producer;
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 发送消息体为简单数据类型的消息
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void send() {
 | 
			
		||||
        Map<String, Object> heads = new HashMap<>();
 | 
			
		||||
        heads.put("msgInfo", "自定义消息头信息");
 | 
			
		||||
        // 模拟生成消息ID,在实际中应该是全局唯一的 消息不可达时候可以在setConfirmCallback回调中取得,可以进行对应的重发或错误处理
 | 
			
		||||
        String id = String.valueOf(Math.round(Math.random() * 10000));
 | 
			
		||||
        producer.sendSimpleMessage(heads, "hello Spring", id, RabbitInfo.EXCHANGE_NAME, "springboot.simple.abc");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 发送消息体为bean的消息
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void sendBean() {
 | 
			
		||||
        String id = String.valueOf(Math.round(Math.random() * 10000));
 | 
			
		||||
        Programmer programmer = new Programmer("xiaoMing", 12, 12123.45f, new Date());
 | 
			
		||||
        producer.sendSimpleMessage(null, programmer, id, RabbitBeanInfo.EXCHANGE_NAME, RabbitBeanInfo.ROUTING_KEY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 六、项目构建的说明
 | 
			
		||||
 | 
			
		||||
因为在项目中,consumer和producer模块均依赖公共模块,所以在构建consumer和producer项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
mvn install -Dmaven.test.skip = true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
consumer中 pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying</groupId>
 | 
			
		||||
        <artifactId>spring-boot-rabbitmq</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>rabbitmq-consumer</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>rabbitmq-consumer</name>
 | 
			
		||||
    <description>RabbitMQ consumer project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.heibaiying</groupId>
 | 
			
		||||
            <artifactId>rabbitmq-common</artifactId>
 | 
			
		||||
            <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
producer中 pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying</groupId>
 | 
			
		||||
        <artifactId>spring-boot-rabbitmq</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>rabbitmq-producer</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>rabbitmq-producer</name>
 | 
			
		||||
    <description>RabbitMQ producer project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.heibaiying</groupId>
 | 
			
		||||
            <artifactId>rabbitmq-common</artifactId>
 | 
			
		||||
            <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
# spring boot 整合 rabbitmq
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
 | 
			
		||||
<a href="#二关键依赖">二、关键依赖</a><br/>
 | 
			
		||||
<a href="#三公共模块rabbitmq-common">三、公共模块(rabbitmq-common)</a><br/>
 | 
			
		||||
<a href="#四服务消费者rabbitmq-consumer">四、服务消费者(rabbitmq-consumer)</a><br/>
 | 
			
		||||
        <a href="#41-消息消费者配置">4.1 消息消费者配置</a><br/>
 | 
			
		||||
        <a href="#42-使用注解@RabbitListener和@RabbitHandler创建消息监听者">4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者</a><br/>
 | 
			
		||||
<a href="#五-消息生产者rabbitmq-producer">五、 消息生产者(rabbitmq-producer)</a><br/>
 | 
			
		||||
        <a href="#51-消息生产者配置">5.1 消息生产者配置</a><br/>
 | 
			
		||||
        <a href="#52--创建消息生产者">5.2  创建消息生产者</a><br/>
 | 
			
		||||
        <a href="#53--以单元测试的方式发送消息">5.3  以单元测试的方式发送消息</a><br/>
 | 
			
		||||
<a href="#六项目构建的说明">六、项目构建的说明</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、 项目结构说明
 | 
			
		||||
 | 
			
		||||
1.1  之前关于spring 整合 rabbitmq 我们采用的是单项目的方式,为了使得用例更具有实际意义,这里采用maven多模块的构建方式,在spring-boot-rabbitmq下构建三个子模块:
 | 
			
		||||
 | 
			
		||||
1. rabbitmq-common 是公共模块,用于存放公共的接口、配置和bean,被rabbitmq-producer和rabbitmq-consumer在pom.xml中引用;
 | 
			
		||||
2. rabbitmq-producer 是消息的生产者模块;
 | 
			
		||||
3. rabbitmq-consumer是消息的消费者模块。
 | 
			
		||||
 | 
			
		||||
1.2  关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-rabbitmq.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、关键依赖
 | 
			
		||||
 | 
			
		||||
在父工程的项目中统一导入依赖rabbitmq的starter(spring-boot-starter-amqp),父工程的pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
    <packaging>pom</packaging>
 | 
			
		||||
 | 
			
		||||
    <modules>
 | 
			
		||||
        <module>rabbitmq-consumer</module>
 | 
			
		||||
        <module>rabbitmq-producer</module>
 | 
			
		||||
        <module>rabbitmq-common</module>
 | 
			
		||||
    </modules>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-parent</artifactId>
 | 
			
		||||
        <version>2.1.1.RELEASE</version>
 | 
			
		||||
        <relativePath/> <!-- lookup parent from repository -->
 | 
			
		||||
    </parent>
 | 
			
		||||
    <groupId>com.heibaiying</groupId>
 | 
			
		||||
    <artifactId>spring-boot-rabbitmq</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>spring-boot-rabbitmq</name>
 | 
			
		||||
    <description>RabbitMQ project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-amqp</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.projectlombok</groupId>
 | 
			
		||||
            <artifactId>lombok</artifactId>
 | 
			
		||||
            <optional>true</optional>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
    
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、公共模块(rabbitmq-common)
 | 
			
		||||
 | 
			
		||||
- bean 下为公共的实体类。
 | 
			
		||||
- constant 下为公共配置,用静态常量引用。(这里我使用静态常量是为了方便引用,实际中也可以按照情况,抽取为公共配置文件)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-common.png"/> </div>
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
package com.heibaiying.constant;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : rabbit 公用配置信息
 | 
			
		||||
 */
 | 
			
		||||
public class RabbitInfo {
 | 
			
		||||
 | 
			
		||||
    // queue 配置
 | 
			
		||||
    public static final String QUEUE_NAME = "spring.boot.simple.queue";
 | 
			
		||||
    public static final String QUEUE_DURABLE = "true";
 | 
			
		||||
 | 
			
		||||
    // exchange 配置
 | 
			
		||||
    public static final String EXCHANGE_NAME = "spring.boot.simple.exchange";
 | 
			
		||||
    public static final String EXCHANGE_TYPE = "topic";
 | 
			
		||||
 | 
			
		||||
    // routing key
 | 
			
		||||
    public static final String ROUTING_KEY = "springboot.simple.*";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、服务消费者(rabbitmq-consumer)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-consumer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 4.1 消息消费者配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  rabbitmq:
 | 
			
		||||
    addresses: 127.0.0.1:5672
 | 
			
		||||
    # RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
 | 
			
		||||
    # 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
 | 
			
		||||
    username: guest
 | 
			
		||||
    password: guest
 | 
			
		||||
    virtual-host: /
 | 
			
		||||
    listener:
 | 
			
		||||
      simple:
 | 
			
		||||
        # 为了保证信息能够被正确消费,建议签收模式设置为手工签收,并在代码中实现手工签收
 | 
			
		||||
        acknowledge-mode: manual
 | 
			
		||||
        # 侦听器调用者线程的最小数量
 | 
			
		||||
        concurrency: 10
 | 
			
		||||
        # 侦听器调用者线程的最大数量
 | 
			
		||||
        max-concurrency: 50
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者
 | 
			
		||||
 | 
			
		||||
1. 使用注解创建的交换机、队列、和绑定关系会在项目初始化的时候自动创建,但是不会重复创建;
 | 
			
		||||
2. 这里我们创建两个消息监听器,分别演示消息是基本类型和消息是对象时的配置区别。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 消息是对象的消费者
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class RabbitmqBeanConsumer {
 | 
			
		||||
 | 
			
		||||
    @RabbitListener(bindings = @QueueBinding(
 | 
			
		||||
            value = @Queue(value = RabbitBeanInfo.QUEUE_NAME, durable = RabbitBeanInfo.QUEUE_DURABLE),
 | 
			
		||||
            exchange = @Exchange(value = RabbitBeanInfo.EXCHANGE_NAME, type = RabbitBeanInfo.EXCHANGE_TYPE),
 | 
			
		||||
            key = RabbitBeanInfo.ROUTING_KEY)
 | 
			
		||||
    )
 | 
			
		||||
    @RabbitHandler
 | 
			
		||||
    public void onMessage(@Payload Programmer programmer, @Headers Map<String, Object> headers, Channel channel) throws Exception {
 | 
			
		||||
        log.info("programmer:{} ", programmer);
 | 
			
		||||
        Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
 | 
			
		||||
        channel.basicAck(deliveryTag, false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class RabbitmqConsumer {
 | 
			
		||||
 | 
			
		||||
    @RabbitListener(bindings = @QueueBinding(
 | 
			
		||||
            value = @Queue(value = RabbitInfo.QUEUE_NAME, durable = RabbitInfo.QUEUE_DURABLE),
 | 
			
		||||
            exchange = @Exchange(value = RabbitInfo.EXCHANGE_NAME, type = RabbitInfo.EXCHANGE_TYPE),
 | 
			
		||||
            key = RabbitInfo.ROUTING_KEY)
 | 
			
		||||
    )
 | 
			
		||||
    @RabbitHandler
 | 
			
		||||
    public void onMessage(Message message, Channel channel) throws Exception {
 | 
			
		||||
        MessageHeaders headers = message.getHeaders();
 | 
			
		||||
        // 获取消息头信息和消息体
 | 
			
		||||
        log.info("msgInfo:{} ; payload:{} ", headers.get("msgInfo"), message.getPayload());
 | 
			
		||||
        //  DELIVERY_TAG 代表 RabbitMQ 向该Channel投递的这条消息的唯一标识ID,是一个单调递增的正整数
 | 
			
		||||
        Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
 | 
			
		||||
        // 第二个参数代表是否一次签收多条,当该参数为 true 时,则可以一次性确认 DELIVERY_TAG 小于等于传入值的所有消息
 | 
			
		||||
        channel.basicAck(deliveryTag, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、 消息生产者(rabbitmq-producer)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-producer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 5.1 消息生产者配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  rabbitmq:
 | 
			
		||||
    addresses: 127.0.0.1:5672
 | 
			
		||||
    # RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
 | 
			
		||||
    # 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
 | 
			
		||||
    username: guest
 | 
			
		||||
    password: guest
 | 
			
		||||
    virtual-host: /
 | 
			
		||||
    # 是否启用发布者确认 具体确认回调实现见代码
 | 
			
		||||
    publisher-confirms: true
 | 
			
		||||
    # 是否启用发布者返回 具体返回回调实现见代码
 | 
			
		||||
    publisher-returns: true
 | 
			
		||||
    # 是否启用强制消息 保证消息的有效监听
 | 
			
		||||
    template.mandatory: true
 | 
			
		||||
 | 
			
		||||
server:
 | 
			
		||||
  port: 8090
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.2  创建消息生产者
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 消息生产者
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class RabbitmqProducer {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RabbitTemplate rabbitTemplate;
 | 
			
		||||
 | 
			
		||||
    public void sendSimpleMessage(Map<String, Object> headers, Object message,
 | 
			
		||||
                                  String messageId, String exchangeName, String key) {
 | 
			
		||||
        // 自定义消息头
 | 
			
		||||
        MessageHeaders messageHeaders = new MessageHeaders(headers);
 | 
			
		||||
        // 创建消息
 | 
			
		||||
        Message<Object> msg = MessageBuilder.createMessage(message, messageHeaders);
 | 
			
		||||
        /* 确认的回调 确认消息是否到达 Broker 服务器 其实就是是否到达交换器
 | 
			
		||||
           如果发送时候指定的交换器不存在 ack就是false 代表消息不可达 */
 | 
			
		||||
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
 | 
			
		||||
            log.info("correlationData:{} , ack:{}", correlationData.getId(), ack);
 | 
			
		||||
            if (!ack) {
 | 
			
		||||
                System.out.println("进行对应的消息补偿机制");
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        /* 消息失败的回调
 | 
			
		||||
         * 例如消息已经到达交换器上,但路由键匹配任何绑定到该交换器的队列,会触发这个回调,此时 replyText: NO_ROUTE
 | 
			
		||||
         */
 | 
			
		||||
        rabbitTemplate.setReturnCallback((message1, replyCode, replyText, exchange, routingKey) -> {
 | 
			
		||||
            log.info("message:{}; replyCode: {}; replyText: {} ; exchange:{} ; routingKey:{}",
 | 
			
		||||
                    message1, replyCode, replyText, exchange, routingKey);
 | 
			
		||||
        });
 | 
			
		||||
        // 在实际中ID 应该是全局唯一 能够唯一标识消息 消息不可达的时候触发ConfirmCallback回调方法时可以获取该值,进行对应的错误处理
 | 
			
		||||
        CorrelationData correlationData = new CorrelationData(messageId);
 | 
			
		||||
        rabbitTemplate.convertAndSend(exchangeName, key, msg, correlationData);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.3  以单元测试的方式发送消息
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class RabbitmqProducerTests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RabbitmqProducer producer;
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 发送消息体为简单数据类型的消息
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void send() {
 | 
			
		||||
        Map<String, Object> heads = new HashMap<>();
 | 
			
		||||
        heads.put("msgInfo", "自定义消息头信息");
 | 
			
		||||
        // 模拟生成消息ID,在实际中应该是全局唯一的 消息不可达时候可以在setConfirmCallback回调中取得,可以进行对应的重发或错误处理
 | 
			
		||||
        String id = String.valueOf(Math.round(Math.random() * 10000));
 | 
			
		||||
        producer.sendSimpleMessage(heads, "hello Spring", id, RabbitInfo.EXCHANGE_NAME, "springboot.simple.abc");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 发送消息体为bean的消息
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void sendBean() {
 | 
			
		||||
        String id = String.valueOf(Math.round(Math.random() * 10000));
 | 
			
		||||
        Programmer programmer = new Programmer("xiaoMing", 12, 12123.45f, new Date());
 | 
			
		||||
        producer.sendSimpleMessage(null, programmer, id, RabbitBeanInfo.EXCHANGE_NAME, RabbitBeanInfo.ROUTING_KEY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 六、项目构建的说明
 | 
			
		||||
 | 
			
		||||
因为在项目中,consumer和producer模块均依赖公共模块,所以在构建consumer和producer项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
mvn install -Dmaven.test.skip = true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
consumer中 pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying</groupId>
 | 
			
		||||
        <artifactId>spring-boot-rabbitmq</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>rabbitmq-consumer</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>rabbitmq-consumer</name>
 | 
			
		||||
    <description>RabbitMQ consumer project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.heibaiying</groupId>
 | 
			
		||||
            <artifactId>rabbitmq-common</artifactId>
 | 
			
		||||
            <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
producer中 pom.xml如下
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying</groupId>
 | 
			
		||||
        <artifactId>spring-boot-rabbitmq</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>rabbitmq-producer</artifactId>
 | 
			
		||||
    <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>rabbitmq-producer</name>
 | 
			
		||||
    <description>RabbitMQ producer project for Spring Boot</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.heibaiying</groupId>
 | 
			
		||||
            <artifactId>rabbitmq-common</artifactId>
 | 
			
		||||
            <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
            <scope>compile</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,444 +1,476 @@
 | 
			
		||||
# spring boot 整合 redis
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
1. RedisConfig.java实现了redisTemplate 序列化与反序列化的配置;
 | 
			
		||||
2. RedisOperation和RedisObjectOperation分别封装了对基本类型和对象的操作。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-redis.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--redis starter -->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-data-redis</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--jackson 序列化包 -->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>com.fasterxml.jackson.core</groupId>
 | 
			
		||||
    <artifactId>jackson-databind</artifactId>
 | 
			
		||||
    <version>2.9.8</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、整合 Redis
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置redis数据源
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  redis:
 | 
			
		||||
    host: 127.0.0.1
 | 
			
		||||
    port: 6379
 | 
			
		||||
    # 默认采用的也是 0 号数据库 redis官方在4.0之后版本就不推荐采用单节点多数据库(db1-db15)的方式存储数据,如果有需要应该采用集群方式构建
 | 
			
		||||
    database: 0
 | 
			
		||||
 | 
			
		||||
# 如果是集群节点 采用如下配置指定节点
 | 
			
		||||
#spring.redis.cluster.nodes
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2  封装redis基本操作
 | 
			
		||||
 | 
			
		||||
需要说明的是spring boot 提供了两个template 用于操作redis:
 | 
			
		||||
 | 
			
		||||
- StringRedisTemplate:由于redis在大多数使用情况下都是操作字符串类型的存储,所以spring boot 将对字符串的操作单独封装在StringRedisTemplate ;
 | 
			
		||||
- RedisTemplate<Object, Object> :redis 用于操作任意类型的template。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : redis 基本操作
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
public class RedisOperation {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private StringRedisTemplate redisTemplate;
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作普通字符串
 | 
			
		||||
     */
 | 
			
		||||
    public void StringSet(String key, String value) {
 | 
			
		||||
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
 | 
			
		||||
        valueOperations.set(key, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作列表
 | 
			
		||||
     */
 | 
			
		||||
    public void ListSet(String key, List<String> values) {
 | 
			
		||||
        ListOperations<String, String> listOperations = redisTemplate.opsForList();
 | 
			
		||||
        values.forEach(value -> listOperations.leftPush(key, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作集合
 | 
			
		||||
     */
 | 
			
		||||
    public void SetSet(String key, Set<String> values) {
 | 
			
		||||
        SetOperations<String, String> setOperations = redisTemplate.opsForSet();
 | 
			
		||||
        values.forEach(value -> setOperations.add(key, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 获取字符串
 | 
			
		||||
     */
 | 
			
		||||
    public String StringGet(String key) {
 | 
			
		||||
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
 | 
			
		||||
        return valueOperations.get(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 列表弹出元素
 | 
			
		||||
     */
 | 
			
		||||
    public String ListLeftPop(String key) {
 | 
			
		||||
        ListOperations<String, String> listOperations = redisTemplate.opsForList();
 | 
			
		||||
        return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 集合弹出元素
 | 
			
		||||
     */
 | 
			
		||||
    public String SetPop(String key) {
 | 
			
		||||
        SetOperations<String, String> setOperations = redisTemplate.opsForSet();
 | 
			
		||||
        return setOperations.pop(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : redis 基本操作
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
public class RedisObjectOperation {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RedisTemplate<Object, Object> objectRedisTemplate;
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作对象
 | 
			
		||||
     */
 | 
			
		||||
    public void ObjectSet(Object key, Object value) {
 | 
			
		||||
        ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
 | 
			
		||||
        valueOperations.set(key, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作元素为对象列表
 | 
			
		||||
     */
 | 
			
		||||
    public void ListSet(Object key, List<Object> values) {
 | 
			
		||||
        ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
 | 
			
		||||
        values.forEach(value -> listOperations.leftPush(key, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作元素为对象集合
 | 
			
		||||
     */
 | 
			
		||||
    public void SetSet(Object key, Set<Object> values) {
 | 
			
		||||
        SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
 | 
			
		||||
        values.forEach(value -> setOperations.add(key, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 获取对象
 | 
			
		||||
     */
 | 
			
		||||
    public Object ObjectGet(Object key) {
 | 
			
		||||
        ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
 | 
			
		||||
        return valueOperations.get(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 列表弹出元素
 | 
			
		||||
     */
 | 
			
		||||
    public Object ListLeftPop(Object key) {
 | 
			
		||||
        ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
 | 
			
		||||
        return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 集合弹出元素
 | 
			
		||||
     */
 | 
			
		||||
    public Object SetPop(Object key) {
 | 
			
		||||
        SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
 | 
			
		||||
        return setOperations.pop(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 redisTemplate 序列化为json格式与反序列化
 | 
			
		||||
 | 
			
		||||
这里需要说明的spring boot 的 redisTemplate 本身是实现了对象的序列化与反序列化的,是支持直接存取对象的。但是这里的序列化默认采用的是JdkSerializationRedisSerializer.serialize()序列化为二进制码,这个本身是不影响redisTemplate 的操作的,因为redisTemplate在取出数据的时候会自动进行反序列化。
 | 
			
		||||
 | 
			
		||||
但是如果我们在命令行中使用get命令去获取数据时候,得到的是一串不直观的二进制码,所以我们尽量将其序列化为直观的json格式存储。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义序列化器
 | 
			
		||||
 * 不定义的话默认采用的是serializer.JdkSerializationRedisSerializer.serialize()序列化为二进制字节码 存储在数据库中不直观
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
public class RedisConfig {
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
 | 
			
		||||
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
 | 
			
		||||
        redisTemplate.setConnectionFactory(redisConnectionFactory);
 | 
			
		||||
 | 
			
		||||
        // 使用Jackson2JsonRedisSerialize  需要导入依赖 com.fasterxml.jackson.core jackson-databind
 | 
			
		||||
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
 | 
			
		||||
 | 
			
		||||
        ObjectMapper objectMapper = new ObjectMapper();
 | 
			
		||||
        // 第一个参数表示: 表示所有访问者都受到影响 包括 字段, getter / isGetter,setter,creator
 | 
			
		||||
        // 第二个参数表示: 所有类型的访问修饰符都是可接受的,不论是公有还有私有表变量都会被序列化
 | 
			
		||||
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
 | 
			
		||||
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
 | 
			
		||||
 | 
			
		||||
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
 | 
			
		||||
 | 
			
		||||
        // 设置key,value 序列化规则
 | 
			
		||||
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
 | 
			
		||||
        redisTemplate.setKeySerializer(new StringRedisSerializer());
 | 
			
		||||
        redisTemplate.afterPropertiesSet();
 | 
			
		||||
        return redisTemplate;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.4 测试
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class RedisTests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RedisOperation redisOperation;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void StringOperation() {
 | 
			
		||||
        redisOperation.StringSet("hello", "redis");
 | 
			
		||||
        String s = redisOperation.StringGet("hello");
 | 
			
		||||
        Assert.assertEquals(s, "redis");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void ListOperation() {
 | 
			
		||||
        redisOperation.ListSet("skill", Arrays.asList("java", "oracle", "vue"));
 | 
			
		||||
        String s = redisOperation.ListLeftPop("skill");
 | 
			
		||||
        Assert.assertEquals(s, "vue");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 需要注意的是Redis的集合(set)不仅不允许有重复元素,并且集合中的元素是无序的,
 | 
			
		||||
     * 不能通过索引下标获取元素。哪怕你在java中传入的集合是有序的newLinkedHashSet,但是实际在Redis存储的还是无序的集合
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void SetOperation() {
 | 
			
		||||
        redisOperation.SetSet("skillSet", Sets.newLinkedHashSet("java", "oracle", "vue"));
 | 
			
		||||
        String s = redisOperation.SetPop("skillSet");
 | 
			
		||||
        Assert.assertNotNull(s);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
## 附:Redis的数据结构和操作命令
 | 
			
		||||
 | 
			
		||||
### 1.1 预备
 | 
			
		||||
 | 
			
		||||
#### 1.1.1 全局命令
 | 
			
		||||
 | 
			
		||||
1. 查看所有键: **keys \*** 
 | 
			
		||||
 | 
			
		||||
2. 查看键总数:**dbsize**  
 | 
			
		||||
 | 
			
		||||
3. 检查键是否存在:**exists key**
 | 
			
		||||
 | 
			
		||||
4. 删除键:**del key [key ...]**   支持删除多个键
 | 
			
		||||
 | 
			
		||||
5. 键过期:**expire key seconds**
 | 
			
		||||
 | 
			
		||||
   ttl命令会返回键的剩余过期时间, 它有3种返回值:
 | 
			
		||||
 | 
			
		||||
   - 大于等于0的整数: 键剩余的过期时间。
 | 
			
		||||
   - -1: 键没设置过期时间。
 | 
			
		||||
   - -2: 键不存在 
 | 
			
		||||
 | 
			
		||||
6. 键的数据结构 **type key**
 | 
			
		||||
 | 
			
		||||
#### 1.1.2 数据结构和内部编码
 | 
			
		||||
 | 
			
		||||
type命令实际返回的就是当前键的数据结构类型, 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合) 
 | 
			
		||||
 | 
			
		||||
#### 1.1.3 单线程架构
 | 
			
		||||
 | 
			
		||||
1. 纯内存访问, Redis将所有数据放在内存中, 内存的响应时长大约为100纳秒, 这是Redis达到每秒万级别访问的重要基础。
 | 
			
		||||
2. 非阻塞I/O, Redis使用epoll作为I/O多路复用技术的实现, 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间, 如图2-6所示。 
 | 
			
		||||
3. 单线程避免了线程切换和竞态产生的消耗。 
 | 
			
		||||
 | 
			
		||||
### 1.2 字符串
 | 
			
		||||
 | 
			
		||||
| 作用                   | 格式                                                         | 参数或示例                                                   |
 | 
			
		||||
| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| 设置值                 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds: 为键设置秒级过期时间。 <br/>px milliseconds: 为键设置毫秒级过期时间。<br/>nx: 键必须不存在, 才可以设置成功, 用于添加。<br/>xx: 与nx相反, 键必须存在, 才可以设置成功, 用于更新。 |
 | 
			
		||||
| 获取值                 | get key                                                      | r如果获取的键不存在 ,则返回nil(空)                          |
 | 
			
		||||
| 批量设置               | mset key value [key value ...]                               | mset a 1 b 2 c 3 d 4                                         |
 | 
			
		||||
| 批量获取值             | mget key [key ...]                                           | mget a b c d                                                 |
 | 
			
		||||
| 计数                   | incr key decr key incrby key increment(指定数值自增)<br/>decrby key decrement(指定数值自减)<br/>incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。<br/>键不存在,创建键,并按照值为0自增或自减, 返回结果为1。 |
 | 
			
		||||
| 追加值                 | append key value                                             | 向字符串的默认追加值                                         |
 | 
			
		||||
| 字符串长度             | strlen key                                                   | 获取字符串长度,中文占用三个字节                             |
 | 
			
		||||
| 设置并返回原值         | getset key value                                             |                                                              |
 | 
			
		||||
| 设置指定位置的租字符串 | setrange key offeset value                                   |                                                              |
 | 
			
		||||
| 获取部分字符串         | getrange key start end                                       |                                                              |
 | 
			
		||||
 | 
			
		||||
### 1.3 哈希
 | 
			
		||||
 | 
			
		||||
| 作用                      | 格式                                                         | 参数或示例                                                   |
 | 
			
		||||
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| 设置值                    | hset key field value                                         | hset user:1 name tom<br/>hset user:1 age 12                  |
 | 
			
		||||
| 获取值                    | hget key field                                               | hget user:1 name                                             |
 | 
			
		||||
| 删除field                 | hdel key field [field ...]                                   |                                                              |
 | 
			
		||||
| 计算field个数             | hlen key                                                     |                                                              |
 | 
			
		||||
| 批量设置或获取field-value | hmget key field [field]<br/>hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin<br/>hmget user:1 name city |
 | 
			
		||||
| 判断field是否存在         | hexists key field                                            |                                                              |
 | 
			
		||||
| 获取所有field             | hkeys key                                                    |                                                              |
 | 
			
		||||
| 获取所有value             | hvals key                                                    |                                                              |
 | 
			
		||||
| 获取所有的filed-value     | hgetall key                                                  | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。<br/>获取全部 可以使用hscan命令, 该命令会渐进式遍历哈希类型 |
 | 
			
		||||
| 计数                      | hincrby key field<br/>hincrbyfloat key field                 |                                                              |
 | 
			
		||||
 | 
			
		||||
### 1.4 列表
 | 
			
		||||
 | 
			
		||||
| 作用     | 格式                                                         | 参数或示例                                                   |
 | 
			
		||||
| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| 增       | 左侧插入:lpush key value [value ...] 右侧插入:rpush key value [value ...] 某个指定元素前后插入:linsert key before\|after pivot value |                                                              |
 | 
			
		||||
| 查       | 获取指定范围内的元素列表:lrange key start end 获取列表指定索引下标的元素:lindex key index 获取列表指定长度:llen key | lrange listkey 0 -1                                          |
 | 
			
		||||
| 删       | 从列表左侧弹出元素:lpop key 从列表右侧弹出元素:rpop key 删除指定元素:lrem key count value 截取列表:ltrim key start end | count>0, 从左到右, 删除最多count个元素。<br/>count<0, 从右到左, 删除最多count绝对值个元素。<br/>count=0, 删除所有 |
 | 
			
		||||
| 改       | 修改指定索引下标的元素:lset key index newValue              |                                                              |
 | 
			
		||||
| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout      | key[key...]: 多个列表的键。 timeout: 阻塞时间\|等待时间(单位: 秒) |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.5 集合
 | 
			
		||||
 | 
			
		||||
集合(set) 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素**, 并且集合中的元素是无序的, **不能通过索引下标获取元素**。  
 | 
			
		||||
 | 
			
		||||
**集合内操作**:
 | 
			
		||||
 | 
			
		||||
| 作用                 | 格式                           | 参数或示例                                |
 | 
			
		||||
| -------------------- | ------------------------------ | ----------------------------------------- |
 | 
			
		||||
| 添加元素             | sadd key element [element ...] | 返回结果为添加成功的元素个数              |
 | 
			
		||||
| 删除元素             | srem key element [element ...] | 返回结果为成功删除的元素个数              |
 | 
			
		||||
| 计算元素个数         | scard key                      |                                           |
 | 
			
		||||
| 判断元素是否在集合中 | sismember key element          |                                           |
 | 
			
		||||
| 随机返回             | srandmember key [count]        | 随机从集合返回指定个数元素,count 默认为1 |
 | 
			
		||||
| 从集合随机弹出元素   | spop key                       | srandmember 不会从集合中删除元素,spop 会 |
 | 
			
		||||
| 获取集合中所有元素   | smembers key                   | 可用sscan 代替                            |
 | 
			
		||||
 | 
			
		||||
**集合间操作**:
 | 
			
		||||
 | 
			
		||||
| 作用                         | 格式                                                         |
 | 
			
		||||
| ---------------------------- | ------------------------------------------------------------ |
 | 
			
		||||
| 求多个集合的交集             | sinter key [key ...]                                         |
 | 
			
		||||
| 求多个集合的并集             | suinon key [key ...]                                         |
 | 
			
		||||
| 求多个集合的差集             | sdiff key [key ...]                                          |
 | 
			
		||||
| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...] <br/>suionstore destination key [key ...]<br/>sdiffstore destination key [key ...] |
 | 
			
		||||
 | 
			
		||||
### 1.6 有序集合
 | 
			
		||||
 | 
			
		||||
有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数(score) 作为排序的依据。  
 | 
			
		||||
 | 
			
		||||
**集合内操作**:
 | 
			
		||||
 | 
			
		||||
| 作用                     | 格式                                                         | 参数或示例                                                   |
 | 
			
		||||
| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| 添加成员                 | zadd key score member [score member ...]                     | nx: member必须不存在, 才可设置成功, 用于添加。<br> xx: member必须存在, 才可以设置成功, 用于更新。<br/>ch: 返回此次操作后, 有序集合元素和分数发生变化的个数<br/>incr: 对score做增加, 相当于后面介绍的zincrby。 |
 | 
			
		||||
| 计算成员个数             | zcard key                                                    |                                                              |
 | 
			
		||||
| 计算某个成员的分数       | zscore key member                                            |                                                              |
 | 
			
		||||
| 计算某个成员的排名       | zrank key member  zrevrank key member                        | zrank是从分数从低到高返回排名, zrevrank反之。               |
 | 
			
		||||
| 删除成员                 | zrem key member [member ...]                                 |                                                              |
 | 
			
		||||
| 增加成员分数             | zincrby key increment member                                 | zincrby user:ranking 9 tom                                   |
 | 
			
		||||
| 返回指定排名范围的成员   | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回, zrevrange反之。                       |
 | 
			
		||||
| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回, zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间(小括号) 和闭区间(中括号) , -inf和+inf分别代表无限小和无限大 |
 | 
			
		||||
| 删除指定排名内的升序元素 | zremrangerank key start end                                  |                                                              |
 | 
			
		||||
| 删除指定分数范围的成员   | zremrangebyscore key min max                                 |                                                              |
 | 
			
		||||
 | 
			
		||||
**集合间操作**:
 | 
			
		||||
 | 
			
		||||
| 作用 | 格式                                                         |
 | 
			
		||||
| ---- | ------------------------------------------------------------ |
 | 
			
		||||
| 交集 | zinterstore destination numkeys key \[key ...]  [weights weight [weight ...]] \[aggregate sum\|min\|max] |
 | 
			
		||||
| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
 | 
			
		||||
 | 
			
		||||
- destination: 交集计算结果保存到这个键。
 | 
			
		||||
- numkeys: 需要做交集计算键的个数。
 | 
			
		||||
- key[key...]: 需要做交集计算的键。 
 | 
			
		||||
- weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重, 每个键的权重默认是1。
 | 
			
		||||
- aggregate sum|min|max: 计算成员交集后, 分值可以按照sum(和) 、min(最小值) 、 max(最大值) 做汇总, 默认值是sum。 
 | 
			
		||||
 | 
			
		||||
### 1.7 键管理
 | 
			
		||||
 | 
			
		||||
#### 1.7.1 单个键管理
 | 
			
		||||
 | 
			
		||||
##### 1.键重命名  
 | 
			
		||||
 | 
			
		||||
**rename key newkey** 
 | 
			
		||||
 | 
			
		||||
 为了防止被强行rename, Redis提供了renamenx命令, 确保只有newKey不存在时候才被覆盖。
 | 
			
		||||
 | 
			
		||||
##### 2. 随机返回键 
 | 
			
		||||
 | 
			
		||||
 **random  key**
 | 
			
		||||
 | 
			
		||||
##### 3.键过期
 | 
			
		||||
 | 
			
		||||
- expire key seconds: 键在seconds秒后过期。
 | 
			
		||||
- expireat key timestamp: 键在秒级时间戳timestamp后过期。 
 | 
			
		||||
- pexpire key milliseconds: 键在milliseconds毫秒后过期。
 | 
			
		||||
- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期 
 | 
			
		||||
 | 
			
		||||
注意:
 | 
			
		||||
 | 
			
		||||
1. 如果expire key的键不存在, 返回结果为0 
 | 
			
		||||
2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样 
 | 
			
		||||
3. persist  key  t命令可以将键的过期时间清除 
 | 
			
		||||
4. 对于字符串类型键, 执行set命令会去掉过期时间, 这个问题很容易在开发中被忽视 
 | 
			
		||||
5. Redis不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
 | 
			
		||||
6. setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间  
 | 
			
		||||
 | 
			
		||||
#### 1.7.2 键遍历
 | 
			
		||||
 | 
			
		||||
##### 1. 全量键遍历
 | 
			
		||||
 | 
			
		||||
**keys pattern** 
 | 
			
		||||
 | 
			
		||||
##### 2. 渐进式遍历
 | 
			
		||||
 | 
			
		||||
scan cursor \[match pattern] \[count number] 
 | 
			
		||||
 | 
			
		||||
- cursor是必需参数, 实际上cursor是一个游标, 第一次遍历从0开始, 每次scan遍历完都会返回当前游标的值, 直到游标值为0, 表示遍历结束。
 | 
			
		||||
- match pattern是可选参数, 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。
 | 
			
		||||
- count number是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是10, 此参数可以适当增大。 
 | 
			
		||||
 | 
			
		||||
#### 1.7.3 数据库管理
 | 
			
		||||
 | 
			
		||||
##### 1.切换数据库
 | 
			
		||||
 | 
			
		||||
**select dbIndex**
 | 
			
		||||
 | 
			
		||||
##### 2.flushdb/flushall 
 | 
			
		||||
 | 
			
		||||
# spring boot 整合 redis
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构">1.1 项目结构</a><br/>
 | 
			
		||||
        <a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
 | 
			
		||||
<a href="#二整合-Redis">二、整合 Redis</a><br/>
 | 
			
		||||
        <a href="#21-在applicationyml-中配置redis数据源">2.1 在application.yml 中配置redis数据源</a><br/>
 | 
			
		||||
        <a href="#22--封装redis基本操作">2.2  封装redis基本操作</a><br/>
 | 
			
		||||
        <a href="#23-redisTemplate-序列化为json格式与反序列化">2.3 redisTemplate 序列化为json格式与反序列化</a><br/>
 | 
			
		||||
        <a href="#24-测试">2.4 测试</a><br/>
 | 
			
		||||
<a href="#附Redis的数据结构和操作命令">附:Redis的数据结构和操作命令</a><br/>
 | 
			
		||||
    <a href="#11-预备">1.1 预备</a><br/>
 | 
			
		||||
        <a href="#111-全局命令">1.1.1 全局命令</a><br/>
 | 
			
		||||
        <a href="#112-数据结构和内部编码">1.1.2 数据结构和内部编码</a><br/>
 | 
			
		||||
        <a href="#113-单线程架构">1.1.3 单线程架构</a><br/>
 | 
			
		||||
    <a href="#12-字符串">1.2 字符串</a><br/>
 | 
			
		||||
    <a href="#13-哈希">1.3 哈希</a><br/>
 | 
			
		||||
    <a href="#14-列表">1.4 列表</a><br/>
 | 
			
		||||
    <a href="#15-集合">1.5 集合</a><br/>
 | 
			
		||||
    <a href="#16-有序集合">1.6 有序集合</a><br/>
 | 
			
		||||
    <a href="#17-键管理">1.7 键管理</a><br/>
 | 
			
		||||
        <a href="#171-单个键管理">1.7.1 单个键管理</a><br/>
 | 
			
		||||
            <a href="#1键重命名">1.键重命名  </a><br/>
 | 
			
		||||
            <a href="#2-随机返回键">2. 随机返回键 </a><br/>
 | 
			
		||||
            <a href="#3键过期">3.键过期</a><br/>
 | 
			
		||||
        <a href="#172-键遍历">1.7.2 键遍历</a><br/>
 | 
			
		||||
            <a href="#1-全量键遍历">1. 全量键遍历</a><br/>
 | 
			
		||||
            <a href="#2-渐进式遍历">2. 渐进式遍历</a><br/>
 | 
			
		||||
        <a href="#173-数据库管理">1.7.3 数据库管理</a><br/>
 | 
			
		||||
            <a href="#1切换数据库">1.切换数据库</a><br/>
 | 
			
		||||
            <a href="#2flushdb/flushall">2.flushdb/flushall </a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构
 | 
			
		||||
 | 
			
		||||
1. RedisConfig.java实现了redisTemplate 序列化与反序列化的配置;
 | 
			
		||||
2. RedisOperation和RedisObjectOperation分别封装了对基本类型和对象的操作。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-redis.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--redis starter -->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-data-redis</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--jackson 序列化包 -->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>com.fasterxml.jackson.core</groupId>
 | 
			
		||||
    <artifactId>jackson-databind</artifactId>
 | 
			
		||||
    <version>2.9.8</version>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、整合 Redis
 | 
			
		||||
 | 
			
		||||
#### 2.1 在application.yml 中配置redis数据源
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  redis:
 | 
			
		||||
    host: 127.0.0.1
 | 
			
		||||
    port: 6379
 | 
			
		||||
    # 默认采用的也是 0 号数据库 redis官方在4.0之后版本就不推荐采用单节点多数据库(db1-db15)的方式存储数据,如果有需要应该采用集群方式构建
 | 
			
		||||
    database: 0
 | 
			
		||||
 | 
			
		||||
# 如果是集群节点 采用如下配置指定节点
 | 
			
		||||
#spring.redis.cluster.nodes
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2  封装redis基本操作
 | 
			
		||||
 | 
			
		||||
需要说明的是spring boot 提供了两个template 用于操作redis:
 | 
			
		||||
 | 
			
		||||
- StringRedisTemplate:由于redis在大多数使用情况下都是操作字符串类型的存储,所以spring boot 将对字符串的操作单独封装在StringRedisTemplate ;
 | 
			
		||||
- RedisTemplate<Object, Object> :redis 用于操作任意类型的template。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : redis 基本操作
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
public class RedisOperation {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private StringRedisTemplate redisTemplate;
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作普通字符串
 | 
			
		||||
     */
 | 
			
		||||
    public void StringSet(String key, String value) {
 | 
			
		||||
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
 | 
			
		||||
        valueOperations.set(key, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作列表
 | 
			
		||||
     */
 | 
			
		||||
    public void ListSet(String key, List<String> values) {
 | 
			
		||||
        ListOperations<String, String> listOperations = redisTemplate.opsForList();
 | 
			
		||||
        values.forEach(value -> listOperations.leftPush(key, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作集合
 | 
			
		||||
     */
 | 
			
		||||
    public void SetSet(String key, Set<String> values) {
 | 
			
		||||
        SetOperations<String, String> setOperations = redisTemplate.opsForSet();
 | 
			
		||||
        values.forEach(value -> setOperations.add(key, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 获取字符串
 | 
			
		||||
     */
 | 
			
		||||
    public String StringGet(String key) {
 | 
			
		||||
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
 | 
			
		||||
        return valueOperations.get(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 列表弹出元素
 | 
			
		||||
     */
 | 
			
		||||
    public String ListLeftPop(String key) {
 | 
			
		||||
        ListOperations<String, String> listOperations = redisTemplate.opsForList();
 | 
			
		||||
        return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 集合弹出元素
 | 
			
		||||
     */
 | 
			
		||||
    public String SetPop(String key) {
 | 
			
		||||
        SetOperations<String, String> setOperations = redisTemplate.opsForSet();
 | 
			
		||||
        return setOperations.pop(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : redis 基本操作
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
public class RedisObjectOperation {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RedisTemplate<Object, Object> objectRedisTemplate;
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作对象
 | 
			
		||||
     */
 | 
			
		||||
    public void ObjectSet(Object key, Object value) {
 | 
			
		||||
        ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
 | 
			
		||||
        valueOperations.set(key, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作元素为对象列表
 | 
			
		||||
     */
 | 
			
		||||
    public void ListSet(Object key, List<Object> values) {
 | 
			
		||||
        ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
 | 
			
		||||
        values.forEach(value -> listOperations.leftPush(key, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 操作元素为对象集合
 | 
			
		||||
     */
 | 
			
		||||
    public void SetSet(Object key, Set<Object> values) {
 | 
			
		||||
        SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
 | 
			
		||||
        values.forEach(value -> setOperations.add(key, value));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 获取对象
 | 
			
		||||
     */
 | 
			
		||||
    public Object ObjectGet(Object key) {
 | 
			
		||||
        ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
 | 
			
		||||
        return valueOperations.get(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 列表弹出元素
 | 
			
		||||
     */
 | 
			
		||||
    public Object ListLeftPop(Object key) {
 | 
			
		||||
        ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
 | 
			
		||||
        return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 集合弹出元素
 | 
			
		||||
     */
 | 
			
		||||
    public Object SetPop(Object key) {
 | 
			
		||||
        SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
 | 
			
		||||
        return setOperations.pop(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 redisTemplate 序列化为json格式与反序列化
 | 
			
		||||
 | 
			
		||||
这里需要说明的spring boot 的 redisTemplate 本身是实现了对象的序列化与反序列化的,是支持直接存取对象的。但是这里的序列化默认采用的是JdkSerializationRedisSerializer.serialize()序列化为二进制码,这个本身是不影响redisTemplate 的操作的,因为redisTemplate在取出数据的时候会自动进行反序列化。
 | 
			
		||||
 | 
			
		||||
但是如果我们在命令行中使用get命令去获取数据时候,得到的是一串不直观的二进制码,所以我们尽量将其序列化为直观的json格式存储。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义序列化器
 | 
			
		||||
 * 不定义的话默认采用的是serializer.JdkSerializationRedisSerializer.serialize()序列化为二进制字节码 存储在数据库中不直观
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
public class RedisConfig {
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
 | 
			
		||||
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
 | 
			
		||||
        redisTemplate.setConnectionFactory(redisConnectionFactory);
 | 
			
		||||
 | 
			
		||||
        // 使用Jackson2JsonRedisSerialize  需要导入依赖 com.fasterxml.jackson.core jackson-databind
 | 
			
		||||
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
 | 
			
		||||
 | 
			
		||||
        ObjectMapper objectMapper = new ObjectMapper();
 | 
			
		||||
        // 第一个参数表示: 表示所有访问者都受到影响 包括 字段, getter / isGetter,setter,creator
 | 
			
		||||
        // 第二个参数表示: 所有类型的访问修饰符都是可接受的,不论是公有还有私有表变量都会被序列化
 | 
			
		||||
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
 | 
			
		||||
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
 | 
			
		||||
 | 
			
		||||
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
 | 
			
		||||
 | 
			
		||||
        // 设置key,value 序列化规则
 | 
			
		||||
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
 | 
			
		||||
        redisTemplate.setKeySerializer(new StringRedisSerializer());
 | 
			
		||||
        redisTemplate.afterPropertiesSet();
 | 
			
		||||
        return redisTemplate;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.4 测试
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
@SpringBootTest
 | 
			
		||||
public class RedisTests {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RedisOperation redisOperation;
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void StringOperation() {
 | 
			
		||||
        redisOperation.StringSet("hello", "redis");
 | 
			
		||||
        String s = redisOperation.StringGet("hello");
 | 
			
		||||
        Assert.assertEquals(s, "redis");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void ListOperation() {
 | 
			
		||||
        redisOperation.ListSet("skill", Arrays.asList("java", "oracle", "vue"));
 | 
			
		||||
        String s = redisOperation.ListLeftPop("skill");
 | 
			
		||||
        Assert.assertEquals(s, "vue");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 需要注意的是Redis的集合(set)不仅不允许有重复元素,并且集合中的元素是无序的,
 | 
			
		||||
     * 不能通过索引下标获取元素。哪怕你在java中传入的集合是有序的newLinkedHashSet,但是实际在Redis存储的还是无序的集合
 | 
			
		||||
     */
 | 
			
		||||
    @Test
 | 
			
		||||
    public void SetOperation() {
 | 
			
		||||
        redisOperation.SetSet("skillSet", Sets.newLinkedHashSet("java", "oracle", "vue"));
 | 
			
		||||
        String s = redisOperation.SetPop("skillSet");
 | 
			
		||||
        Assert.assertNotNull(s);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<br/>
 | 
			
		||||
 | 
			
		||||
## 附:Redis的数据结构和操作命令
 | 
			
		||||
 | 
			
		||||
### 1.1 预备
 | 
			
		||||
 | 
			
		||||
#### 1.1.1 全局命令
 | 
			
		||||
 | 
			
		||||
1. 查看所有键: **keys \*** 
 | 
			
		||||
 | 
			
		||||
2. 查看键总数:**dbsize**  
 | 
			
		||||
 | 
			
		||||
3. 检查键是否存在:**exists key**
 | 
			
		||||
 | 
			
		||||
4. 删除键:**del key [key ...]**   支持删除多个键
 | 
			
		||||
 | 
			
		||||
5. 键过期:**expire key seconds**
 | 
			
		||||
 | 
			
		||||
   ttl命令会返回键的剩余过期时间, 它有3种返回值:
 | 
			
		||||
 | 
			
		||||
   - 大于等于0的整数: 键剩余的过期时间。
 | 
			
		||||
   - -1: 键没设置过期时间。
 | 
			
		||||
   - -2: 键不存在 
 | 
			
		||||
 | 
			
		||||
6. 键的数据结构 **type key**
 | 
			
		||||
 | 
			
		||||
#### 1.1.2 数据结构和内部编码
 | 
			
		||||
 | 
			
		||||
type命令实际返回的就是当前键的数据结构类型, 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合) 
 | 
			
		||||
 | 
			
		||||
#### 1.1.3 单线程架构
 | 
			
		||||
 | 
			
		||||
1. 纯内存访问, Redis将所有数据放在内存中, 内存的响应时长大约为100纳秒, 这是Redis达到每秒万级别访问的重要基础。
 | 
			
		||||
2. 非阻塞I/O, Redis使用epoll作为I/O多路复用技术的实现, 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间, 如图2-6所示。 
 | 
			
		||||
3. 单线程避免了线程切换和竞态产生的消耗。 
 | 
			
		||||
 | 
			
		||||
### 1.2 字符串
 | 
			
		||||
 | 
			
		||||
| 作用                   | 格式                                                         | 参数或示例                                                   |
 | 
			
		||||
| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| 设置值                 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds: 为键设置秒级过期时间。 <br/>px milliseconds: 为键设置毫秒级过期时间。<br/>nx: 键必须不存在, 才可以设置成功, 用于添加。<br/>xx: 与nx相反, 键必须存在, 才可以设置成功, 用于更新。 |
 | 
			
		||||
| 获取值                 | get key                                                      | r如果获取的键不存在 ,则返回nil(空)                          |
 | 
			
		||||
| 批量设置               | mset key value [key value ...]                               | mset a 1 b 2 c 3 d 4                                         |
 | 
			
		||||
| 批量获取值             | mget key [key ...]                                           | mget a b c d                                                 |
 | 
			
		||||
| 计数                   | incr key decr key incrby key increment(指定数值自增)<br/>decrby key decrement(指定数值自减)<br/>incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。<br/>键不存在,创建键,并按照值为0自增或自减, 返回结果为1。 |
 | 
			
		||||
| 追加值                 | append key value                                             | 向字符串的默认追加值                                         |
 | 
			
		||||
| 字符串长度             | strlen key                                                   | 获取字符串长度,中文占用三个字节                             |
 | 
			
		||||
| 设置并返回原值         | getset key value                                             |                                                              |
 | 
			
		||||
| 设置指定位置的租字符串 | setrange key offeset value                                   |                                                              |
 | 
			
		||||
| 获取部分字符串         | getrange key start end                                       |                                                              |
 | 
			
		||||
 | 
			
		||||
### 1.3 哈希
 | 
			
		||||
 | 
			
		||||
| 作用                      | 格式                                                         | 参数或示例                                                   |
 | 
			
		||||
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| 设置值                    | hset key field value                                         | hset user:1 name tom<br/>hset user:1 age 12                  |
 | 
			
		||||
| 获取值                    | hget key field                                               | hget user:1 name                                             |
 | 
			
		||||
| 删除field                 | hdel key field [field ...]                                   |                                                              |
 | 
			
		||||
| 计算field个数             | hlen key                                                     |                                                              |
 | 
			
		||||
| 批量设置或获取field-value | hmget key field [field]<br/>hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin<br/>hmget user:1 name city |
 | 
			
		||||
| 判断field是否存在         | hexists key field                                            |                                                              |
 | 
			
		||||
| 获取所有field             | hkeys key                                                    |                                                              |
 | 
			
		||||
| 获取所有value             | hvals key                                                    |                                                              |
 | 
			
		||||
| 获取所有的filed-value     | hgetall key                                                  | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。<br/>获取全部 可以使用hscan命令, 该命令会渐进式遍历哈希类型 |
 | 
			
		||||
| 计数                      | hincrby key field<br/>hincrbyfloat key field                 |                                                              |
 | 
			
		||||
 | 
			
		||||
### 1.4 列表
 | 
			
		||||
 | 
			
		||||
| 作用     | 格式                                                         | 参数或示例                                                   |
 | 
			
		||||
| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| 增       | 左侧插入:lpush key value [value ...] 右侧插入:rpush key value [value ...] 某个指定元素前后插入:linsert key before\|after pivot value |                                                              |
 | 
			
		||||
| 查       | 获取指定范围内的元素列表:lrange key start end 获取列表指定索引下标的元素:lindex key index 获取列表指定长度:llen key | lrange listkey 0 -1                                          |
 | 
			
		||||
| 删       | 从列表左侧弹出元素:lpop key 从列表右侧弹出元素:rpop key 删除指定元素:lrem key count value 截取列表:ltrim key start end | count>0, 从左到右, 删除最多count个元素。<br/>count<0, 从右到左, 删除最多count绝对值个元素。<br/>count=0, 删除所有 |
 | 
			
		||||
| 改       | 修改指定索引下标的元素:lset key index newValue              |                                                              |
 | 
			
		||||
| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout      | key[key...]: 多个列表的键。 timeout: 阻塞时间\|等待时间(单位: 秒) |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.5 集合
 | 
			
		||||
 | 
			
		||||
集合(set) 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素**, 并且集合中的元素是无序的, **不能通过索引下标获取元素**。  
 | 
			
		||||
 | 
			
		||||
**集合内操作**:
 | 
			
		||||
 | 
			
		||||
| 作用                 | 格式                           | 参数或示例                                |
 | 
			
		||||
| -------------------- | ------------------------------ | ----------------------------------------- |
 | 
			
		||||
| 添加元素             | sadd key element [element ...] | 返回结果为添加成功的元素个数              |
 | 
			
		||||
| 删除元素             | srem key element [element ...] | 返回结果为成功删除的元素个数              |
 | 
			
		||||
| 计算元素个数         | scard key                      |                                           |
 | 
			
		||||
| 判断元素是否在集合中 | sismember key element          |                                           |
 | 
			
		||||
| 随机返回             | srandmember key [count]        | 随机从集合返回指定个数元素,count 默认为1 |
 | 
			
		||||
| 从集合随机弹出元素   | spop key                       | srandmember 不会从集合中删除元素,spop 会 |
 | 
			
		||||
| 获取集合中所有元素   | smembers key                   | 可用sscan 代替                            |
 | 
			
		||||
 | 
			
		||||
**集合间操作**:
 | 
			
		||||
 | 
			
		||||
| 作用                         | 格式                                                         |
 | 
			
		||||
| ---------------------------- | ------------------------------------------------------------ |
 | 
			
		||||
| 求多个集合的交集             | sinter key [key ...]                                         |
 | 
			
		||||
| 求多个集合的并集             | suinon key [key ...]                                         |
 | 
			
		||||
| 求多个集合的差集             | sdiff key [key ...]                                          |
 | 
			
		||||
| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...] <br/>suionstore destination key [key ...]<br/>sdiffstore destination key [key ...] |
 | 
			
		||||
 | 
			
		||||
### 1.6 有序集合
 | 
			
		||||
 | 
			
		||||
有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数(score) 作为排序的依据。  
 | 
			
		||||
 | 
			
		||||
**集合内操作**:
 | 
			
		||||
 | 
			
		||||
| 作用                     | 格式                                                         | 参数或示例                                                   |
 | 
			
		||||
| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
 | 
			
		||||
| 添加成员                 | zadd key score member [score member ...]                     | nx: member必须不存在, 才可设置成功, 用于添加。<br> xx: member必须存在, 才可以设置成功, 用于更新。<br/>ch: 返回此次操作后, 有序集合元素和分数发生变化的个数<br/>incr: 对score做增加, 相当于后面介绍的zincrby。 |
 | 
			
		||||
| 计算成员个数             | zcard key                                                    |                                                              |
 | 
			
		||||
| 计算某个成员的分数       | zscore key member                                            |                                                              |
 | 
			
		||||
| 计算某个成员的排名       | zrank key member  zrevrank key member                        | zrank是从分数从低到高返回排名, zrevrank反之。               |
 | 
			
		||||
| 删除成员                 | zrem key member [member ...]                                 |                                                              |
 | 
			
		||||
| 增加成员分数             | zincrby key increment member                                 | zincrby user:ranking 9 tom                                   |
 | 
			
		||||
| 返回指定排名范围的成员   | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回, zrevrange反之。                       |
 | 
			
		||||
| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回, zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间(小括号) 和闭区间(中括号) , -inf和+inf分别代表无限小和无限大 |
 | 
			
		||||
| 删除指定排名内的升序元素 | zremrangerank key start end                                  |                                                              |
 | 
			
		||||
| 删除指定分数范围的成员   | zremrangebyscore key min max                                 |                                                              |
 | 
			
		||||
 | 
			
		||||
**集合间操作**:
 | 
			
		||||
 | 
			
		||||
| 作用 | 格式                                                         |
 | 
			
		||||
| ---- | ------------------------------------------------------------ |
 | 
			
		||||
| 交集 | zinterstore destination numkeys key \[key ...]  [weights weight [weight ...]] \[aggregate sum\|min\|max] |
 | 
			
		||||
| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
 | 
			
		||||
 | 
			
		||||
- destination: 交集计算结果保存到这个键。
 | 
			
		||||
- numkeys: 需要做交集计算键的个数。
 | 
			
		||||
- key[key...]: 需要做交集计算的键。 
 | 
			
		||||
- weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重, 每个键的权重默认是1。
 | 
			
		||||
- aggregate sum|min|max: 计算成员交集后, 分值可以按照sum(和) 、min(最小值) 、 max(最大值) 做汇总, 默认值是sum。 
 | 
			
		||||
 | 
			
		||||
### 1.7 键管理
 | 
			
		||||
 | 
			
		||||
#### 1.7.1 单个键管理
 | 
			
		||||
 | 
			
		||||
##### 1.键重命名  
 | 
			
		||||
 | 
			
		||||
**rename key newkey** 
 | 
			
		||||
 | 
			
		||||
 为了防止被强行rename, Redis提供了renamenx命令, 确保只有newKey不存在时候才被覆盖。
 | 
			
		||||
 | 
			
		||||
##### 2. 随机返回键 
 | 
			
		||||
 | 
			
		||||
 **random  key**
 | 
			
		||||
 | 
			
		||||
##### 3.键过期
 | 
			
		||||
 | 
			
		||||
- expire key seconds: 键在seconds秒后过期。
 | 
			
		||||
- expireat key timestamp: 键在秒级时间戳timestamp后过期。 
 | 
			
		||||
- pexpire key milliseconds: 键在milliseconds毫秒后过期。
 | 
			
		||||
- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期 
 | 
			
		||||
 | 
			
		||||
注意:
 | 
			
		||||
 | 
			
		||||
1. 如果expire key的键不存在, 返回结果为0 
 | 
			
		||||
2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样 
 | 
			
		||||
3. persist  key  t命令可以将键的过期时间清除 
 | 
			
		||||
4. 对于字符串类型键, 执行set命令会去掉过期时间, 这个问题很容易在开发中被忽视 
 | 
			
		||||
5. Redis不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
 | 
			
		||||
6. setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间  
 | 
			
		||||
 | 
			
		||||
#### 1.7.2 键遍历
 | 
			
		||||
 | 
			
		||||
##### 1. 全量键遍历
 | 
			
		||||
 | 
			
		||||
**keys pattern** 
 | 
			
		||||
 | 
			
		||||
##### 2. 渐进式遍历
 | 
			
		||||
 | 
			
		||||
scan cursor \[match pattern] \[count number] 
 | 
			
		||||
 | 
			
		||||
- cursor是必需参数, 实际上cursor是一个游标, 第一次遍历从0开始, 每次scan遍历完都会返回当前游标的值, 直到游标值为0, 表示遍历结束。
 | 
			
		||||
- match pattern是可选参数, 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。
 | 
			
		||||
- count number是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是10, 此参数可以适当增大。 
 | 
			
		||||
 | 
			
		||||
#### 1.7.3 数据库管理
 | 
			
		||||
 | 
			
		||||
##### 1.切换数据库
 | 
			
		||||
 | 
			
		||||
**select dbIndex**
 | 
			
		||||
 | 
			
		||||
##### 2.flushdb/flushall 
 | 
			
		||||
 | 
			
		||||
@@ -1,230 +1,242 @@
 | 
			
		||||
# spring boot 整合 servlet 
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
1. 项目提供与servlet整合的两种方式,一种是servlet3.0 原生的注解方式,一种是采用spring 注册的方式;
 | 
			
		||||
2. servlet、过滤器、监听器分别位于servlet、filter、listen 下,其中以Annotation命名结尾的代表是servlet注解方式实现,采用spring注册方式则在ServletConfig中进行注册;
 | 
			
		||||
3. 为了说明外置容器对servlet注解的自动发现机制,项目采用外置容器构建,关于spring boot 整合外置容器的详细说明可以参考[spring-boot-tomcat](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-tomcat)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-servlet.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
            <!--排除依赖 使用外部tomcat容器启动-->
 | 
			
		||||
            <exclusions>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
                    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                    <artifactId>spring-boot-starter-tomcat</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
            </exclusions>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
 | 
			
		||||
            <groupId>javax.servlet</groupId>
 | 
			
		||||
            <artifactId>servlet-api</artifactId>
 | 
			
		||||
            <version>2.5</version>
 | 
			
		||||
            <scope>provided</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--servlet api 注解依赖包-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>javax.servlet</groupId>
 | 
			
		||||
            <artifactId>javax.servlet-api</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 二、采用spring 注册方式整合 servlet
 | 
			
		||||
 | 
			
		||||
#### 2.1 新建过滤器、监听器和servlet
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义过滤器
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
public class CustomFilter implements Filter {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void init(FilterConfig filterConfig) throws ServletException {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 | 
			
		||||
        request.setAttribute("filterParam","我是filter传递的参数");
 | 
			
		||||
        chain.doFilter(request,response);
 | 
			
		||||
        response.getWriter().append(" CustomFilter ");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void destroy() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义监听器
 | 
			
		||||
 */
 | 
			
		||||
public class CustomListen implements ServletContextListener {
 | 
			
		||||
 | 
			
		||||
    //Web应用程序初始化过程正在启动的通知
 | 
			
		||||
    @Override
 | 
			
		||||
    public void contextInitialized(ServletContextEvent sce) {
 | 
			
		||||
        System.out.println("容器初始化启动");
 | 
			
		||||
    }
 | 
			
		||||
# spring boot 整合 servlet 
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
 | 
			
		||||
        <a href="#12-项目依赖">1.2 项目依赖</a><br/>
 | 
			
		||||
<a href="#二采用spring-注册方式整合-servlet">二、采用spring 注册方式整合 servlet</a><br/>
 | 
			
		||||
        <a href="#21-新建过滤器监听器和servlet">2.1 新建过滤器、监听器和servlet</a><br/>
 | 
			
		||||
        <a href="#22-注册过滤器监听器和servlet">2.2 注册过滤器、监听器和servlet</a><br/>
 | 
			
		||||
<a href="#三采用注解方式整合-servlet">三、采用注解方式整合 servlet</a><br/>
 | 
			
		||||
        <a href="#31-新建过滤器监听器和servlet分别使用@WebFilter@WebListener@WebServlet注解标注">3.1 新建过滤器、监听器和servlet,分别使用@WebFilter、@WebListener、@WebServlet注解标注</a><br/>
 | 
			
		||||
        <a href="#32-使注解生效">3.2 使注解生效</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /* 通知servlet上下文即将关闭
 | 
			
		||||
     * 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void contextDestroyed(ServletContextEvent sce) {
 | 
			
		||||
        System.out.println("容器即将销毁");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义servlet
 | 
			
		||||
 */
 | 
			
		||||
public class CustomServlet extends HttpServlet {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 | 
			
		||||
        System.out.println("doGet 执行:" + req.getAttribute("filterParam"));
 | 
			
		||||
        resp.getWriter().append("CustomServlet");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 | 
			
		||||
        doGet(req, resp);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 注册过滤器、监听器和servlet
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
public class ServletConfig {
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public ServletRegistrationBean registrationBean() {
 | 
			
		||||
        return new ServletRegistrationBean<HttpServlet>(new CustomServlet(), "/servlet");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public FilterRegistrationBean filterRegistrationBean() {
 | 
			
		||||
        FilterRegistrationBean bean = new FilterRegistrationBean<Filter>();
 | 
			
		||||
        bean.setFilter(new CustomFilter());
 | 
			
		||||
        bean.addUrlPatterns("/servlet");
 | 
			
		||||
        return bean;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public ServletListenerRegistrationBean listenerRegistrationBean() {
 | 
			
		||||
        return new ServletListenerRegistrationBean<ServletContextListener>(new CustomListen());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 三、采用注解方式整合 servlet
 | 
			
		||||
 | 
			
		||||
#### 3.1 新建过滤器、监听器和servlet,分别使用@WebFilter、@WebListener、@WebServlet注解标注
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义过滤器
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@WebFilter(urlPatterns = "/servletAnn")
 | 
			
		||||
public class CustomFilterAnnotation implements Filter {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void init(FilterConfig filterConfig) throws ServletException {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 | 
			
		||||
        chain.doFilter(request,response);
 | 
			
		||||
        response.getWriter().append(" CustomFilter Annotation");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void destroy() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description :自定义监听器
 | 
			
		||||
 */
 | 
			
		||||
@WebListener
 | 
			
		||||
public class CustomListenAnnotation implements ServletContextListener {
 | 
			
		||||
 | 
			
		||||
    //Web应用程序初始化过程正在启动的通知
 | 
			
		||||
    @Override
 | 
			
		||||
    public void contextInitialized(ServletContextEvent sce) {
 | 
			
		||||
        System.out.println("容器初始化启动 Annotation");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /* 通知servlet上下文即将关闭
 | 
			
		||||
     * 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void contextDestroyed(ServletContextEvent sce) {
 | 
			
		||||
        System.out.println("容器即将销毁 Annotation");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义servlet
 | 
			
		||||
 */
 | 
			
		||||
@WebServlet(urlPatterns = "/servletAnn")
 | 
			
		||||
public class CustomServletAnnotation extends HttpServlet {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 | 
			
		||||
        resp.getWriter().append("CustomServlet Annotation");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 | 
			
		||||
        doGet(req, resp);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 使注解生效
 | 
			
		||||
 | 
			
		||||
1. 如果是内置容器,需要在启动类上添加@ServletComponentScan("com.heibaiying.springbootservlet") ,指定扫描的包目录;
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
1. 项目提供与servlet整合的两种方式,一种是servlet3.0 原生的注解方式,一种是采用spring 注册的方式;
 | 
			
		||||
2. servlet、过滤器、监听器分别位于servlet、filter、listen 下,其中以Annotation命名结尾的代表是servlet注解方式实现,采用spring注册方式则在ServletConfig中进行注册;
 | 
			
		||||
3. 为了说明外置容器对servlet注解的自动发现机制,项目采用外置容器构建,关于spring boot 整合外置容器的详细说明可以参考[spring-boot-tomcat](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-tomcat)
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-servlet.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
            <!--排除依赖 使用外部tomcat容器启动-->
 | 
			
		||||
            <exclusions>
 | 
			
		||||
                <exclusion>
 | 
			
		||||
                    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                    <artifactId>spring-boot-starter-tomcat</artifactId>
 | 
			
		||||
                </exclusion>
 | 
			
		||||
            </exclusions>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
 | 
			
		||||
            <groupId>javax.servlet</groupId>
 | 
			
		||||
            <artifactId>servlet-api</artifactId>
 | 
			
		||||
            <version>2.5</version>
 | 
			
		||||
            <scope>provided</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--servlet api 注解依赖包-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>javax.servlet</groupId>
 | 
			
		||||
            <artifactId>javax.servlet-api</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 二、采用spring 注册方式整合 servlet
 | 
			
		||||
 | 
			
		||||
#### 2.1 新建过滤器、监听器和servlet
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义过滤器
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
public class CustomFilter implements Filter {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void init(FilterConfig filterConfig) throws ServletException {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 | 
			
		||||
        request.setAttribute("filterParam","我是filter传递的参数");
 | 
			
		||||
        chain.doFilter(request,response);
 | 
			
		||||
        response.getWriter().append(" CustomFilter ");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void destroy() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义监听器
 | 
			
		||||
 */
 | 
			
		||||
public class CustomListen implements ServletContextListener {
 | 
			
		||||
 | 
			
		||||
    //Web应用程序初始化过程正在启动的通知
 | 
			
		||||
    @Override
 | 
			
		||||
    public void contextInitialized(ServletContextEvent sce) {
 | 
			
		||||
        System.out.println("容器初始化启动");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /* 通知servlet上下文即将关闭
 | 
			
		||||
     * 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void contextDestroyed(ServletContextEvent sce) {
 | 
			
		||||
        System.out.println("容器即将销毁");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义servlet
 | 
			
		||||
 */
 | 
			
		||||
public class CustomServlet extends HttpServlet {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 | 
			
		||||
        System.out.println("doGet 执行:" + req.getAttribute("filterParam"));
 | 
			
		||||
        resp.getWriter().append("CustomServlet");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 | 
			
		||||
        doGet(req, resp);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 注册过滤器、监听器和servlet
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 */
 | 
			
		||||
@Configuration
 | 
			
		||||
public class ServletConfig {
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public ServletRegistrationBean registrationBean() {
 | 
			
		||||
        return new ServletRegistrationBean<HttpServlet>(new CustomServlet(), "/servlet");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public FilterRegistrationBean filterRegistrationBean() {
 | 
			
		||||
        FilterRegistrationBean bean = new FilterRegistrationBean<Filter>();
 | 
			
		||||
        bean.setFilter(new CustomFilter());
 | 
			
		||||
        bean.addUrlPatterns("/servlet");
 | 
			
		||||
        return bean;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public ServletListenerRegistrationBean listenerRegistrationBean() {
 | 
			
		||||
        return new ServletListenerRegistrationBean<ServletContextListener>(new CustomListen());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 三、采用注解方式整合 servlet
 | 
			
		||||
 | 
			
		||||
#### 3.1 新建过滤器、监听器和servlet,分别使用@WebFilter、@WebListener、@WebServlet注解标注
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义过滤器
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@WebFilter(urlPatterns = "/servletAnn")
 | 
			
		||||
public class CustomFilterAnnotation implements Filter {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void init(FilterConfig filterConfig) throws ServletException {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 | 
			
		||||
        chain.doFilter(request,response);
 | 
			
		||||
        response.getWriter().append(" CustomFilter Annotation");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void destroy() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description :自定义监听器
 | 
			
		||||
 */
 | 
			
		||||
@WebListener
 | 
			
		||||
public class CustomListenAnnotation implements ServletContextListener {
 | 
			
		||||
 | 
			
		||||
    //Web应用程序初始化过程正在启动的通知
 | 
			
		||||
    @Override
 | 
			
		||||
    public void contextInitialized(ServletContextEvent sce) {
 | 
			
		||||
        System.out.println("容器初始化启动 Annotation");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /* 通知servlet上下文即将关闭
 | 
			
		||||
     * 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void contextDestroyed(ServletContextEvent sce) {
 | 
			
		||||
        System.out.println("容器即将销毁 Annotation");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义servlet
 | 
			
		||||
 */
 | 
			
		||||
@WebServlet(urlPatterns = "/servletAnn")
 | 
			
		||||
public class CustomServletAnnotation extends HttpServlet {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 | 
			
		||||
        resp.getWriter().append("CustomServlet Annotation");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 | 
			
		||||
        doGet(req, resp);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 使注解生效
 | 
			
		||||
 | 
			
		||||
1. 如果是内置容器,需要在启动类上添加@ServletComponentScan("com.heibaiying.springbootservlet") ,指定扫描的包目录;
 | 
			
		||||
@@ -1,101 +1,110 @@
 | 
			
		||||
# spring boot 整合 tomcat
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
spring boot 整合 tomcat 后支持jsp 的使用(内置容器默认是不支持jsp),所以项目整合后采用jspController 跳转到show.jsp测试整合是否成功。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-tomcat.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
    <!--排除内置容器依赖 使用外部tomcat容器启动-->
 | 
			
		||||
    <exclusions>
 | 
			
		||||
        <exclusion>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-tomcat</artifactId>
 | 
			
		||||
        </exclusion>
 | 
			
		||||
    </exclusions>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
 | 
			
		||||
    <groupId>javax.servlet</groupId>
 | 
			
		||||
    <artifactId>servlet-api</artifactId>
 | 
			
		||||
    <version>2.5</version>
 | 
			
		||||
    <scope>provided</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 二、整合 tomcat
 | 
			
		||||
 | 
			
		||||
#### 2.1 修改启动类,继承自SpringBootServletInitializer,并覆盖重写其中configure方法
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * 如果用外置tomcat,启动报错java.lang.NoClassDefFoundError: javax/el/ELManager
 | 
			
		||||
 * 是因为tomcat 7.0 el-api包中没有ELManager类 , 切换tomcat 为8.0 以上版本即可
 | 
			
		||||
 */
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
public class SpringBootTomcatApplication extends SpringBootServletInitializer {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
 | 
			
		||||
        //传入SpringBoot应用的主程序
 | 
			
		||||
        return application.sources(SpringBootTomcatApplication.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(SpringBootTomcatApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀 
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
spring:
 | 
			
		||||
  mvc:
 | 
			
		||||
    view:
 | 
			
		||||
      prefix: /WEB-INF/jsp/
 | 
			
		||||
      suffix: .jsp
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 新建controller和show.jsp 测试整合是否成功
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("index")
 | 
			
		||||
public class JspController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping
 | 
			
		||||
    public String jsp(Model model){
 | 
			
		||||
        Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
 | 
			
		||||
        model.addAttribute("programmer",programmer);
 | 
			
		||||
        return "show";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```jsp
 | 
			
		||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <title>programmer</title>
 | 
			
		||||
    <link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<ul>
 | 
			
		||||
    <li>姓名: ${programmer.name}</li>
 | 
			
		||||
    <li>年龄: ${programmer.age}</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
# spring boot 整合 tomcat
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
        <a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
 | 
			
		||||
        <a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
 | 
			
		||||
<a href="#二整合-tomcat">二、整合 tomcat</a><br/>
 | 
			
		||||
        <a href="#21-修改启动类继承自SpringBootServletInitializer并覆盖重写其中configure方法">2.1 修改启动类,继承自SpringBootServletInitializer,并覆盖重写其中configure方法</a><br/>
 | 
			
		||||
        <a href="#22-在applicationyml-中指定访问视图文件的前缀和后缀">2.2 在application.yml 中指定访问视图文件的前缀和后缀 </a><br/>
 | 
			
		||||
        <a href="#23-新建controller和showjsp-测试整合是否成功">2.3 新建controller和show.jsp 测试整合是否成功</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
#### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
spring boot 整合 tomcat 后支持jsp 的使用(内置容器默认是不支持jsp),所以项目整合后采用jspController 跳转到show.jsp测试整合是否成功。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-tomcat.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 1.2 项目主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
    <!--排除内置容器依赖 使用外部tomcat容器启动-->
 | 
			
		||||
    <exclusions>
 | 
			
		||||
        <exclusion>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-tomcat</artifactId>
 | 
			
		||||
        </exclusion>
 | 
			
		||||
    </exclusions>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
 | 
			
		||||
    <groupId>javax.servlet</groupId>
 | 
			
		||||
    <artifactId>servlet-api</artifactId>
 | 
			
		||||
    <version>2.5</version>
 | 
			
		||||
    <scope>provided</scope>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 二、整合 tomcat
 | 
			
		||||
 | 
			
		||||
#### 2.1 修改启动类,继承自SpringBootServletInitializer,并覆盖重写其中configure方法
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * 如果用外置tomcat,启动报错java.lang.NoClassDefFoundError: javax/el/ELManager
 | 
			
		||||
 * 是因为tomcat 7.0 el-api包中没有ELManager类 , 切换tomcat 为8.0 以上版本即可
 | 
			
		||||
 */
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
public class SpringBootTomcatApplication extends SpringBootServletInitializer {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
 | 
			
		||||
        //传入SpringBoot应用的主程序
 | 
			
		||||
        return application.sources(SpringBootTomcatApplication.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(SpringBootTomcatApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀 
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
spring:
 | 
			
		||||
  mvc:
 | 
			
		||||
    view:
 | 
			
		||||
      prefix: /WEB-INF/jsp/
 | 
			
		||||
      suffix: .jsp
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 新建controller和show.jsp 测试整合是否成功
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("index")
 | 
			
		||||
public class JspController {
 | 
			
		||||
 | 
			
		||||
    @RequestMapping
 | 
			
		||||
    public String jsp(Model model){
 | 
			
		||||
        Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
 | 
			
		||||
        model.addAttribute("programmer",programmer);
 | 
			
		||||
        return "show";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```jsp
 | 
			
		||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <title>programmer</title>
 | 
			
		||||
    <link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<ul>
 | 
			
		||||
    <li>姓名: ${programmer.name}</li>
 | 
			
		||||
    <li>年龄: ${programmer.age}</li>
 | 
			
		||||
</ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,183 +1,193 @@
 | 
			
		||||
# spring boot websocket
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
 | 
			
		||||
2. 关于websocket的主要配置在websocket文件夹下;
 | 
			
		||||
3. 模板引擎采用freemaker;
 | 
			
		||||
4. 项目以web的方式构建。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-websocket.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.2 主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--spring boot webSocket 的依赖包 -->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-websocket</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-freemarker</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、spring boot websocket
 | 
			
		||||
 | 
			
		||||
#### 2.1 创建消息处理类ChatSocket,使用@ServerEndpoint声明websocket服务
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@ServerEndpoint(value = "/socket/{username}")
 | 
			
		||||
@Component
 | 
			
		||||
public class ChatSocket {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 建立连接时候触发
 | 
			
		||||
     */
 | 
			
		||||
    @OnOpen
 | 
			
		||||
    public void onOpen(Session session, @PathParam("username") String username) {
 | 
			
		||||
        // 这个方法是线程不安全的
 | 
			
		||||
        Constant.nameAndSession.putIfAbsent(username, session);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 关闭连接时候触发
 | 
			
		||||
     */
 | 
			
		||||
    @OnClose
 | 
			
		||||
    public void onClose(Session session, @PathParam("username") String username) {
 | 
			
		||||
        Constant.nameAndSession.remove(username);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 处理消息
 | 
			
		||||
     */
 | 
			
		||||
    @OnMessage
 | 
			
		||||
    public void onMessage(Session session, String message, @PathParam("username") String username) throws UnsupportedEncodingException {
 | 
			
		||||
        // 防止中文乱码
 | 
			
		||||
        String msg = URLDecoder.decode(message, "utf-8");
 | 
			
		||||
        // 简单模拟群发消息
 | 
			
		||||
        Constant.nameAndSession.forEach((s, webSocketSession)
 | 
			
		||||
                -> {
 | 
			
		||||
            try {
 | 
			
		||||
                webSocketSession.getBasicRemote().sendText(username + " : " + msg);
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                e.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 配置ServerEndpointExporter,ServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Configuration
 | 
			
		||||
public class WebSocketConfig {
 | 
			
		||||
 | 
			
		||||
    /***
 | 
			
		||||
     * 检测{@link javax.websocket.server.ServerEndpointConfig}和{@link ServerEndpoint} 类型的bean,
 | 
			
		||||
     * 并在运行时使用标准Java WebSocket时注册。
 | 
			
		||||
     * 我们在{@link com.heibaiying.springboot.websocket.WebSocketConfig}中就是使用@ServerEndpoint去声明websocket服务
 | 
			
		||||
     */
 | 
			
		||||
    @Bean
 | 
			
		||||
    public ServerEndpointExporter serverEndpointExporter() {
 | 
			
		||||
        return new ServerEndpointExporter();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 前端websocket的实现
 | 
			
		||||
 | 
			
		||||
```jsp
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <title>${Session["username"]}您好!欢迎进入群聊大厅!</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <h5>${Session["username"]}您好!欢迎进入群聊大厅!</h5>
 | 
			
		||||
    <input id="message" type="text">
 | 
			
		||||
    <button id="btn">发送消息</button>
 | 
			
		||||
    <div id="show">
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
<script>
 | 
			
		||||
    let btn = document.getElementById("btn");
 | 
			
		||||
    let message = document.getElementById("message");
 | 
			
		||||
    let show = document.getElementById("show");
 | 
			
		||||
    let ws = new WebSocket("ws://localhost:8080/socket/${Session["username"]}");
 | 
			
		||||
    ws.onmessage = function (evt) {
 | 
			
		||||
        let node = document.createElement("div");
 | 
			
		||||
        node.innerHTML = "<h5>" + evt.data + "</h5>";
 | 
			
		||||
        show.appendChild(node);
 | 
			
		||||
    };
 | 
			
		||||
    btn.addEventListener("click", function () {
 | 
			
		||||
        let data = message.value;
 | 
			
		||||
        console.log(data);
 | 
			
		||||
        if (data) {
 | 
			
		||||
            ws.send(encodeURI(data));
 | 
			
		||||
        } else {
 | 
			
		||||
            alert("请输入消息后发送");
 | 
			
		||||
        }
 | 
			
		||||
        message.value = "";
 | 
			
		||||
    });
 | 
			
		||||
    // 关闭页面时候关闭ws
 | 
			
		||||
    window.addEventListener("beforeunload", function (event) {
 | 
			
		||||
        ws.close();
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.4 简单登录的实现
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <title>Title</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<form action="/login" method="post">
 | 
			
		||||
    <input name="username" type="text">
 | 
			
		||||
    <button id="btn">输入临时用户名后登录!</button>
 | 
			
		||||
</form>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @description : 简单登录
 | 
			
		||||
 */
 | 
			
		||||
@Controller
 | 
			
		||||
public class LoginController {
 | 
			
		||||
 | 
			
		||||
    @PostMapping("login")
 | 
			
		||||
    public String login(String username, HttpSession session) {
 | 
			
		||||
        session.setAttribute(Constant.USER_NAME, username);
 | 
			
		||||
        return "chat";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping
 | 
			
		||||
    public String index() {
 | 
			
		||||
        return "index";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
# spring boot websocket
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一说明">一、说明</a><br/>
 | 
			
		||||
    <a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
 | 
			
		||||
    <a href="#12-主要依赖">1.2 主要依赖</a><br/>
 | 
			
		||||
<a href="#二spring-boot-websocket">二、spring boot websocket</a><br/>
 | 
			
		||||
        <a href="#21-创建消息处理类ChatSocket使用@ServerEndpoint声明websocket服务">2.1 创建消息处理类ChatSocket,使用@ServerEndpoint声明websocket服务</a><br/>
 | 
			
		||||
        <a href="#22-配置ServerEndpointExporterServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务。">2.2 配置ServerEndpointExporter,ServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务。</a><br/>
 | 
			
		||||
        <a href="#23-前端websocket的实现">2.3 前端websocket的实现</a><br/>
 | 
			
		||||
        <a href="#24-简单登录的实现">2.4 简单登录的实现</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
## 一、说明
 | 
			
		||||
 | 
			
		||||
### 1.1 项目结构说明
 | 
			
		||||
 | 
			
		||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
 | 
			
		||||
2. 关于websocket的主要配置在websocket文件夹下;
 | 
			
		||||
3. 模板引擎采用freemaker;
 | 
			
		||||
4. 项目以web的方式构建。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-websocket.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 1.2 主要依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--spring boot webSocket 的依赖包 -->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-websocket</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-freemarker</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、spring boot websocket
 | 
			
		||||
 | 
			
		||||
#### 2.1 创建消息处理类ChatSocket,使用@ServerEndpoint声明websocket服务
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@ServerEndpoint(value = "/socket/{username}")
 | 
			
		||||
@Component
 | 
			
		||||
public class ChatSocket {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 建立连接时候触发
 | 
			
		||||
     */
 | 
			
		||||
    @OnOpen
 | 
			
		||||
    public void onOpen(Session session, @PathParam("username") String username) {
 | 
			
		||||
        // 这个方法是线程不安全的
 | 
			
		||||
        Constant.nameAndSession.putIfAbsent(username, session);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 关闭连接时候触发
 | 
			
		||||
     */
 | 
			
		||||
    @OnClose
 | 
			
		||||
    public void onClose(Session session, @PathParam("username") String username) {
 | 
			
		||||
        Constant.nameAndSession.remove(username);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 处理消息
 | 
			
		||||
     */
 | 
			
		||||
    @OnMessage
 | 
			
		||||
    public void onMessage(Session session, String message, @PathParam("username") String username) throws UnsupportedEncodingException {
 | 
			
		||||
        // 防止中文乱码
 | 
			
		||||
        String msg = URLDecoder.decode(message, "utf-8");
 | 
			
		||||
        // 简单模拟群发消息
 | 
			
		||||
        Constant.nameAndSession.forEach((s, webSocketSession)
 | 
			
		||||
                -> {
 | 
			
		||||
            try {
 | 
			
		||||
                webSocketSession.getBasicRemote().sendText(username + " : " + msg);
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                e.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.2 配置ServerEndpointExporter,ServerEndpointExporter会在运行时候自动注册我们用@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";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,177 +1,193 @@
 | 
			
		||||
# spring-boot-yml-profile
 | 
			
		||||
 | 
			
		||||
## 一、项目结构
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-yml-profile.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 二、常用 yaml 语法讲解
 | 
			
		||||
 | 
			
		||||
项目中的yml配置文件如下:
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
programmer:
 | 
			
		||||
  name: xiaoming-DEV
 | 
			
		||||
  married: false
 | 
			
		||||
  hireDate: 2018/12/23
 | 
			
		||||
  salary: 66666.88
 | 
			
		||||
  random: ${random.int[1024,65536]}
 | 
			
		||||
  skill: {java: master, jquery: proficiency}
 | 
			
		||||
  company: [baidu,tengxun,alibaba]
 | 
			
		||||
  school:
 | 
			
		||||
    name: unviersity
 | 
			
		||||
    location: shanghai
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.1 基本规则
 | 
			
		||||
 | 
			
		||||
1. 大小写敏感 
 | 
			
		||||
2. 使用缩进表示层级关系 
 | 
			
		||||
3. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。 
 | 
			
		||||
4. 使用#表示注释 
 | 
			
		||||
5. 字符串默认不用加单双引号,但单引号和双引号都可以使用,双引号不会对特殊字符转义。
 | 
			
		||||
6. YAML中提供了多种常量结构,包括:整数,浮点数,字符串,NULL,日期,布尔,时间。
 | 
			
		||||
 | 
			
		||||
#### 2.2 对象的写法
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
key: value
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 map的写法
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
# 写法一 同一缩进的所有键值对属于一个map
 | 
			
		||||
key: 
 | 
			
		||||
    key1: value1
 | 
			
		||||
    key2: value2
 | 
			
		||||
 | 
			
		||||
# 写法二
 | 
			
		||||
{key1: value1, key2: value2}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 数组的写法
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
# 写法一 使用一个短横线加一个空格代表一个数组项
 | 
			
		||||
- a
 | 
			
		||||
- b
 | 
			
		||||
- c
 | 
			
		||||
 | 
			
		||||
# 写法二
 | 
			
		||||
[a,b,c]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.5 单双引号
 | 
			
		||||
 | 
			
		||||
单引号和双引号都可以使用,双引号不会对特殊字符转义。
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
s1: '内容\n字符串'
 | 
			
		||||
s2: "内容\n字符串"
 | 
			
		||||
 | 
			
		||||
转换后:
 | 
			
		||||
{ s1: '内容\\n字符串', s2: '内容\n字符串' }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.6 特殊符号
 | 
			
		||||
 | 
			
		||||
---  YAML可以在同一个文件中,使用---表示一个文档的开始。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、spring boot 与 yaml
 | 
			
		||||
 | 
			
		||||
#### 3.1  spring boot 支持使用 ${app.name} 引用预先定义的值
 | 
			
		||||
 | 
			
		||||
```properties
 | 
			
		||||
appName: MyApp
 | 
			
		||||
appDescription: ${app.name} is a Spring Boot application
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 spring boot 支持使用 ${random.xxx} 配置随机值
 | 
			
		||||
 | 
			
		||||
```properties
 | 
			
		||||
my.secret: ${random.value}
 | 
			
		||||
my.number: ${random.int}
 | 
			
		||||
my.bignumber: ${random.long}
 | 
			
		||||
my.number.less.than.ten: ${random.int(10)}
 | 
			
		||||
my.number.in.range: ${random.int[1024,65536]}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、@ConfigurationProperties实现属性绑定
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@ConfigurationProperties(prefix = "programmer")
 | 
			
		||||
@Data
 | 
			
		||||
@ToString
 | 
			
		||||
public class Programmer {
 | 
			
		||||
 | 
			
		||||
    private String name;
 | 
			
		||||
    private int age;
 | 
			
		||||
    private boolean married;
 | 
			
		||||
    private Date hireDate;
 | 
			
		||||
    private float salary;
 | 
			
		||||
    private int random;
 | 
			
		||||
    private Map<String, String> skill;
 | 
			
		||||
    private List company;
 | 
			
		||||
    private School school;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Spring Boot将环境属性绑定到@ConfigurationProperties beans时会使用一些宽松的规则,称之为松散绑定。所以Environment属性名和bean属性名不需要精确匹配。常见的示例中有用的包括虚线分割(比如,context-path绑定到contextPath),将环境属性转为大写字母(比如,PORT绑定port)。
 | 
			
		||||
 | 
			
		||||
需要注意的是`@Value`是不支持松散绑定的,所以建议除非有特殊的需求,否则在`ConfigurationProperties`和`value` 配置属性的时候最好都保持属性和变量的一致,以免造成不必要的勿扰。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、多配置文件
 | 
			
		||||
 | 
			
		||||
多配置文件可以在同一个yml中使用 --- 分割为多个配置,或者遵循application-xxx.yml 的方式命名拆分为多个文件,并在主配置文件application.yml 中确定激活哪个配置文件,当然也可在命令行中确定,命令行的优先级大于配置文件。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/profile.png"/> </div>
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
# 配置文件中激活配置
 | 
			
		||||
spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    active: dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
# 命令行参数激活配置
 | 
			
		||||
--spring.profiles.active=dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 六、优先级的说明
 | 
			
		||||
 | 
			
		||||
Spring Boot设计了一个非常特别的PropertySource顺序,以允许对属性值进行合理的覆盖,属性会以如下的顺序进行设值:
 | 
			
		||||
 | 
			
		||||
1. home目录下的devtools全局设置属性(~/.spring-boot-devtools.properties,如果devtools激活)。
 | 
			
		||||
2. 测试用例上的@TestPropertySource注解。
 | 
			
		||||
3. 测试用例上的@SpringBootTest#properties注解。
 | 
			
		||||
4. 命令行参数
 | 
			
		||||
5. 来自SPRING_APPLICATION_JSON的属性(环境变量或系统属性中内嵌的内联JSON)。
 | 
			
		||||
6. ServletConfig初始化参数。
 | 
			
		||||
7. ServletContext初始化参数。
 | 
			
		||||
8. 来自于java:comp/env的JNDI属性。
 | 
			
		||||
9. Java系统属性(System.getProperties())。
 | 
			
		||||
10. 操作系统环境变量。
 | 
			
		||||
11. RandomValuePropertySource,只包含random.*中的属性。
 | 
			
		||||
12. 没有打进jar包的Profile-specific应用属性(application-{profile}.properties和YAML变量)
 | 
			
		||||
13. 打进jar包中的Profile-specific应用属性(application-{profile}.properties和YAML变量)。
 | 
			
		||||
14. 没有打进jar包的应用配置(application.properties和YAML变量)。
 | 
			
		||||
15. 打进jar包中的应用配置(application.properties和YAML变量)。
 | 
			
		||||
16. @Configuration类上的@PropertySource注解。
 | 
			
		||||
17. 默认属性(使用SpringApplication.setDefaultProperties指定)。
 | 
			
		||||
 | 
			
		||||
这里做一下说明,上文第12,14 点没有打进jar包的文件指的是在启动时候通过`spring.config.location`参数指定的外部配置文件,外部配置文件的优先级应该是大于jar中的配置文件。
 | 
			
		||||
 | 
			
		||||
对上面的配置中常用的规则可以精简如下:
 | 
			
		||||
# spring-boot-yml-profile
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一项目结构">一、项目结构</a><br/>
 | 
			
		||||
<a href="#二常用-yaml-语法讲解">二、常用 yaml 语法讲解</a><br/>
 | 
			
		||||
        <a href="#21-基本规则">2.1 基本规则</a><br/>
 | 
			
		||||
        <a href="#22-对象的写法">2.2 对象的写法</a><br/>
 | 
			
		||||
        <a href="#23-map的写法">2.3 map的写法</a><br/>
 | 
			
		||||
        <a href="#23-数组的写法">2.3 数组的写法</a><br/>
 | 
			
		||||
        <a href="#25-单双引号">2.5 单双引号</a><br/>
 | 
			
		||||
        <a href="#26-特殊符号">2.6 特殊符号</a><br/>
 | 
			
		||||
<a href="#三spring-boot-与-yaml">三、spring boot 与 yaml</a><br/>
 | 
			
		||||
        <a href="#31--spring-boot-支持使用-${appname}-引用预先定义的值">3.1  spring boot 支持使用 ${app.name} 引用预先定义的值</a><br/>
 | 
			
		||||
        <a href="#32-spring-boot-支持使用-${randomxxx}-配置随机值">3.2 spring boot 支持使用 ${random.xxx} 配置随机值</a><br/>
 | 
			
		||||
<a href="#四@ConfigurationProperties实现属性绑定">四、@ConfigurationProperties实现属性绑定</a><br/>
 | 
			
		||||
<a href="#五多配置文件">五、多配置文件</a><br/>
 | 
			
		||||
<a href="#六优先级的说明">六、优先级的说明</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
## 一、项目结构
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-yml-profile.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 二、常用 yaml 语法讲解
 | 
			
		||||
 | 
			
		||||
项目中的yml配置文件如下:
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
programmer:
 | 
			
		||||
  name: xiaoming-DEV
 | 
			
		||||
  married: false
 | 
			
		||||
  hireDate: 2018/12/23
 | 
			
		||||
  salary: 66666.88
 | 
			
		||||
  random: ${random.int[1024,65536]}
 | 
			
		||||
  skill: {java: master, jquery: proficiency}
 | 
			
		||||
  company: [baidu,tengxun,alibaba]
 | 
			
		||||
  school:
 | 
			
		||||
    name: unviersity
 | 
			
		||||
    location: shanghai
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.1 基本规则
 | 
			
		||||
 | 
			
		||||
1. 大小写敏感 
 | 
			
		||||
2. 使用缩进表示层级关系 
 | 
			
		||||
3. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。 
 | 
			
		||||
4. 使用#表示注释 
 | 
			
		||||
5. 字符串默认不用加单双引号,但单引号和双引号都可以使用,双引号不会对特殊字符转义。
 | 
			
		||||
6. YAML中提供了多种常量结构,包括:整数,浮点数,字符串,NULL,日期,布尔,时间。
 | 
			
		||||
 | 
			
		||||
#### 2.2 对象的写法
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
key: value
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 map的写法
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
# 写法一 同一缩进的所有键值对属于一个map
 | 
			
		||||
key: 
 | 
			
		||||
    key1: value1
 | 
			
		||||
    key2: value2
 | 
			
		||||
 | 
			
		||||
# 写法二
 | 
			
		||||
{key1: value1, key2: value2}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.3 数组的写法
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
# 写法一 使用一个短横线加一个空格代表一个数组项
 | 
			
		||||
- a
 | 
			
		||||
- b
 | 
			
		||||
- c
 | 
			
		||||
 | 
			
		||||
# 写法二
 | 
			
		||||
[a,b,c]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.5 单双引号
 | 
			
		||||
 | 
			
		||||
单引号和双引号都可以使用,双引号不会对特殊字符转义。
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
s1: '内容\n字符串'
 | 
			
		||||
s2: "内容\n字符串"
 | 
			
		||||
 | 
			
		||||
转换后:
 | 
			
		||||
{ s1: '内容\\n字符串', s2: '内容\n字符串' }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 2.6 特殊符号
 | 
			
		||||
 | 
			
		||||
---  YAML可以在同一个文件中,使用---表示一个文档的开始。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、spring boot 与 yaml
 | 
			
		||||
 | 
			
		||||
#### 3.1  spring boot 支持使用 ${app.name} 引用预先定义的值
 | 
			
		||||
 | 
			
		||||
```properties
 | 
			
		||||
appName: MyApp
 | 
			
		||||
appDescription: ${app.name} is a Spring Boot application
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 spring boot 支持使用 ${random.xxx} 配置随机值
 | 
			
		||||
 | 
			
		||||
```properties
 | 
			
		||||
my.secret: ${random.value}
 | 
			
		||||
my.number: ${random.int}
 | 
			
		||||
my.bignumber: ${random.long}
 | 
			
		||||
my.number.less.than.ten: ${random.int(10)}
 | 
			
		||||
my.number.in.range: ${random.int[1024,65536]}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、@ConfigurationProperties实现属性绑定
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@ConfigurationProperties(prefix = "programmer")
 | 
			
		||||
@Data
 | 
			
		||||
@ToString
 | 
			
		||||
public class Programmer {
 | 
			
		||||
 | 
			
		||||
    private String name;
 | 
			
		||||
    private int age;
 | 
			
		||||
    private boolean married;
 | 
			
		||||
    private Date hireDate;
 | 
			
		||||
    private float salary;
 | 
			
		||||
    private int random;
 | 
			
		||||
    private Map<String, String> skill;
 | 
			
		||||
    private List company;
 | 
			
		||||
    private School school;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Spring Boot将环境属性绑定到@ConfigurationProperties beans时会使用一些宽松的规则,称之为松散绑定。所以Environment属性名和bean属性名不需要精确匹配。常见的示例中有用的包括虚线分割(比如,context-path绑定到contextPath),将环境属性转为大写字母(比如,PORT绑定port)。
 | 
			
		||||
 | 
			
		||||
需要注意的是`@Value`是不支持松散绑定的,所以建议除非有特殊的需求,否则在`ConfigurationProperties`和`value` 配置属性的时候最好都保持属性和变量的一致,以免造成不必要的勿扰。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、多配置文件
 | 
			
		||||
 | 
			
		||||
多配置文件可以在同一个yml中使用 --- 分割为多个配置,或者遵循application-xxx.yml 的方式命名拆分为多个文件,并在主配置文件application.yml 中确定激活哪个配置文件,当然也可在命令行中确定,命令行的优先级大于配置文件。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/profile.png"/> </div>
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
# 配置文件中激活配置
 | 
			
		||||
spring:
 | 
			
		||||
  profiles:
 | 
			
		||||
    active: dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
# 命令行参数激活配置
 | 
			
		||||
--spring.profiles.active=dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 六、优先级的说明
 | 
			
		||||
 | 
			
		||||
Spring Boot设计了一个非常特别的PropertySource顺序,以允许对属性值进行合理的覆盖,属性会以如下的顺序进行设值:
 | 
			
		||||
 | 
			
		||||
1. home目录下的devtools全局设置属性(~/.spring-boot-devtools.properties,如果devtools激活)。
 | 
			
		||||
2. 测试用例上的@TestPropertySource注解。
 | 
			
		||||
3. 测试用例上的@SpringBootTest#properties注解。
 | 
			
		||||
4. 命令行参数
 | 
			
		||||
5. 来自SPRING_APPLICATION_JSON的属性(环境变量或系统属性中内嵌的内联JSON)。
 | 
			
		||||
6. ServletConfig初始化参数。
 | 
			
		||||
7. ServletContext初始化参数。
 | 
			
		||||
8. 来自于java:comp/env的JNDI属性。
 | 
			
		||||
9. Java系统属性(System.getProperties())。
 | 
			
		||||
10. 操作系统环境变量。
 | 
			
		||||
11. RandomValuePropertySource,只包含random.*中的属性。
 | 
			
		||||
12. 没有打进jar包的Profile-specific应用属性(application-{profile}.properties和YAML变量)
 | 
			
		||||
13. 打进jar包中的Profile-specific应用属性(application-{profile}.properties和YAML变量)。
 | 
			
		||||
14. 没有打进jar包的应用配置(application.properties和YAML变量)。
 | 
			
		||||
15. 打进jar包中的应用配置(application.properties和YAML变量)。
 | 
			
		||||
16. @Configuration类上的@PropertySource注解。
 | 
			
		||||
17. 默认属性(使用SpringApplication.setDefaultProperties指定)。
 | 
			
		||||
 | 
			
		||||
这里做一下说明,上文第12,14 点没有打进jar包的文件指的是在启动时候通过`spring.config.location`参数指定的外部配置文件,外部配置文件的优先级应该是大于jar中的配置文件。
 | 
			
		||||
 | 
			
		||||
对上面的配置中常用的规则可以精简如下:
 | 
			
		||||
 | 
			
		||||
@@ -1,433 +1,455 @@
 | 
			
		||||
# spring-cloud-config
 | 
			
		||||
 | 
			
		||||
## 一、config 简介
 | 
			
		||||
 | 
			
		||||
spring cloud config 分为服务端和客户端,服务端称为分布式配置中心,集中管理配置文件,客户端为各个业务单元,它们从配置中心获取相关配置,同时config 还实现了配置热更新,在服务不停机的情况下刷新配置。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
+ config-server: 配置中心;
 | 
			
		||||
+ config-client: 服务单元,可以从配置中心获取相关配置;
 | 
			
		||||
+ eureka: 注册中心。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-config.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、config-server 配置中心的实现
 | 
			
		||||
 | 
			
		||||
#### 3.1 导入依赖 
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying.config</groupId>
 | 
			
		||||
        <artifactId>spring-cloud-config</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>config-server</artifactId>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-config-server</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.2 在启动类上添加@EnableDiscoveryClient和@EnableConfigServer 注解
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
@EnableConfigServer
 | 
			
		||||
public class ConfigServerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ConfigServerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.3  指定注册中心地址,并配置git仓库地址的配置文件路径
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8020
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: config-server
 | 
			
		||||
  cloud:
 | 
			
		||||
    config:
 | 
			
		||||
      server:
 | 
			
		||||
        git:
 | 
			
		||||
          uri: https://github.com/heibaiying/spring-samples-for-all/
 | 
			
		||||
          search-paths: spring-cloud/spring-cloud-test-config/
 | 
			
		||||
          # 如果代码仓库是公开的 则 不需要设置用户名和密码
 | 
			
		||||
          username:
 | 
			
		||||
          password:
 | 
			
		||||
          # 指定拉取的配置文件的存放位置,配置文件最后存储的目录为 basedir + search-paths
 | 
			
		||||
          # 这个地方还需要注意的是,配置文件的仓库最好只放配置文件
 | 
			
		||||
          # 因为配置中心不仅会拉取search-paths下的文件,还会把uri指定仓库中的全部文件拉取到basedir下
 | 
			
		||||
          basedir: D:\git-config
 | 
			
		||||
      # 指定分支
 | 
			
		||||
      label: master
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
这里的git 仓库就是本用例的仓库,是公开的仓库,所以不用配置用户名和密码,配置文件如下
 | 
			
		||||
 | 
			
		||||
- application.yml 为主配置;
 | 
			
		||||
- application-dev.yml 为开发环境配置。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-git.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.4  启动eureka和config-server服务,访问 http://localhost:8020/application-dev.yml
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-application-dev.png"/> </div>
 | 
			
		||||
 | 
			
		||||
这里需要注意的拉取配置的时候,我们此时指定拉取的是dev配置,application.yml实际 配置如下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-dev.png"/> </div>
 | 
			
		||||
 | 
			
		||||
这说明在用配置中心拉取配置的时候,和我们在本地开发的时候是一致的,配置是互补的,即dev中的实际配置应该是主配置和dev配置的结合,且遵循同名属性精确优先的原则。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.5 http请求地址和资源文件映射
 | 
			
		||||
 | 
			
		||||
在本用例中如果我们想要直接访问主配置,用以下路径 http://localhost:8020/application.yml 是不行的,会得到错误页面。如果想要访问主配置,,可以用http://localhost:8020/application-X.yml,其中可以是任意字符,原因是:
 | 
			
		||||
 | 
			
		||||
请求地址和实际的配置文件应该遵循以下规则,application为配置文件名,profile 为环境,label为分支(如果不指定默认就是master分支)。
 | 
			
		||||
 | 
			
		||||
- /{application}/{profile}[/{label}]
 | 
			
		||||
- /{application}-{profile}.yml
 | 
			
		||||
- /{label}/{application}-{profile}.yml
 | 
			
		||||
- /{application}-{profile}.properties
 | 
			
		||||
- /{label}/{application}-{profile}.properties
 | 
			
		||||
 | 
			
		||||
访问主配置:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-a.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、config-client 搭建
 | 
			
		||||
 | 
			
		||||
#### 4.1 导入依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying.config</groupId>
 | 
			
		||||
        <artifactId>spring-cloud-config</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>config-client</artifactId>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--config client-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-config</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--eureka-client-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 新建 `bootstrap.yml`配置文件,指定注册中心地址和配置中心服务名,并在启动类上开启自动注册@EnableDiscoveryClient
 | 
			
		||||
 | 
			
		||||
这里需要特别说明的是,在之前的所有项目中我们采用的配置文件都是application.yml,但是这里**一定要采用bootstrap.yml**。
 | 
			
		||||
 | 
			
		||||
假设我们的数据库配置是放在远程配置中心的,那么我们应该先去远程配置中心拉取配置,然后再去进行数据库的自动化配置,反之如果我们先进行了数据库的自动化配置,那么就会因为找不到url或驱动而抛出异常。
 | 
			
		||||
 | 
			
		||||
- bootstrap.yml(bootstrap.properties)用来程序引导时执行,应用于更加早期配置信息读取,bootstrap.yml 先于 application.yml 加载。
 | 
			
		||||
 | 
			
		||||
- application.yml(application.properties) 应用程序各个模块的配置信息。
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8030
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: config-client
 | 
			
		||||
  cloud:
 | 
			
		||||
    config:
 | 
			
		||||
      discovery:
 | 
			
		||||
        enabled: true
 | 
			
		||||
        # 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用的
 | 
			
		||||
        serviceId: config-server
 | 
			
		||||
      # 指定分支
 | 
			
		||||
      label: master
 | 
			
		||||
      # 指定环境
 | 
			
		||||
      profile: dev
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
 | 
			
		||||
# 因为我们必须要先从注册中心去获取可用的配置中心, 从配置中心去拉取配置
 | 
			
		||||
eureka:
 | 
			
		||||
    client:
 | 
			
		||||
      serviceUrl:
 | 
			
		||||
        defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class ConfigClientApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ConfigClientApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.3 创建配置映射类用于测试
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@ConfigurationProperties(prefix = "programmer")
 | 
			
		||||
@Data
 | 
			
		||||
@ToString
 | 
			
		||||
public class Programmer{
 | 
			
		||||
 | 
			
		||||
    private String name;
 | 
			
		||||
    private int age;
 | 
			
		||||
    private boolean married;
 | 
			
		||||
    private Date hireDate;
 | 
			
		||||
    private float salary;
 | 
			
		||||
    private int random;
 | 
			
		||||
    private Map<String, String> skill;
 | 
			
		||||
    private List company;
 | 
			
		||||
    private School school;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RestController
 | 
			
		||||
public class ConfigController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private Programmer programmer;
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("programmer")
 | 
			
		||||
    public String getProgrammer() {
 | 
			
		||||
        return programmer.toString();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.4 依次启动eureka,config-server,config-client ,访问 http://localhost:8030/programmer
 | 
			
		||||
 | 
			
		||||
这里需要注意是在启动eureka和config-server,要稍等一会在启动config-client,这里是为了确保config-server已经将服务注册到eureka,然后我们的config-client才能从eureka中获取配置中心的服务。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-client-programmer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
启动的时候可以从控制台看到如下拉取服务的信息:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
Fetching config from server at : http://localhost:8020/
 | 
			
		||||
Located environment: name=config-client, profiles=[dev], label=master, version=50dcfb85cd751e4f28761cd6bad84c1f73034002, state=null
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、集成 spring-cloud-bus 实现配置热更新
 | 
			
		||||
 | 
			
		||||
#### 5.1 消息总线简介
 | 
			
		||||
 | 
			
		||||
在微服务的架构中,我们通常想要构建一个共同的消息主题被所有微服务实例所监听,以便对所有微服务实例的管理和通知,这就是消息总线,spring cloud bus 就是消息总线的一种实现。
 | 
			
		||||
 | 
			
		||||
目前spring cloud bus 支持的消息中间件有 RabbitMQ和kafka, 我们下面的整合采用的是RrabbitMQ。
 | 
			
		||||
 | 
			
		||||
关于热更新只需要对配置客户端(config-client)做更改,不需要对(config-server)做改动。
 | 
			
		||||
 | 
			
		||||
#### 5.1 导入bus依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--因为要用到端点功能(主要是刷新端点),所以需要导入actuator-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-actuator</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.2 修改bootstrap.yml 配置,开启总线配置,配置rabbitmq  和 开启热刷新[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8030
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: config-client
 | 
			
		||||
  cloud:
 | 
			
		||||
    config:
 | 
			
		||||
      discovery:
 | 
			
		||||
        enabled: true
 | 
			
		||||
        # 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用
 | 
			
		||||
        serviceId: config-server
 | 
			
		||||
      # 指定分支
 | 
			
		||||
      label: master
 | 
			
		||||
      # 指定环境
 | 
			
		||||
      profile: dev
 | 
			
		||||
    bus:
 | 
			
		||||
      #开启总线
 | 
			
		||||
      enabled: true
 | 
			
		||||
      # 打开ack跟踪的标志(默认关闭)
 | 
			
		||||
      trace:
 | 
			
		||||
        enabled: true
 | 
			
		||||
  # 使用bus实现热更新
 | 
			
		||||
  rabbitmq:
 | 
			
		||||
    host: 127.0.0.1
 | 
			
		||||
    port: 5672
 | 
			
		||||
    username: guest
 | 
			
		||||
    password: guest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
 | 
			
		||||
# 因为我们必须要先从注册中心去获取可用的配置中心, 然后从配置中心去拉取配置
 | 
			
		||||
eureka:
 | 
			
		||||
    client:
 | 
			
		||||
      serviceUrl:
 | 
			
		||||
        defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
 | 
			
		||||
# 暴露热刷新的端点
 | 
			
		||||
management:
 | 
			
		||||
  endpoints:
 | 
			
		||||
    web:
 | 
			
		||||
      exposure:
 | 
			
		||||
        include: bus-refresh
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.3 用@RefreshScope指定需要热刷新的配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@ConfigurationProperties(prefix = "programmer")
 | 
			
		||||
@Data
 | 
			
		||||
@ToString
 | 
			
		||||
@RefreshScope // 定义下面配置热刷新范围
 | 
			
		||||
public class Programmer{
 | 
			
		||||
 | 
			
		||||
    private String name;
 | 
			
		||||
    private int age;
 | 
			
		||||
    private boolean married;
 | 
			
		||||
    private Date hireDate;
 | 
			
		||||
    private float salary;
 | 
			
		||||
    private int random;
 | 
			
		||||
    private Map<String, String> skill;
 | 
			
		||||
    private List company;
 | 
			
		||||
    private School school;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.4 依次启动eureka,config-server, config-client 服务
 | 
			
		||||
 | 
			
		||||
在client服务端启动时候,可以在控制台 看到bus 自动创建了交换机、队列等
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Created new connection: rabbitConnectionFactory#496c6d94:22/SimpleConnection@185d85d2 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 63713]
 | 
			
		||||
o.s.amqp.rabbit.core.RabbitAdmin         : Auto-declaring a non-durable, auto-delete, or exclusive Queue (springCloudBus.anonymous.iY4TIIi9TSe0bL-TWAMhWg) durable:false, auto-delete:true, exclusive:true. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
也可以在 rabbitmq 管控台查看
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-exchange.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-queue.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 5.6  直接在 git 上修改配置文件,然后用 `post` 触发热刷新端点 http://localhost:8030/actuator/bus-refresh ,即可看到配置已经热刷新
 | 
			
		||||
 | 
			
		||||
注意: 这里的只能用 post 方式请求 ,可以用 postman 等测试软件
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/bus-refresh.png"/> </div>
 | 
			
		||||
 | 
			
		||||
热刷新的过程在控制台有详细的打印,部分日志如下:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
# 消息传播
 | 
			
		||||
Attempting to connect to: [127.0.0.1:5672]
 | 
			
		||||
Created new connection: rabbitConnectionFactory.publisher#b00f2d6:0/SimpleConnection@403c0406 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 62748]
 | 
			
		||||
# 从配置中心拉取配置文件
 | 
			
		||||
Fetching config from server at : http://DESKTOP-8JGSFLJ:8020/
 | 
			
		||||
# 刷新应用上下文 AnnotationConfigApplicationContext
 | 
			
		||||
Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@62e12f66
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# spring-cloud-config
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一config-简介">一、config 简介</a><br/>
 | 
			
		||||
<a href="#二项目结构">二、项目结构</a><br/>
 | 
			
		||||
<a href="#三config-server-配置中心的实现">三、config-server 配置中心的实现</a><br/>
 | 
			
		||||
        <a href="#31-导入依赖">3.1 导入依赖 </a><br/>
 | 
			
		||||
        <a href="#32-在启动类上添加@EnableDiscoveryClient和@EnableConfigServer-注解">3.2 在启动类上添加@EnableDiscoveryClient和@EnableConfigServer 注解</a><br/>
 | 
			
		||||
        <a href="#33--指定注册中心地址并配置git仓库地址的配置文件路径">3.3  指定注册中心地址,并配置git仓库地址的配置文件路径</a><br/>
 | 
			
		||||
        <a href="#34--启动eureka和config-server服务访问-http//localhost8020/application-devyml">3.4  启动eureka和config-server服务,访问 http://localhost:8020/application-dev.yml</a><br/>
 | 
			
		||||
        <a href="#35-http请求地址和资源文件映射">3.5 http请求地址和资源文件映射</a><br/>
 | 
			
		||||
<a href="#四config-client-搭建">四、config-client 搭建</a><br/>
 | 
			
		||||
        <a href="#41-导入依赖">4.1 导入依赖</a><br/>
 | 
			
		||||
        <a href="#42-新建-`bootstrapyml`配置文件指定注册中心地址和配置中心服务名并在启动类上开启自动注册@EnableDiscoveryClient">4.2 新建 `bootstrap.yml`配置文件,指定注册中心地址和配置中心服务名,并在启动类上开启自动注册@EnableDiscoveryClient</a><br/>
 | 
			
		||||
        <a href="#43-创建配置映射类用于测试">4.3 创建配置映射类用于测试</a><br/>
 | 
			
		||||
        <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/>
 | 
			
		||||
        <a href="#51-消息总线简介">5.1 消息总线简介</a><br/>
 | 
			
		||||
        <a href="#51-导入bus依赖">5.1 导入bus依赖</a><br/>
 | 
			
		||||
        <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/>
 | 
			
		||||
        <a href="#53-用@RefreshScope指定需要热刷新的配置">5.3 用@RefreshScope指定需要热刷新的配置</a><br/>
 | 
			
		||||
        <a href="#54-依次启动eurekaconfig-server-config-client-服务">5.4 依次启动eureka,config-server, config-client 服务</a><br/>
 | 
			
		||||
        <a href="#56--直接在-git-上修改配置文件然后用-`post`-触发热刷新端点-http//localhost8030/actuator/bus-refresh-即可看到配置已经热刷新">5.6  直接在 git 上修改配置文件,然后用 `post` 触发热刷新端点 http://localhost:8030/actuator/bus-refresh ,即可看到配置已经热刷新</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
## 一、config 简介
 | 
			
		||||
 | 
			
		||||
spring cloud config 分为服务端和客户端,服务端称为分布式配置中心,集中管理配置文件,客户端为各个业务单元,它们从配置中心获取相关配置,同时config 还实现了配置热更新,在服务不停机的情况下刷新配置。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
+ config-server: 配置中心;
 | 
			
		||||
+ config-client: 服务单元,可以从配置中心获取相关配置;
 | 
			
		||||
+ eureka: 注册中心。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-config.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、config-server 配置中心的实现
 | 
			
		||||
 | 
			
		||||
#### 3.1 导入依赖 
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying.config</groupId>
 | 
			
		||||
        <artifactId>spring-cloud-config</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>config-server</artifactId>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-config-server</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.2 在启动类上添加@EnableDiscoveryClient和@EnableConfigServer 注解
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
@EnableConfigServer
 | 
			
		||||
public class ConfigServerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ConfigServerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.3  指定注册中心地址,并配置git仓库地址的配置文件路径
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8020
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: config-server
 | 
			
		||||
  cloud:
 | 
			
		||||
    config:
 | 
			
		||||
      server:
 | 
			
		||||
        git:
 | 
			
		||||
          uri: https://github.com/heibaiying/spring-samples-for-all/
 | 
			
		||||
          search-paths: spring-cloud/spring-cloud-test-config/
 | 
			
		||||
          # 如果代码仓库是公开的 则 不需要设置用户名和密码
 | 
			
		||||
          username:
 | 
			
		||||
          password:
 | 
			
		||||
          # 指定拉取的配置文件的存放位置,配置文件最后存储的目录为 basedir + search-paths
 | 
			
		||||
          # 这个地方还需要注意的是,配置文件的仓库最好只放配置文件
 | 
			
		||||
          # 因为配置中心不仅会拉取search-paths下的文件,还会把uri指定仓库中的全部文件拉取到basedir下
 | 
			
		||||
          basedir: D:\git-config
 | 
			
		||||
      # 指定分支
 | 
			
		||||
      label: master
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
这里的git 仓库就是本用例的仓库,是公开的仓库,所以不用配置用户名和密码,配置文件如下
 | 
			
		||||
 | 
			
		||||
- application.yml 为主配置;
 | 
			
		||||
- application-dev.yml 为开发环境配置。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-git.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.4  启动eureka和config-server服务,访问 http://localhost:8020/application-dev.yml
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-application-dev.png"/> </div>
 | 
			
		||||
 | 
			
		||||
这里需要注意的拉取配置的时候,我们此时指定拉取的是dev配置,application.yml实际 配置如下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-dev.png"/> </div>
 | 
			
		||||
 | 
			
		||||
这说明在用配置中心拉取配置的时候,和我们在本地开发的时候是一致的,配置是互补的,即dev中的实际配置应该是主配置和dev配置的结合,且遵循同名属性精确优先的原则。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.5 http请求地址和资源文件映射
 | 
			
		||||
 | 
			
		||||
在本用例中如果我们想要直接访问主配置,用以下路径 http://localhost:8020/application.yml 是不行的,会得到错误页面。如果想要访问主配置,,可以用http://localhost:8020/application-X.yml,其中可以是任意字符,原因是:
 | 
			
		||||
 | 
			
		||||
请求地址和实际的配置文件应该遵循以下规则,application为配置文件名,profile 为环境,label为分支(如果不指定默认就是master分支)。
 | 
			
		||||
 | 
			
		||||
- /{application}/{profile}[/{label}]
 | 
			
		||||
- /{application}-{profile}.yml
 | 
			
		||||
- /{label}/{application}-{profile}.yml
 | 
			
		||||
- /{application}-{profile}.properties
 | 
			
		||||
- /{label}/{application}-{profile}.properties
 | 
			
		||||
 | 
			
		||||
访问主配置:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-a.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、config-client 搭建
 | 
			
		||||
 | 
			
		||||
#### 4.1 导入依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying.config</groupId>
 | 
			
		||||
        <artifactId>spring-cloud-config</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>config-client</artifactId>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--config client-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-config</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--eureka-client-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 新建 `bootstrap.yml`配置文件,指定注册中心地址和配置中心服务名,并在启动类上开启自动注册@EnableDiscoveryClient
 | 
			
		||||
 | 
			
		||||
这里需要特别说明的是,在之前的所有项目中我们采用的配置文件都是application.yml,但是这里**一定要采用bootstrap.yml**。
 | 
			
		||||
 | 
			
		||||
假设我们的数据库配置是放在远程配置中心的,那么我们应该先去远程配置中心拉取配置,然后再去进行数据库的自动化配置,反之如果我们先进行了数据库的自动化配置,那么就会因为找不到url或驱动而抛出异常。
 | 
			
		||||
 | 
			
		||||
- bootstrap.yml(bootstrap.properties)用来程序引导时执行,应用于更加早期配置信息读取,bootstrap.yml 先于 application.yml 加载。
 | 
			
		||||
 | 
			
		||||
- application.yml(application.properties) 应用程序各个模块的配置信息。
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8030
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: config-client
 | 
			
		||||
  cloud:
 | 
			
		||||
    config:
 | 
			
		||||
      discovery:
 | 
			
		||||
        enabled: true
 | 
			
		||||
        # 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用的
 | 
			
		||||
        serviceId: config-server
 | 
			
		||||
      # 指定分支
 | 
			
		||||
      label: master
 | 
			
		||||
      # 指定环境
 | 
			
		||||
      profile: dev
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
 | 
			
		||||
# 因为我们必须要先从注册中心去获取可用的配置中心, 从配置中心去拉取配置
 | 
			
		||||
eureka:
 | 
			
		||||
    client:
 | 
			
		||||
      serviceUrl:
 | 
			
		||||
        defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class ConfigClientApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ConfigClientApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.3 创建配置映射类用于测试
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@ConfigurationProperties(prefix = "programmer")
 | 
			
		||||
@Data
 | 
			
		||||
@ToString
 | 
			
		||||
public class Programmer{
 | 
			
		||||
 | 
			
		||||
    private String name;
 | 
			
		||||
    private int age;
 | 
			
		||||
    private boolean married;
 | 
			
		||||
    private Date hireDate;
 | 
			
		||||
    private float salary;
 | 
			
		||||
    private int random;
 | 
			
		||||
    private Map<String, String> skill;
 | 
			
		||||
    private List company;
 | 
			
		||||
    private School school;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RestController
 | 
			
		||||
public class ConfigController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private Programmer programmer;
 | 
			
		||||
 | 
			
		||||
    @RequestMapping("programmer")
 | 
			
		||||
    public String getProgrammer() {
 | 
			
		||||
        return programmer.toString();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.4 依次启动eureka,config-server,config-client ,访问 http://localhost:8030/programmer
 | 
			
		||||
 | 
			
		||||
这里需要注意是在启动eureka和config-server,要稍等一会在启动config-client,这里是为了确保config-server已经将服务注册到eureka,然后我们的config-client才能从eureka中获取配置中心的服务。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-client-programmer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
启动的时候可以从控制台看到如下拉取服务的信息:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
Fetching config from server at : http://localhost:8020/
 | 
			
		||||
Located environment: name=config-client, profiles=[dev], label=master, version=50dcfb85cd751e4f28761cd6bad84c1f73034002, state=null
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、集成 spring-cloud-bus 实现配置热更新
 | 
			
		||||
 | 
			
		||||
#### 5.1 消息总线简介
 | 
			
		||||
 | 
			
		||||
在微服务的架构中,我们通常想要构建一个共同的消息主题被所有微服务实例所监听,以便对所有微服务实例的管理和通知,这就是消息总线,spring cloud bus 就是消息总线的一种实现。
 | 
			
		||||
 | 
			
		||||
目前spring cloud bus 支持的消息中间件有 RabbitMQ和kafka, 我们下面的整合采用的是RrabbitMQ。
 | 
			
		||||
 | 
			
		||||
关于热更新只需要对配置客户端(config-client)做更改,不需要对(config-server)做改动。
 | 
			
		||||
 | 
			
		||||
#### 5.1 导入bus依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--因为要用到端点功能(主要是刷新端点),所以需要导入actuator-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-actuator</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.2 修改bootstrap.yml 配置,开启总线配置,配置rabbitmq  和 开启热刷新[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8030
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: config-client
 | 
			
		||||
  cloud:
 | 
			
		||||
    config:
 | 
			
		||||
      discovery:
 | 
			
		||||
        enabled: true
 | 
			
		||||
        # 这里我们指定的是服务名 如果配置中心有多个,且用同一个服务名,我们的客户端拉取配置的时候是负载均衡的,配置中心也就是高可用
 | 
			
		||||
        serviceId: config-server
 | 
			
		||||
      # 指定分支
 | 
			
		||||
      label: master
 | 
			
		||||
      # 指定环境
 | 
			
		||||
      profile: dev
 | 
			
		||||
    bus:
 | 
			
		||||
      #开启总线
 | 
			
		||||
      enabled: true
 | 
			
		||||
      # 打开ack跟踪的标志(默认关闭)
 | 
			
		||||
      trace:
 | 
			
		||||
        enabled: true
 | 
			
		||||
  # 使用bus实现热更新
 | 
			
		||||
  rabbitmq:
 | 
			
		||||
    host: 127.0.0.1
 | 
			
		||||
    port: 5672
 | 
			
		||||
    username: guest
 | 
			
		||||
    password: guest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 注意指定注册中心的配置不要从公共配置中拉取,要在本地的配置文件中指定
 | 
			
		||||
# 因为我们必须要先从注册中心去获取可用的配置中心, 然后从配置中心去拉取配置
 | 
			
		||||
eureka:
 | 
			
		||||
    client:
 | 
			
		||||
      serviceUrl:
 | 
			
		||||
        defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
 | 
			
		||||
# 暴露热刷新的端点
 | 
			
		||||
management:
 | 
			
		||||
  endpoints:
 | 
			
		||||
    web:
 | 
			
		||||
      exposure:
 | 
			
		||||
        include: bus-refresh
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.3 用@RefreshScope指定需要热刷新的配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Component
 | 
			
		||||
@ConfigurationProperties(prefix = "programmer")
 | 
			
		||||
@Data
 | 
			
		||||
@ToString
 | 
			
		||||
@RefreshScope // 定义下面配置热刷新范围
 | 
			
		||||
public class Programmer{
 | 
			
		||||
 | 
			
		||||
    private String name;
 | 
			
		||||
    private int age;
 | 
			
		||||
    private boolean married;
 | 
			
		||||
    private Date hireDate;
 | 
			
		||||
    private float salary;
 | 
			
		||||
    private int random;
 | 
			
		||||
    private Map<String, String> skill;
 | 
			
		||||
    private List company;
 | 
			
		||||
    private School school;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.4 依次启动eureka,config-server, config-client 服务
 | 
			
		||||
 | 
			
		||||
在client服务端启动时候,可以在控制台 看到bus 自动创建了交换机、队列等
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Created new connection: rabbitConnectionFactory#496c6d94:22/SimpleConnection@185d85d2 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 63713]
 | 
			
		||||
o.s.amqp.rabbit.core.RabbitAdmin         : Auto-declaring a non-durable, auto-delete, or exclusive Queue (springCloudBus.anonymous.iY4TIIi9TSe0bL-TWAMhWg) durable:false, auto-delete:true, exclusive:true. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
也可以在 rabbitmq 管控台查看
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-exchange.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-bus-queue.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 5.6  直接在 git 上修改配置文件,然后用 `post` 触发热刷新端点 http://localhost:8030/actuator/bus-refresh ,即可看到配置已经热刷新
 | 
			
		||||
 | 
			
		||||
注意: 这里的只能用 post 方式请求 ,可以用 postman 等测试软件
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/bus-refresh.png"/> </div>
 | 
			
		||||
 | 
			
		||||
热刷新的过程在控制台有详细的打印,部分日志如下:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
# 消息传播
 | 
			
		||||
Attempting to connect to: [127.0.0.1:5672]
 | 
			
		||||
Created new connection: rabbitConnectionFactory.publisher#b00f2d6:0/SimpleConnection@403c0406 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 62748]
 | 
			
		||||
# 从配置中心拉取配置文件
 | 
			
		||||
Fetching config from server at : http://DESKTOP-8JGSFLJ:8020/
 | 
			
		||||
# 刷新应用上下文 AnnotationConfigApplicationContext
 | 
			
		||||
Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@62e12f66
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,22 @@
 | 
			
		||||
# eureka 高可用注册中心的搭建
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一项目结构">一、项目结构</a><br/>
 | 
			
		||||
<a href="#二三步搭建eureka-高可用注册中心">二、三步搭建eureka 高可用注册中心</a><br/>
 | 
			
		||||
        <a href="#21-引入eureka服务端依赖">2.1 引入eureka服务端依赖</a><br/>
 | 
			
		||||
        <a href="#22--创建三份配置文件分别代表不同注册中心的配置">2.2  创建三份配置文件,分别代表不同注册中心的配置</a><br/>
 | 
			
		||||
        <a href="#23-启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置">2.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置</a><br/>
 | 
			
		||||
<a href="#三三步搭建eureka-客户端">三、三步搭建eureka 客户端</a><br/>
 | 
			
		||||
        <a href="#31-引入eureka客户端依赖">3.1 引入eureka客户端依赖</a><br/>
 | 
			
		||||
        <a href="#32-eureka-客户端配置指定注册中心地址">3.2 eureka 客户端配置,指定注册中心地址</a><br/>
 | 
			
		||||
        <a href="#33-启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置">3.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置</a><br/>
 | 
			
		||||
<a href="#4启动项目">4.启动项目 </a><br/>
 | 
			
		||||
    <a href="#41-这里我们可以采用命令行方式指定配置分别启动三个注册中心">4.1 这里我们可以采用命令行方式指定配置,分别启动三个注册中心</a><br/>
 | 
			
		||||
    <a href="#42--高可用集群搭建成功的判定">4.2  高可用集群搭建成功的判定</a><br/>
 | 
			
		||||
        <a href="#**421--点击下面注册中心的可用实例列表中的地址访问链接分以下几个情况**">4.2.1  点击下面注册中心的可用实例列表中的地址,访问链接分以下几个情况:</a><br/>
 | 
			
		||||
    <a href="#43--prefer-ip-address-参数说明">4.3  prefer-ip-address 参数说明</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
## 一、项目结构
 | 
			
		||||
 | 
			
		||||
eureka-server为服务注册中心,负责服务的管理;
 | 
			
		||||
@@ -155,7 +172,7 @@ public class EurekaClientApplication {
 | 
			
		||||
 | 
			
		||||
这里需要主要的是仅仅status中出现其他注册中心时,并不一定是搭建成功的,**一定是当注册中心的DS Replicas 和 available replicas中显示其余的注册中心时候**,才代表搭建成功。
 | 
			
		||||
 | 
			
		||||
#### **4.2.1  点击下面注册中心的可用实例列表中的地址,访问链接分以下几个情况:**
 | 
			
		||||
#### 4.2.1  点击下面注册中心的可用实例列表中的地址,访问链接分以下几个情况:
 | 
			
		||||
 | 
			
		||||
1. hostname和prefer-ip-address都没有配置,则访问 主机名:服务名:端口号,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,112 +1,126 @@
 | 
			
		||||
# eureka 服务的注册与发现
 | 
			
		||||
# eureka 服务的注册与发现
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一eureka-简介">一、eureka 简介</a><br/>
 | 
			
		||||
<a href="#二项目结构">二、项目结构</a><br/>
 | 
			
		||||
<a href="#三三步搭建eureka-服务注册中心">三、三步搭建eureka 服务注册中心</a><br/>
 | 
			
		||||
        <a href="#31-引入eureka服务端依赖">3.1 引入eureka服务端依赖</a><br/>
 | 
			
		||||
        <a href="#32-eureka-服务端配置">3.2 eureka 服务端配置</a><br/>
 | 
			
		||||
        <a href="#33-启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置">3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置</a><br/>
 | 
			
		||||
<a href="#四三步搭建eureka-客户端">四、三步搭建eureka 客户端</a><br/>
 | 
			
		||||
        <a href="#41-引入eureka客户端依赖">4.1 引入eureka客户端依赖</a><br/>
 | 
			
		||||
        <a href="#42-eureka-客户端配置">4.2 eureka 客户端配置</a><br/>
 | 
			
		||||
        <a href="#43-启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置">4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置</a><br/>
 | 
			
		||||
<a href="#五启动项目">五、启动项目 </a><br/>
 | 
			
		||||
        <a href="#51-进入注册中心控制台查看服务注册情况">5.1 进入注册中心控制台,查看服务注册情况</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
## 一、eureka 简介
 | 
			
		||||
 | 
			
		||||
Spring Cloud Eureka使用Netflix Eureka来实现服务注册与发现,它既包含了服务端组件,也包含了客户端组件。
 | 
			
		||||
 | 
			
		||||
**Eureka服务端**:服务的注册中心,负责维护注册的服务列表。
 | 
			
		||||
 | 
			
		||||
**Eureka客户端**: 在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务,并周期性地发送心跳来更新它的服务租约。同时它也能把从服务端查询到服务信息缓存到本地,并周期性地刷新服务状态。 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
eureka-server为服务注册中心,负责服务的管理;
 | 
			
		||||
 | 
			
		||||
eureka-client 为eureka客户端;
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-eureka.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、三步搭建eureka 服务注册中心
 | 
			
		||||
 | 
			
		||||
#### 3.1 引入eureka服务端依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 eureka 服务端配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8010
 | 
			
		||||
eureka:
 | 
			
		||||
  instance:
 | 
			
		||||
    hostname: localhost
 | 
			
		||||
  client:
 | 
			
		||||
    # 设置为false,代表不向注册中心注册自己
 | 
			
		||||
    register-with-eureka: false
 | 
			
		||||
    # 注册中心主要用于维护服务,并不需要检索服务,所以设置为false
 | 
			
		||||
    fetch-registry: false
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableEurekaServer
 | 
			
		||||
public class EurekaServerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(EurekaServerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、三步搭建eureka 客户端
 | 
			
		||||
 | 
			
		||||
#### 4.1 引入eureka客户端依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 eureka 客户端配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8020
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: eureka-client
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class EurekaClientApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(EurekaClientApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 五、启动项目 
 | 
			
		||||
 | 
			
		||||
#### 5.1 进入注册中心控制台,查看服务注册情况
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/eureka.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 一、eureka 简介
 | 
			
		||||
 | 
			
		||||
Spring Cloud Eureka使用Netflix Eureka来实现服务注册与发现,它既包含了服务端组件,也包含了客户端组件。
 | 
			
		||||
 | 
			
		||||
**Eureka服务端**:服务的注册中心,负责维护注册的服务列表。
 | 
			
		||||
 | 
			
		||||
**Eureka客户端**: 在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务,并周期性地发送心跳来更新它的服务租约。同时它也能把从服务端查询到服务信息缓存到本地,并周期性地刷新服务状态。 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
eureka-server为服务注册中心,负责服务的管理;
 | 
			
		||||
 | 
			
		||||
eureka-client 为eureka客户端;
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-eureka.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、三步搭建eureka 服务注册中心
 | 
			
		||||
 | 
			
		||||
#### 3.1 引入eureka服务端依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 eureka 服务端配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8010
 | 
			
		||||
eureka:
 | 
			
		||||
  instance:
 | 
			
		||||
    hostname: localhost
 | 
			
		||||
  client:
 | 
			
		||||
    # 设置为false,代表不向注册中心注册自己
 | 
			
		||||
    register-with-eureka: false
 | 
			
		||||
    # 注册中心主要用于维护服务,并不需要检索服务,所以设置为false
 | 
			
		||||
    fetch-registry: false
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableEurekaServer
 | 
			
		||||
public class EurekaServerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(EurekaServerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、三步搭建eureka 客户端
 | 
			
		||||
 | 
			
		||||
#### 4.1 引入eureka客户端依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 eureka 客户端配置
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8020
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: eureka-client
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class EurekaClientApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(EurekaClientApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 五、启动项目 
 | 
			
		||||
 | 
			
		||||
#### 5.1 进入注册中心控制台,查看服务注册情况
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,378 +1,400 @@
 | 
			
		||||
# spring-cloud-feign
 | 
			
		||||
 | 
			
		||||
## 一、feign 简介
 | 
			
		||||
 | 
			
		||||
在上一个用例中,我们使用ribbon+restTemplate 实现服务之间的远程调用,实际上每一个调用都是模板化的内容,所以spring cloud Feign 在此基础上进行了进一步的封装。我们只需要定义一个接口并使用feign注解的方式来进行配置,同时采用springMvc 注解进行参数绑定就可以完成服务的调用。feign同时还内置实现了负载均衡、服务容错等功能。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
+ common: 公共的接口和实体类;
 | 
			
		||||
+ consumer: 服务的消费者,采用feign调用产品服务;
 | 
			
		||||
+ producer:服务的提供者;
 | 
			
		||||
+ eureka: 注册中心。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-feign.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、服务提供者的实现
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-producer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。
 | 
			
		||||
 | 
			
		||||
ProductService.java:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 产品提供接口实现类
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {
 | 
			
		||||
 | 
			
		||||
    private static List<Product> productList = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    public Product queryProductById(int id) {
 | 
			
		||||
        return productList.stream().filter(p->p.getId()==id).collect(Collectors.toList()).get(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public List<Product> queryAllProducts() {
 | 
			
		||||
        return productList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveProduct(Product product) {
 | 
			
		||||
        productList.add(product);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onApplicationEvent(WebServerInitializedEvent event) {
 | 
			
		||||
        int port = event.getWebServer().getPort();
 | 
			
		||||
        for (long i = 0; i < 20; i++) {
 | 
			
		||||
            productList.add(new Product(i, port + "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
ProducerController.java:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RestController
 | 
			
		||||
public class ProducerController implements ProductFeign {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private IProductService productService;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("products")
 | 
			
		||||
    public List<Product> productList() {
 | 
			
		||||
        return productService.queryAllProducts();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("product/{id}")
 | 
			
		||||
    public Product productDetail(@PathVariable int id) {
 | 
			
		||||
        return productService.queryProductById(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostMapping("product")
 | 
			
		||||
    public void save(@RequestBody Product product) {
 | 
			
		||||
        productService.saveProduct(product);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
server:
 | 
			
		||||
  port: 8020
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: producer
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class ProducerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ProducerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、服务消费者的实现
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-consumer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 4.1 导入openfeign依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!-- feign 依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-openfeign</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 指定注册中心地址,并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients
 | 
			
		||||
 | 
			
		||||
@EnableFeignClients 会去扫描工程中所有用 @FeignClient 声明的 feign 客户端。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
server:
 | 
			
		||||
  port: 8080
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: consumer
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
@EnableFeignClients
 | 
			
		||||
public class ConsumerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ConsumerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.3 创建服务调用公共接口
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 声明式服务调用
 | 
			
		||||
 */
 | 
			
		||||
public interface ProductFeign {
 | 
			
		||||
 | 
			
		||||
    @GetMapping("products")
 | 
			
		||||
    List<Product> productList();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 这是需要强调的是使用feign时候@PathVariable一定要用value指明参数,
 | 
			
		||||
     * 不然会抛出.IllegalStateException: PathVariable annotation was empty on param 异常
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("product/{id}")
 | 
			
		||||
    Product productDetail(@PathVariable(value = "id") int id);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PostMapping("product")
 | 
			
		||||
    void save(@RequestBody Product product);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
按照官方对于服务最佳化的推荐,这里我们的服务调用接口放在公共模块中,因为在实际的开发中,同一个服务调用接口可能被多个模块所使用。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/common-feign.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.4 继承公共接口,创建CProductFeign, 用@FeignClient声明为feign客户端
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 声明式接口调用
 | 
			
		||||
 */
 | 
			
		||||
@FeignClient(value = "producer",configuration = FeignConfig.class)
 | 
			
		||||
public interface CProductFeign extends ProductFeign {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.5  注入使用 feign 服务调用接口
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("sell")
 | 
			
		||||
public class SellController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private CProductFeign cproductFeign;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("products")
 | 
			
		||||
    public String productList(Model model) {
 | 
			
		||||
        List<Product> products = cproductFeign.productList();
 | 
			
		||||
        model.addAttribute("products", products);
 | 
			
		||||
        return "products";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("product/{id}")
 | 
			
		||||
    public String productDetail(@PathVariable int id, Model model) {
 | 
			
		||||
        Product product = cproductFeign.productDetail(id);
 | 
			
		||||
        model.addAttribute("product", product);
 | 
			
		||||
        return "product";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PostMapping("product")
 | 
			
		||||
    public String save(@RequestParam String productName) {
 | 
			
		||||
        long id = Math.round(Math.random() * 100);
 | 
			
		||||
        Product product = new Product(id, productName, false, new Date(), 88);
 | 
			
		||||
        cproductFeign.save(product);
 | 
			
		||||
        return "redirect:products";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、启动测试
 | 
			
		||||
 | 
			
		||||
#### 5.1 启动一个Eureka服务、三个producer服务(注意区分端口)、和一个消费者服务
 | 
			
		||||
 | 
			
		||||
feign 的依赖中导入了spring-cloud-starter-netflix-ribbon依赖,并且在内部实现了基于ribbon的客户端负载均衡,所以我们这里启动三个producer实例来观察负载均衡的情况。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div>
 | 
			
		||||
 | 
			
		||||
**服务注册中心:**
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-eureka.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 5.2  访问http://localhost:8080/sell/products 查看负载均衡的调用结果
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8020.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8030.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-8040.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 六、 feign 的服务容错
 | 
			
		||||
 | 
			
		||||
#### 6.1 feign 的依赖中默认导入了hystrix 的相关依赖,我们不需要额外导入,只需要开启相关配置即可
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-hystrix-maven.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 6.2 在application.yml 中开启hystrix
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
feign:
 | 
			
		||||
  hystrix:
 | 
			
		||||
    # 如果为true,则OpenFign客户端将使用Hystrix断路器进行封装 默认为false
 | 
			
		||||
    enabled: true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 6.3 创建`CProductFeignImpl`,继承feign接口(CProductFeign),定义熔断时候的回退处理
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 定义发生熔断时候的回退处理。除了继承自CProductFeign,还需要用@Component声明为spring的组件
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class CProductFeignImpl implements CProductFeign {
 | 
			
		||||
 | 
			
		||||
    // 发生熔断时候,返回空集合,前端页面会做容错显示
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Product> productList() {
 | 
			
		||||
        return new ArrayList<>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Product productDetail(int id) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void save(Product product) {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
页面的简单容错处理:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <title>产品列表</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<h3>产品列表:点击查看详情</h3>
 | 
			
		||||
<form action="/sell/product" method="post">
 | 
			
		||||
    <input type="text" name="productName">
 | 
			
		||||
    <input type="submit" value="新增产品">
 | 
			
		||||
</form>
 | 
			
		||||
<ul>
 | 
			
		||||
    <#if (products?size>0) >
 | 
			
		||||
        <#list products as product>
 | 
			
		||||
            <li>
 | 
			
		||||
                <a href="/sell/product/${product.id}">${product.name}</a>
 | 
			
		||||
            </li>
 | 
			
		||||
        </#list>
 | 
			
		||||
    <#else>
 | 
			
		||||
        <h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
 | 
			
		||||
    </#if>
 | 
			
		||||
</ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 6.4 在 @FeignClient 注解中,用fallback参数指定熔断时候的回退处理
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 声明式接口调用
 | 
			
		||||
 */
 | 
			
		||||
@FeignClient(value = "producer",configuration = FeignConfig.class,fallback = CProductFeignImpl.class)
 | 
			
		||||
public interface CProductFeign extends ProductFeign {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 6.5 测试熔断处理
 | 
			
		||||
 | 
			
		||||
hystrix 默认调用超时时间为2s ,这里我们使用线程休眠的方式来模拟超时熔断。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public List<Product> queryAllProducts() {
 | 
			
		||||
    /*用于测试 hystrix 超时熔断
 | 
			
		||||
    try {
 | 
			
		||||
        int i = new Random().nextInt(2500);
 | 
			
		||||
        Thread.sleep(i);
 | 
			
		||||
    } catch (InterruptedException e) {
 | 
			
		||||
        e.printStackTrace();
 | 
			
		||||
    }*/
 | 
			
		||||
    return productList;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
测试结果:
 | 
			
		||||
 | 
			
		||||
# spring-cloud-feign
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一feign-简介">一、feign 简介</a><br/>
 | 
			
		||||
<a href="#二项目结构">二、项目结构</a><br/>
 | 
			
		||||
<a href="#三服务提供者的实现">三、服务提供者的实现</a><br/>
 | 
			
		||||
        <a href="#31-产品服务由`ProductService`提供并通过`ProducerController`将服务暴露给外部调用。">3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。</a><br/>
 | 
			
		||||
        <a href="#32-指定注册中心地址并在启动类上开启自动注册@EnableDiscoveryClient">3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient</a><br/>
 | 
			
		||||
<a href="#四服务消费者的实现">四、服务消费者的实现</a><br/>
 | 
			
		||||
        <a href="#41-导入openfeign依赖">4.1 导入openfeign依赖</a><br/>
 | 
			
		||||
        <a href="#42-指定注册中心地址并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients">4.2 指定注册中心地址,并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients</a><br/>
 | 
			
		||||
        <a href="#43-创建服务调用公共接口">4.3 创建服务调用公共接口</a><br/>
 | 
			
		||||
        <a href="#44-继承公共接口创建CProductFeign-用@FeignClient声明为feign客户端">4.4 继承公共接口,创建CProductFeign, 用@FeignClient声明为feign客户端</a><br/>
 | 
			
		||||
        <a href="#45--注入使用-feign-服务调用接口">4.5  注入使用 feign 服务调用接口</a><br/>
 | 
			
		||||
<a href="#五启动测试">五、启动测试</a><br/>
 | 
			
		||||
        <a href="#51-启动一个Eureka服务三个producer服务注意区分端口和一个消费者服务">5.1 启动一个Eureka服务、三个producer服务(注意区分端口)、和一个消费者服务</a><br/>
 | 
			
		||||
        <a href="#52--访问http//localhost8080/sell/products-查看负载均衡的调用结果">5.2  访问http://localhost:8080/sell/products 查看负载均衡的调用结果</a><br/>
 | 
			
		||||
<a href="#六-feign-的服务容错">六、 feign 的服务容错</a><br/>
 | 
			
		||||
        <a href="#61-feign-的依赖中默认导入了hystrix-的相关依赖我们不需要额外导入只需要开启相关配置即可">6.1 feign 的依赖中默认导入了hystrix 的相关依赖,我们不需要额外导入,只需要开启相关配置即可</a><br/>
 | 
			
		||||
        <a href="#62-在applicationyml-中开启hystrix">6.2 在application.yml 中开启hystrix</a><br/>
 | 
			
		||||
        <a href="#63-创建`CProductFeignImpl`继承feign接口CProductFeign定义熔断时候的回退处理">6.3 创建`CProductFeignImpl`,继承feign接口(CProductFeign),定义熔断时候的回退处理</a><br/>
 | 
			
		||||
        <a href="#64-在-@FeignClient-注解中用fallback参数指定熔断时候的回退处理">6.4 在 @FeignClient 注解中,用fallback参数指定熔断时候的回退处理</a><br/>
 | 
			
		||||
        <a href="#65-测试熔断处理">6.5 测试熔断处理</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
## 一、feign 简介
 | 
			
		||||
 | 
			
		||||
在上一个用例中,我们使用ribbon+restTemplate 实现服务之间的远程调用,实际上每一个调用都是模板化的内容,所以spring cloud Feign 在此基础上进行了进一步的封装。我们只需要定义一个接口并使用feign注解的方式来进行配置,同时采用springMvc 注解进行参数绑定就可以完成服务的调用。feign同时还内置实现了负载均衡、服务容错等功能。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
+ common: 公共的接口和实体类;
 | 
			
		||||
+ consumer: 服务的消费者,采用feign调用产品服务;
 | 
			
		||||
+ producer:服务的提供者;
 | 
			
		||||
+ eureka: 注册中心。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-feign.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、服务提供者的实现
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-producer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。
 | 
			
		||||
 | 
			
		||||
ProductService.java:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 产品提供接口实现类
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {
 | 
			
		||||
 | 
			
		||||
    private static List<Product> productList = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    public Product queryProductById(int id) {
 | 
			
		||||
        return productList.stream().filter(p->p.getId()==id).collect(Collectors.toList()).get(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public List<Product> queryAllProducts() {
 | 
			
		||||
        return productList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveProduct(Product product) {
 | 
			
		||||
        productList.add(product);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onApplicationEvent(WebServerInitializedEvent event) {
 | 
			
		||||
        int port = event.getWebServer().getPort();
 | 
			
		||||
        for (long i = 0; i < 20; i++) {
 | 
			
		||||
            productList.add(new Product(i, port + "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
ProducerController.java:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RestController
 | 
			
		||||
public class ProducerController implements ProductFeign {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private IProductService productService;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("products")
 | 
			
		||||
    public List<Product> productList() {
 | 
			
		||||
        return productService.queryAllProducts();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("product/{id}")
 | 
			
		||||
    public Product productDetail(@PathVariable int id) {
 | 
			
		||||
        return productService.queryProductById(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostMapping("product")
 | 
			
		||||
    public void save(@RequestBody Product product) {
 | 
			
		||||
        productService.saveProduct(product);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
server:
 | 
			
		||||
  port: 8020
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: producer
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class ProducerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ProducerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、服务消费者的实现
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-consumer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 4.1 导入openfeign依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!-- feign 依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-openfeign</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 指定注册中心地址,并在启动类上添加注解@EnableDiscoveryClient和@EnableFeignClients
 | 
			
		||||
 | 
			
		||||
@EnableFeignClients 会去扫描工程中所有用 @FeignClient 声明的 feign 客户端。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
server:
 | 
			
		||||
  port: 8080
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: consumer
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
@EnableFeignClients
 | 
			
		||||
public class ConsumerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ConsumerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.3 创建服务调用公共接口
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 声明式服务调用
 | 
			
		||||
 */
 | 
			
		||||
public interface ProductFeign {
 | 
			
		||||
 | 
			
		||||
    @GetMapping("products")
 | 
			
		||||
    List<Product> productList();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 这是需要强调的是使用feign时候@PathVariable一定要用value指明参数,
 | 
			
		||||
     * 不然会抛出.IllegalStateException: PathVariable annotation was empty on param 异常
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("product/{id}")
 | 
			
		||||
    Product productDetail(@PathVariable(value = "id") int id);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PostMapping("product")
 | 
			
		||||
    void save(@RequestBody Product product);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
按照官方对于服务最佳化的推荐,这里我们的服务调用接口放在公共模块中,因为在实际的开发中,同一个服务调用接口可能被多个模块所使用。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/common-feign.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.4 继承公共接口,创建CProductFeign, 用@FeignClient声明为feign客户端
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 声明式接口调用
 | 
			
		||||
 */
 | 
			
		||||
@FeignClient(value = "producer",configuration = FeignConfig.class)
 | 
			
		||||
public interface CProductFeign extends ProductFeign {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.5  注入使用 feign 服务调用接口
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Controller
 | 
			
		||||
@RequestMapping("sell")
 | 
			
		||||
public class SellController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private CProductFeign cproductFeign;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("products")
 | 
			
		||||
    public String productList(Model model) {
 | 
			
		||||
        List<Product> products = cproductFeign.productList();
 | 
			
		||||
        model.addAttribute("products", products);
 | 
			
		||||
        return "products";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("product/{id}")
 | 
			
		||||
    public String productDetail(@PathVariable int id, Model model) {
 | 
			
		||||
        Product product = cproductFeign.productDetail(id);
 | 
			
		||||
        model.addAttribute("product", product);
 | 
			
		||||
        return "product";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @PostMapping("product")
 | 
			
		||||
    public String save(@RequestParam String productName) {
 | 
			
		||||
        long id = Math.round(Math.random() * 100);
 | 
			
		||||
        Product product = new Product(id, productName, false, new Date(), 88);
 | 
			
		||||
        cproductFeign.save(product);
 | 
			
		||||
        return "redirect:products";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、启动测试
 | 
			
		||||
 | 
			
		||||
#### 5.1 启动一个Eureka服务、三个producer服务(注意区分端口)、和一个消费者服务
 | 
			
		||||
 | 
			
		||||
feign 的依赖中导入了spring-cloud-starter-netflix-ribbon依赖,并且在内部实现了基于ribbon的客户端负载均衡,所以我们这里启动三个producer实例来观察负载均衡的情况。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div>
 | 
			
		||||
 | 
			
		||||
**服务注册中心:**
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-eureka.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 5.2  访问http://localhost:8080/sell/products 查看负载均衡的调用结果
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8020.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8030.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-8040.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 六、 feign 的服务容错
 | 
			
		||||
 | 
			
		||||
#### 6.1 feign 的依赖中默认导入了hystrix 的相关依赖,我们不需要额外导入,只需要开启相关配置即可
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-hystrix-maven.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 6.2 在application.yml 中开启hystrix
 | 
			
		||||
 | 
			
		||||
```yml
 | 
			
		||||
feign:
 | 
			
		||||
  hystrix:
 | 
			
		||||
    # 如果为true,则OpenFign客户端将使用Hystrix断路器进行封装 默认为false
 | 
			
		||||
    enabled: true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 6.3 创建`CProductFeignImpl`,继承feign接口(CProductFeign),定义熔断时候的回退处理
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 定义发生熔断时候的回退处理。除了继承自CProductFeign,还需要用@Component声明为spring的组件
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class CProductFeignImpl implements CProductFeign {
 | 
			
		||||
 | 
			
		||||
    // 发生熔断时候,返回空集合,前端页面会做容错显示
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Product> productList() {
 | 
			
		||||
        return new ArrayList<>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Product productDetail(int id) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void save(Product product) {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
页面的简单容错处理:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <title>产品列表</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<h3>产品列表:点击查看详情</h3>
 | 
			
		||||
<form action="/sell/product" method="post">
 | 
			
		||||
    <input type="text" name="productName">
 | 
			
		||||
    <input type="submit" value="新增产品">
 | 
			
		||||
</form>
 | 
			
		||||
<ul>
 | 
			
		||||
    <#if (products?size>0) >
 | 
			
		||||
        <#list products as product>
 | 
			
		||||
            <li>
 | 
			
		||||
                <a href="/sell/product/${product.id}">${product.name}</a>
 | 
			
		||||
            </li>
 | 
			
		||||
        </#list>
 | 
			
		||||
    <#else>
 | 
			
		||||
        <h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
 | 
			
		||||
    </#if>
 | 
			
		||||
</ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 6.4 在 @FeignClient 注解中,用fallback参数指定熔断时候的回退处理
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 声明式接口调用
 | 
			
		||||
 */
 | 
			
		||||
@FeignClient(value = "producer",configuration = FeignConfig.class,fallback = CProductFeignImpl.class)
 | 
			
		||||
public interface CProductFeign extends ProductFeign {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 6.5 测试熔断处理
 | 
			
		||||
 | 
			
		||||
hystrix 默认调用超时时间为2s ,这里我们使用线程休眠的方式来模拟超时熔断。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public List<Product> queryAllProducts() {
 | 
			
		||||
    /*用于测试 hystrix 超时熔断
 | 
			
		||||
    try {
 | 
			
		||||
        int i = new Random().nextInt(2500);
 | 
			
		||||
        Thread.sleep(i);
 | 
			
		||||
    } catch (InterruptedException e) {
 | 
			
		||||
        e.printStackTrace();
 | 
			
		||||
    }*/
 | 
			
		||||
    return productList;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
测试结果:
 | 
			
		||||
 | 
			
		||||
@@ -1,325 +1,346 @@
 | 
			
		||||
# spring-cloud-hystrix-turbine
 | 
			
		||||
 | 
			
		||||
## 一、hystrix 简介
 | 
			
		||||
 | 
			
		||||
#### 1.1 熔断器
 | 
			
		||||
 | 
			
		||||
在分布式系统中,由于服务之间相互的依赖调用,如果一个服务单元发生了故障就有可能导致故障蔓延至整个系统,从而衍生出一系列的保护机制,断路器就是其中之一。
 | 
			
		||||
 | 
			
		||||
断路器可以在服务单元发生故障的时候,及时切断与服务单元的连接,避免资源被长时间占用。spring cloud hystrix组件实现了断路器、线程隔离等一系列基本功能,并具有服务降级、服务熔断、请求缓存、请求合并以及服务监控等配套功能。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 1.2 hystrix 工作机制
 | 
			
		||||
 | 
			
		||||
- 当一个服务的处理请求的失败次数低于阈值时候,熔断器处于关闭状态,服务正常;
 | 
			
		||||
- 当一个服务的处理请求的失败次数大于阈值时候,熔断器开启,这时候所有的请求都会执行快速失败,是不会去调用实际的服务的;
 | 
			
		||||
- 当熔断器处于打开状态的一段时间后,熔断器处于半打开状态,这时候一定数量的请求回去调用实际的服务,如果调用成功,则代表服务可用了,熔断器关闭;如果还是失败,则代表服务还是不可用,熔断器继续关闭。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/circuitbreaker.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
[spring-cloud-ribbon](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-ribbon)用例已经实现通过ribbon+restTemplate实现服务间的调用,本用例在其基础上进行hystrix 的整合。
 | 
			
		||||
 | 
			
		||||
+ common: 公共的接口和实体类;
 | 
			
		||||
+ consumer: 服务的消费者,采用RestTemplate调用产品服务;
 | 
			
		||||
+ producer:服务的提供者;
 | 
			
		||||
+ eureka: 注册中心;
 | 
			
		||||
+ turbine:多个熔断器的聚合监控。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-hystrix.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、整合 hystrix (以consumer模块为例)
 | 
			
		||||
 | 
			
		||||
#### 3.1 引入依赖
 | 
			
		||||
 | 
			
		||||
hystrix的仪表盘功能实际上是从[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)获取数据,所以需要actuator starter开启端点的相关功能。
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--hystrix 依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--hystrix 监控仪表盘依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--健康检查依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-actuator</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 暴露端点
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
management:
 | 
			
		||||
  endpoints:
 | 
			
		||||
    web:
 | 
			
		||||
      exposure:
 | 
			
		||||
        # 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
 | 
			
		||||
        include: "*"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.3 在启动类上添加注解@EnableHystrix和@EnableHystrixDashboard
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
@EnableHystrix
 | 
			
		||||
@EnableHystrixDashboard
 | 
			
		||||
public class ConsumerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ConsumerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.4  使用 @HystrixCommand 定义失败回退的方法
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@HystrixCommand(fallbackMethod = "queryProductsFail")
 | 
			
		||||
public List<Product> queryAllProducts() {
 | 
			
		||||
    ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://producer/products", List.class);
 | 
			
		||||
    List<Product> productList = responseEntity.getBody();
 | 
			
		||||
    return productList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 如果发送熔断返回空集合,在前端判断处理
 | 
			
		||||
public List<Product> queryProductsFail() {
 | 
			
		||||
    return new ArrayList<>();
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <title>产品列表</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<h3>产品列表:点击查看详情</h3>
 | 
			
		||||
<form action="/sell/product" method="post">
 | 
			
		||||
    <input type="text" name="productName">
 | 
			
		||||
    <input type="submit" value="新增产品">
 | 
			
		||||
</form>
 | 
			
		||||
<ul>
 | 
			
		||||
    <#if (products?size>0) >
 | 
			
		||||
        <#list products as product>
 | 
			
		||||
            <li>
 | 
			
		||||
                <a href="/sell/product/${product.id}">${product.name}</a>
 | 
			
		||||
            </li>
 | 
			
		||||
        </#list>
 | 
			
		||||
    <#else>
 | 
			
		||||
        <h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
 | 
			
		||||
    </#if>
 | 
			
		||||
</ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.5 模拟熔断
 | 
			
		||||
 | 
			
		||||
这里被调用方采用线程休眠的方式模拟服务超时,Hystrix默认超时时间为2s,调用远程服务时候超过这个时间,会触发熔断。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public List<Product> queryAllProducts() {
 | 
			
		||||
    // hystrix 默认超时是2秒
 | 
			
		||||
    int i = new Random().nextInt(2500);
 | 
			
		||||
    try {
 | 
			
		||||
        Thread.sleep(i);
 | 
			
		||||
    } catch (InterruptedException e) {
 | 
			
		||||
        e.printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
    return productList;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
3.5   启动服务,访问http://localhost:8030/sell/products ,多次刷新查看熔断情况
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 3.5 启动服务,访问 localhost:8030/hystrix
 | 
			
		||||
 | 
			
		||||
依次输出http://localhost:8030/actuator/hystrix.stream(监控地址) ,2000(延迟时间),title可以任意填写,进入监控台。
 | 
			
		||||
 | 
			
		||||
需要注意的是在spring cloud Finchley.SR2,监控地址中都是有/actuator的,因为在spring boot 2.x 的所有端点(包括自定义端点)都是暴露在这个路径下,在启动时候的控制台输出的日志可以查看到所有暴露端点的映射。
 | 
			
		||||
 | 
			
		||||
**登录页面**:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-single-login.png"/> </div>
 | 
			
		||||
 | 
			
		||||
**监控页面**:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030-login.png"/> </div>
 | 
			
		||||
 | 
			
		||||
**关于各个参数的说明参见[官方wiki](https://github.com/Netflix-Skunkworks/hystrix-dashboard/wiki)提供的图**:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dashboard.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、使用turbine 实现聚合监控
 | 
			
		||||
 | 
			
		||||
单体监控和聚合监控:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dashboard-direct-vs-turbine-640.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.1 创建turbine模块,导入依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying.hystrix</groupId>
 | 
			
		||||
        <artifactId>spring-cloud-hystrx</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>turbine</artifactId>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
           <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-actuator</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.2 指定注册中心地址和聚合的项目,这里我们监控 consumer,producer 两个项目
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
server:
 | 
			
		||||
  port: 8040
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: turbine
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
# 指定聚合的项目
 | 
			
		||||
turbine:
 | 
			
		||||
  aggregator:
 | 
			
		||||
    cluster-config: default
 | 
			
		||||
  combine-host-port: true
 | 
			
		||||
  app-config: consumer,producer
 | 
			
		||||
  clusterNameExpression: "'default'"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.3 在启动类上添加注解
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
@EnableHystrix
 | 
			
		||||
@EnableHystrixDashboard
 | 
			
		||||
@EnableTurbine
 | 
			
		||||
public class TurbineApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(TurbineApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.4 依次启动eureka、producer、consumer、turbine四个项目
 | 
			
		||||
 | 
			
		||||
在  localhost:8030/hystrix或者localhost:8030/hystrix(consumer和producer都集成了hystrix) 页面输入http://localhost:8040/turbine.stream,查看断路器聚合信息
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-cluster-login.png"/> </div>
 | 
			
		||||
 | 
			
		||||
**显示了不同服务单元(consumer,producer)的多个断路器信息:**
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-cluster.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 五、整合过程中可能出现的问题
 | 
			
		||||
 | 
			
		||||
#### 5.1 无法访问监控页面
 | 
			
		||||
 | 
			
		||||
1. 一般是端点链接输入不对,在F版本的spring cloud 中,输入监控的端点链接是 http://localhost:8030/actuator/hystrix.stream ,中间是有/actuator/(之前版本的没有/actuator/)
 | 
			
		||||
 | 
			
		||||
2.  没有暴露端点链接,暴露端点链接有两种方式,一种是我们在上文中提到的基于配置的方式
 | 
			
		||||
 | 
			
		||||
   ```yaml
 | 
			
		||||
   management:
 | 
			
		||||
     endpoints:
 | 
			
		||||
       web:
 | 
			
		||||
         exposure:
 | 
			
		||||
           # 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
 | 
			
		||||
           include: "*"
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
   第二种方式是基于代码的方式,如下:
 | 
			
		||||
 | 
			
		||||
   ```java
 | 
			
		||||
    @Bean
 | 
			
		||||
   public ServletRegistrationBean getServlet() {
 | 
			
		||||
       HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
 | 
			
		||||
       ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
 | 
			
		||||
       registrationBean.setLoadOnStartup(1);
 | 
			
		||||
       registrationBean.addUrlMappings("/actuator/hystrix.stream");
 | 
			
		||||
       registrationBean.setName("HystrixMetricsStreamServlet");
 | 
			
		||||
       return registrationBean;
 | 
			
		||||
   }
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
   这两种方式二选一即可,就算是采用代码的方式,还是建议将地址设置为/actuator/hystrix.stream,而不是原来的hystrix.stream,因为turbine默认也是从/actuator/hystrix.stream去获取信息。
 | 
			
		||||
 | 
			
		||||
#### 5.2 页面一直loading 或者访问端点页面一直出现ping
 | 
			
		||||
 | 
			
		||||
这种情况是熔断器所在的方法没有被调用,所以没有产生监控数据,不是整合问题,这时候调用一下熔断器所在方法即可。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-loading.png"/> </div>
 | 
			
		||||
# spring-cloud-hystrix-turbine
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一hystrix-简介">一、hystrix 简介</a><br/>
 | 
			
		||||
        <a href="#11-熔断器">1.1 熔断器</a><br/>
 | 
			
		||||
        <a href="#12-hystrix-工作机制">1.2 hystrix 工作机制</a><br/>
 | 
			
		||||
<a href="#二项目结构">二、项目结构</a><br/>
 | 
			
		||||
<a href="#三整合-hystrix-以consumer模块为例">三、整合 hystrix (以consumer模块为例)</a><br/>
 | 
			
		||||
        <a href="#31-引入依赖">3.1 引入依赖</a><br/>
 | 
			
		||||
        <a href="#32-暴露端点">3.2 暴露端点</a><br/>
 | 
			
		||||
        <a href="#33-在启动类上添加注解@EnableHystrix和@EnableHystrixDashboard">3.3 在启动类上添加注解@EnableHystrix和@EnableHystrixDashboard</a><br/>
 | 
			
		||||
        <a href="#34--使用-@HystrixCommand-定义失败回退的方法">3.4  使用 @HystrixCommand 定义失败回退的方法</a><br/>
 | 
			
		||||
        <a href="#35-模拟熔断">3.5 模拟熔断</a><br/>
 | 
			
		||||
        <a href="#35-启动服务访问-localhost8030/hystrix">3.5 启动服务,访问 localhost:8030/hystrix</a><br/>
 | 
			
		||||
<a href="#四使用turbine-实现聚合监控">四、使用turbine 实现聚合监控</a><br/>
 | 
			
		||||
        <a href="#41-创建turbine模块导入依赖">4.1 创建turbine模块,导入依赖</a><br/>
 | 
			
		||||
        <a href="#42-指定注册中心地址和聚合的项目这里我们监控-consumerproducer-两个项目">4.2 指定注册中心地址和聚合的项目,这里我们监控 consumer,producer 两个项目</a><br/>
 | 
			
		||||
        <a href="#43-在启动类上添加注解">4.3 在启动类上添加注解</a><br/>
 | 
			
		||||
        <a href="#44-依次启动eurekaproducerconsumerturbine四个项目">4.4 依次启动eureka、producer、consumer、turbine四个项目</a><br/>
 | 
			
		||||
<a href="#五整合过程中可能出现的问题">五、整合过程中可能出现的问题</a><br/>
 | 
			
		||||
        <a href="#51-无法访问监控页面">5.1 无法访问监控页面</a><br/>
 | 
			
		||||
        <a href="#52-页面一直loading-或者访问端点页面一直出现ping">5.2 页面一直loading 或者访问端点页面一直出现ping</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
## 一、hystrix 简介
 | 
			
		||||
 | 
			
		||||
#### 1.1 熔断器
 | 
			
		||||
 | 
			
		||||
在分布式系统中,由于服务之间相互的依赖调用,如果一个服务单元发生了故障就有可能导致故障蔓延至整个系统,从而衍生出一系列的保护机制,断路器就是其中之一。
 | 
			
		||||
 | 
			
		||||
断路器可以在服务单元发生故障的时候,及时切断与服务单元的连接,避免资源被长时间占用。spring cloud hystrix组件实现了断路器、线程隔离等一系列基本功能,并具有服务降级、服务熔断、请求缓存、请求合并以及服务监控等配套功能。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 1.2 hystrix 工作机制
 | 
			
		||||
 | 
			
		||||
- 当一个服务的处理请求的失败次数低于阈值时候,熔断器处于关闭状态,服务正常;
 | 
			
		||||
- 当一个服务的处理请求的失败次数大于阈值时候,熔断器开启,这时候所有的请求都会执行快速失败,是不会去调用实际的服务的;
 | 
			
		||||
- 当熔断器处于打开状态的一段时间后,熔断器处于半打开状态,这时候一定数量的请求回去调用实际的服务,如果调用成功,则代表服务可用了,熔断器关闭;如果还是失败,则代表服务还是不可用,熔断器继续关闭。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/circuitbreaker.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
[spring-cloud-ribbon](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-ribbon)用例已经实现通过ribbon+restTemplate实现服务间的调用,本用例在其基础上进行hystrix 的整合。
 | 
			
		||||
 | 
			
		||||
+ common: 公共的接口和实体类;
 | 
			
		||||
+ consumer: 服务的消费者,采用RestTemplate调用产品服务;
 | 
			
		||||
+ producer:服务的提供者;
 | 
			
		||||
+ eureka: 注册中心;
 | 
			
		||||
+ turbine:多个熔断器的聚合监控。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-hystrix.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、整合 hystrix (以consumer模块为例)
 | 
			
		||||
 | 
			
		||||
#### 3.1 引入依赖
 | 
			
		||||
 | 
			
		||||
hystrix的仪表盘功能实际上是从[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)获取数据,所以需要actuator starter开启端点的相关功能。
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--hystrix 依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--hystrix 监控仪表盘依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
<!--健康检查依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.boot</groupId>
 | 
			
		||||
    <artifactId>spring-boot-starter-actuator</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 暴露端点
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
management:
 | 
			
		||||
  endpoints:
 | 
			
		||||
    web:
 | 
			
		||||
      exposure:
 | 
			
		||||
        # 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
 | 
			
		||||
        include: "*"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.3 在启动类上添加注解@EnableHystrix和@EnableHystrixDashboard
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
@EnableHystrix
 | 
			
		||||
@EnableHystrixDashboard
 | 
			
		||||
public class ConsumerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ConsumerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.4  使用 @HystrixCommand 定义失败回退的方法
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@HystrixCommand(fallbackMethod = "queryProductsFail")
 | 
			
		||||
public List<Product> queryAllProducts() {
 | 
			
		||||
    ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://producer/products", List.class);
 | 
			
		||||
    List<Product> productList = responseEntity.getBody();
 | 
			
		||||
    return productList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 如果发送熔断返回空集合,在前端判断处理
 | 
			
		||||
public List<Product> queryProductsFail() {
 | 
			
		||||
    return new ArrayList<>();
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <title>产品列表</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<h3>产品列表:点击查看详情</h3>
 | 
			
		||||
<form action="/sell/product" method="post">
 | 
			
		||||
    <input type="text" name="productName">
 | 
			
		||||
    <input type="submit" value="新增产品">
 | 
			
		||||
</form>
 | 
			
		||||
<ul>
 | 
			
		||||
    <#if (products?size>0) >
 | 
			
		||||
        <#list products as product>
 | 
			
		||||
            <li>
 | 
			
		||||
                <a href="/sell/product/${product.id}">${product.name}</a>
 | 
			
		||||
            </li>
 | 
			
		||||
        </#list>
 | 
			
		||||
    <#else>
 | 
			
		||||
        <h4 style="color: red">当前排队人数过多,请之后再购买!</h4>
 | 
			
		||||
    </#if>
 | 
			
		||||
</ul>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.5 模拟熔断
 | 
			
		||||
 | 
			
		||||
这里被调用方采用线程休眠的方式模拟服务超时,Hystrix默认超时时间为2s,调用远程服务时候超过这个时间,会触发熔断。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
public List<Product> queryAllProducts() {
 | 
			
		||||
    // hystrix 默认超时是2秒
 | 
			
		||||
    int i = new Random().nextInt(2500);
 | 
			
		||||
    try {
 | 
			
		||||
        Thread.sleep(i);
 | 
			
		||||
    } catch (InterruptedException e) {
 | 
			
		||||
        e.printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
    return productList;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
3.5   启动服务,访问http://localhost:8030/sell/products ,多次刷新查看熔断情况
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 3.5 启动服务,访问 localhost:8030/hystrix
 | 
			
		||||
 | 
			
		||||
依次输出http://localhost:8030/actuator/hystrix.stream(监控地址) ,2000(延迟时间),title可以任意填写,进入监控台。
 | 
			
		||||
 | 
			
		||||
需要注意的是在spring cloud Finchley.SR2,监控地址中都是有/actuator的,因为在spring boot 2.x 的所有端点(包括自定义端点)都是暴露在这个路径下,在启动时候的控制台输出的日志可以查看到所有暴露端点的映射。
 | 
			
		||||
 | 
			
		||||
**登录页面**:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-single-login.png"/> </div>
 | 
			
		||||
 | 
			
		||||
**监控页面**:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030-login.png"/> </div>
 | 
			
		||||
 | 
			
		||||
**关于各个参数的说明参见[官方wiki](https://github.com/Netflix-Skunkworks/hystrix-dashboard/wiki)提供的图**:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dashboard.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、使用turbine 实现聚合监控
 | 
			
		||||
 | 
			
		||||
单体监控和聚合监控:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dashboard-direct-vs-turbine-640.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.1 创建turbine模块,导入依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>com.heibaiying.hystrix</groupId>
 | 
			
		||||
        <artifactId>spring-cloud-hystrx</artifactId>
 | 
			
		||||
        <version>0.0.1-SNAPSHOT</version>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>turbine</artifactId>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
           <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-actuator</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.2 指定注册中心地址和聚合的项目,这里我们监控 consumer,producer 两个项目
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
server:
 | 
			
		||||
  port: 8040
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: turbine
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
# 指定聚合的项目
 | 
			
		||||
turbine:
 | 
			
		||||
  aggregator:
 | 
			
		||||
    cluster-config: default
 | 
			
		||||
  combine-host-port: true
 | 
			
		||||
  app-config: consumer,producer
 | 
			
		||||
  clusterNameExpression: "'default'"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.3 在启动类上添加注解
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
@EnableHystrix
 | 
			
		||||
@EnableHystrixDashboard
 | 
			
		||||
@EnableTurbine
 | 
			
		||||
public class TurbineApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(TurbineApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 4.4 依次启动eureka、producer、consumer、turbine四个项目
 | 
			
		||||
 | 
			
		||||
在  localhost:8030/hystrix或者localhost:8030/hystrix(consumer和producer都集成了hystrix) 页面输入http://localhost:8040/turbine.stream,查看断路器聚合信息
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-cluster-login.png"/> </div>
 | 
			
		||||
 | 
			
		||||
**显示了不同服务单元(consumer,producer)的多个断路器信息:**
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-cluster.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 五、整合过程中可能出现的问题
 | 
			
		||||
 | 
			
		||||
#### 5.1 无法访问监控页面
 | 
			
		||||
 | 
			
		||||
1. 一般是端点链接输入不对,在F版本的spring cloud 中,输入监控的端点链接是 http://localhost:8030/actuator/hystrix.stream ,中间是有/actuator/(之前版本的没有/actuator/)
 | 
			
		||||
 | 
			
		||||
2.  没有暴露端点链接,暴露端点链接有两种方式,一种是我们在上文中提到的基于配置的方式
 | 
			
		||||
 | 
			
		||||
   ```yaml
 | 
			
		||||
   management:
 | 
			
		||||
     endpoints:
 | 
			
		||||
       web:
 | 
			
		||||
         exposure:
 | 
			
		||||
           # 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
 | 
			
		||||
           include: "*"
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
   第二种方式是基于代码的方式,如下:
 | 
			
		||||
 | 
			
		||||
   ```java
 | 
			
		||||
    @Bean
 | 
			
		||||
   public ServletRegistrationBean getServlet() {
 | 
			
		||||
       HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
 | 
			
		||||
       ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
 | 
			
		||||
       registrationBean.setLoadOnStartup(1);
 | 
			
		||||
       registrationBean.addUrlMappings("/actuator/hystrix.stream");
 | 
			
		||||
       registrationBean.setName("HystrixMetricsStreamServlet");
 | 
			
		||||
       return registrationBean;
 | 
			
		||||
   }
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
   这两种方式二选一即可,就算是采用代码的方式,还是建议将地址设置为/actuator/hystrix.stream,而不是原来的hystrix.stream,因为turbine默认也是从/actuator/hystrix.stream去获取信息。
 | 
			
		||||
 | 
			
		||||
#### 5.2 页面一直loading 或者访问端点页面一直出现ping
 | 
			
		||||
 | 
			
		||||
这种情况是熔断器所在的方法没有被调用,所以没有产生监控数据,不是整合问题,这时候调用一下熔断器所在方法即可。
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,309 +1,331 @@
 | 
			
		||||
# spring-cloud-ribbon
 | 
			
		||||
 | 
			
		||||
## 一、ribbon 简介
 | 
			
		||||
 | 
			
		||||
ribbon是Netfix公司开源的负载均衡组件,采用服务端负载均衡的方式,即消费者客户端维护可用的服务列表,并通过负载均衡的方式将请求按照指定的策略分摊给消费者,从而达到负载均衡的方式。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
+ common: 公共的接口和实体类;
 | 
			
		||||
+ consumer: 服务的消费者,采用RestTemplate调用产品服务;
 | 
			
		||||
+ producer:服务的提供者;
 | 
			
		||||
+ eureka: 注册中心,ribbon 从注册中心获取可用的服务列表,是实现负载均衡的基础。这里使用我们在[服务的注册与发现](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-eureka)这个用例中搭建的简单注册中心作为测试即可。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、服务提供者的实现
 | 
			
		||||
 | 
			
		||||
#### 3.1 产品服务由`ProductService`提供,并通过`ProducerController`将服务暴露给外部调用。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/ribbon-producer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
ProductService.java:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 产品提供接口实现类
 | 
			
		||||
 * 这里为了之后直观的看到负载均衡的结果,我们继承了 ApplicationListener,从 WebServerInitializedEvent 获取服务的端口号,并拼接在产品名称上
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
public class ProductService implements IProductService, ApplicationListener<WebServerInitializedEvent> {
 | 
			
		||||
 | 
			
		||||
    private static List<Product> productList = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    public Product queryProductById(int id) {
 | 
			
		||||
        for (Product product : productList) {
 | 
			
		||||
            if (product.getId() == id) {
 | 
			
		||||
                return product;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<Product> queryAllProducts() {
 | 
			
		||||
        return productList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveProduct(Product product) {
 | 
			
		||||
        productList.add(product);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onApplicationEvent(WebServerInitializedEvent event) {
 | 
			
		||||
        int port = event.getWebServer().getPort();
 | 
			
		||||
        for (long i = 0; i < 20; i++) {
 | 
			
		||||
            productList.add(new Product(i, port + "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
ProducerController.java:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RestController
 | 
			
		||||
public class ProducerController {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private IProductService productService;
 | 
			
		||||
 | 
			
		||||
    @GetMapping("products")
 | 
			
		||||
    public List<Product> productList() {
 | 
			
		||||
        return productService.queryAllProducts();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @GetMapping("product/{id}")
 | 
			
		||||
    public Product productDetail(@PathVariable int id) {
 | 
			
		||||
        return productService.queryProductById(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostMapping("product")
 | 
			
		||||
    public void save(@RequestBody Product product) {
 | 
			
		||||
        productService.saveProduct(product);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 3.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
server:
 | 
			
		||||
  port: 8020
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: producer
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class ProducerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ProducerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、服务消费者的实现
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/ribbon-consumer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 4.1 导入负载均衡需要的依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--ribbon 依赖-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 指定注册中心地址,并在启动类上开启自动注册@EnableDiscoveryClient
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
server:
 | 
			
		||||
  port: 8080
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: consumer
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class ConsumerApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ConsumerApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.3 使用@LoadBalanced配置RestTemplate即可实现客户端负载均衡
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.web.client.RestTemplate;
 | 
			
		||||
 | 
			
		||||
@Configuration
 | 
			
		||||
public class RibbonConfig {
 | 
			
		||||
 | 
			
		||||
    @LoadBalanced   // 配置客户端负载均衡
 | 
			
		||||
    @Bean
 | 
			
		||||
    public RestTemplate restTemplate() {
 | 
			
		||||
        return new RestTemplate();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.4 使用RestTemplate调用远程服务
 | 
			
		||||
 | 
			
		||||
这里我们在调用远程服务的时候,url填写的是服务名+具体接口地址 ,由于我们的同一个服务会存在多个实例,在使用@LoadBalanced配置RestTemplate调用服务时,客户端就会从按照指定的负载均衡的方式将请求分摊到多个实例上。(默认的负载均衡采用的是RoundRobinRule(轮询)的策略,有特殊需求时候可以采用其他内置的策略规则,或者实现IRule来定义自己的负载均衡策略)。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Service
 | 
			
		||||
public class ProductService implements IProductService {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private RestTemplate restTemplate;
 | 
			
		||||
 | 
			
		||||
    public Product queryProductById(int id) {
 | 
			
		||||
        ResponseEntity<Product> responseEntity = restTemplate.getForEntity("http://producer/product/{1}", Product.class, id);
 | 
			
		||||
        return responseEntity.getBody();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public List<Product> queryAllProducts() {
 | 
			
		||||
        ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://producer/products", List.class);
 | 
			
		||||
        List<Product> productList = responseEntity.getBody();
 | 
			
		||||
        return productList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void saveProduct(Product product) {
 | 
			
		||||
        restTemplate.postForObject("http://producer/product", product, Void.class);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、启动测试
 | 
			
		||||
 | 
			
		||||
#### 5.1 启动一个Eureka服务、三个producer服务(注意区分端口)、和一个消费者服务
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div>
 | 
			
		||||
 | 
			
		||||
**服务注册中心:**
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-eureka.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 5.2  访问http://localhost:8080/sell/products 查看负载均衡的调用结果
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8020.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-products-8030.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 六、 附1: 关于RestTemplate的说明
 | 
			
		||||
 | 
			
		||||
#### 6.1  restTemplate 规范
 | 
			
		||||
 | 
			
		||||
restTemplate 调用对应resultful接口时候,使用的方法应该与接口声明方式(@GetMapping、@PostMapping、@PutMapping、@DeleteMapping)保持一致。请求类型与对应的调用方法如下。
 | 
			
		||||
 | 
			
		||||
- GET请求(getForObject 、getForEntity)
 | 
			
		||||
- POST请求(postForObject 、postForEntity)
 | 
			
		||||
- PUT请求(put)
 | 
			
		||||
- DELETE请求 (delete)
 | 
			
		||||
 | 
			
		||||
#### 6.2 ForEntity()和ForObject的区别
 | 
			
		||||
 | 
			
		||||
- `ForEntity()`发送一个请求,返回的ResponseEntity包含了响应体所映射成的对象
 | 
			
		||||
 | 
			
		||||
- `ForObject()`发送一个请求,返回的请求体将映射为一个对象
 | 
			
		||||
 | 
			
		||||
例如:
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
ResponseEntity<Product> responseEntity = restTemplate.getForEntity("http://producer/product/{1}", Product.class, id);
 | 
			
		||||
Product product = restTemplate.getForObject("http://producer/product/{1}", Product.class, id);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 七、 附2: 关于ribbon更多负载均衡的策略
 | 
			
		||||
 | 
			
		||||
Ribbon内置了多种负载均衡策略,如果有更复杂的需求,可以自己实现IRule。
 | 
			
		||||
 | 
			
		||||
#### 7.1 内置的负载均衡的策略如下图
 | 
			
		||||
 | 
			
		||||

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

 | 
			
		||||
 | 
			
		||||
图片来源于博客:[Ribbon负载均衡策略与自定义配置](https://blog.csdn.net/jrn1012/article/details/77837680)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 7.2 配置文件指定负载均衡的方式
 | 
			
		||||
 | 
			
		||||
要设置`IRule`名为的服务名称`users`,您可以设置以下属性:
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
users:
 | 
			
		||||
  ribbon:
 | 
			
		||||
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 7.3 代码指定负载均衡的方式
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Configuration
 | 
			
		||||
@RibbonClient(name = "custom", configuration = CustomConfiguration.class)
 | 
			
		||||
public class TestConfiguration {
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@Configuration
 | 
			
		||||
public class CustomConfiguration {
 | 
			
		||||
 | 
			
		||||
	@Bean
 | 
			
		||||
	public IRule ribbonRule() {
 | 
			
		||||
		return new BestAvailableRule();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
在使用代码方式的时候需要注意 [官方文档](http://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html#_customizing_the_ribbon_client)中关于注解方式有以下强调:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
The CustomConfiguration clas must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,93 +1,102 @@
 | 
			
		||||
# spring-sleuth-zipkin
 | 
			
		||||
 | 
			
		||||
## 一、简介
 | 
			
		||||
 | 
			
		||||
在微服务架构中,几乎每一个前端的请求都会经过多个服务单元协调来提供服务,形成复杂的服务调用链路。当服务发生问题时候,很难知道问题来源于链路的哪一个环节,这时候就需要进行链路追踪。
 | 
			
		||||
 | 
			
		||||
zipkin 是一个开源的分布式跟踪系统,可以使用spring cloud sleuth 来轻松的集成 zipkin。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
这里的项目是在之前的 [spring-cloud-zuul](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-zuul) 进行集成,zuul 项目的产品接口调用链路从 网关 -> consumer -> producer,历经三个环节的调用链路可以直观展示zipkin对链路追踪可视化的好处。
 | 
			
		||||
 | 
			
		||||
+ common: 公共的接口和实体类;
 | 
			
		||||
+ consumer: 服务的消费者,采用feign调用产品服务;
 | 
			
		||||
+ producer:服务的提供者;
 | 
			
		||||
+ eureka: 注册中心;
 | 
			
		||||
+ zuul: api网关。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-sleuth-zipkin.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 三、构建 zipkin 服务端
 | 
			
		||||
 | 
			
		||||
zipkin 客户端可以不用自己构建,直接从[官网](https://zipkin.io/pages/quickstart)上下载对应的jar 包启动即可,默认端口 9411
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
java -jar zipkin.jar
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
可以直接从docker仓库拉取,然后启动容器:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
docker run -d -p 9411:9411 openzipkin/zipkin
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-download.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、集成zipkin
 | 
			
		||||
 | 
			
		||||
这里我们对zuul、consumer、producer 三个模块都进行集成
 | 
			
		||||
 | 
			
		||||
#### 4.1 对三个模块(zuul、consumer、producer )添加依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--zipkin-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-zipkin</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 分别在三个模块的application.yml 配置文件中指定zipkin的地址
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  zipkin:
 | 
			
		||||
    base-url: http://localhost:9411/
 | 
			
		||||
  # 可以指定监控数据的采样率
 | 
			
		||||
  sleuth:
 | 
			
		||||
    sampler:
 | 
			
		||||
      probability: 1
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、启动项目
 | 
			
		||||
 | 
			
		||||
分别启动,eureka,zuul,consumer,producer,zuul ,访问 http://localhost:9411/ ,查看我们的服务调用链路
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin.png"/> </div>
 | 
			
		||||
 | 
			
		||||
点击链路,则可以查看具体的调用情况
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-detail.png"/> </div>
 | 
			
		||||
 | 
			
		||||
展示信息说明:
 | 
			
		||||
 | 
			
		||||
Span : 基本工作单元,发送一个远程调度任务就会产生一个 Span。 
 | 
			
		||||
 | 
			
		||||
Trace:由一系列 Span 组成的,呈树状结构。 所有由这个请求产生的 Span 组成了这个 Trace 。 
 | 
			
		||||
 | 
			
		||||
SpanId ; 工作单元 (Span) 的唯一标识。 
 | 
			
		||||
 | 
			
		||||
TraceId :  一条请求链路 (Trace) 的唯 一 标识。
 | 
			
		||||
 | 
			
		||||
除了TraceID外,还需要SpanID用于记录调用父子关系。每个服务会记录下parent id和span id,通过他们可以组织一次完整调用链的父子关系。
 | 
			
		||||
# spring-sleuth-zipkin
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一简介">一、简介</a><br/>
 | 
			
		||||
<a href="#二项目结构">二、项目结构</a><br/>
 | 
			
		||||
<a href="#三构建-zipkin-服务端">三、构建 zipkin 服务端</a><br/>
 | 
			
		||||
<a href="#四集成zipkin">四、集成zipkin</a><br/>
 | 
			
		||||
        <a href="#41-对三个模块zuulconsumerproducer-添加依赖">4.1 对三个模块(zuul、consumer、producer )添加依赖</a><br/>
 | 
			
		||||
        <a href="#42-分别在三个模块的applicationyml-配置文件中指定zipkin的地址">4.2 分别在三个模块的application.yml 配置文件中指定zipkin的地址</a><br/>
 | 
			
		||||
<a href="#五启动项目">五、启动项目</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
## 一、简介
 | 
			
		||||
 | 
			
		||||
在微服务架构中,几乎每一个前端的请求都会经过多个服务单元协调来提供服务,形成复杂的服务调用链路。当服务发生问题时候,很难知道问题来源于链路的哪一个环节,这时候就需要进行链路追踪。
 | 
			
		||||
 | 
			
		||||
zipkin 是一个开源的分布式跟踪系统,可以使用spring cloud sleuth 来轻松的集成 zipkin。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
这里的项目是在之前的 [spring-cloud-zuul](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-zuul) 进行集成,zuul 项目的产品接口调用链路从 网关 -> consumer -> producer,历经三个环节的调用链路可以直观展示zipkin对链路追踪可视化的好处。
 | 
			
		||||
 | 
			
		||||
+ common: 公共的接口和实体类;
 | 
			
		||||
+ consumer: 服务的消费者,采用feign调用产品服务;
 | 
			
		||||
+ producer:服务的提供者;
 | 
			
		||||
+ eureka: 注册中心;
 | 
			
		||||
+ zuul: api网关。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-sleuth-zipkin.png"/> </div>
 | 
			
		||||
 | 
			
		||||
## 三、构建 zipkin 服务端
 | 
			
		||||
 | 
			
		||||
zipkin 客户端可以不用自己构建,直接从[官网](https://zipkin.io/pages/quickstart)上下载对应的jar 包启动即可,默认端口 9411
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
java -jar zipkin.jar
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
可以直接从docker仓库拉取,然后启动容器:
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
docker run -d -p 9411:9411 openzipkin/zipkin
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-download.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、集成zipkin
 | 
			
		||||
 | 
			
		||||
这里我们对zuul、consumer、producer 三个模块都进行集成
 | 
			
		||||
 | 
			
		||||
#### 4.1 对三个模块(zuul、consumer、producer )添加依赖
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<!--zipkin-->
 | 
			
		||||
<dependency>
 | 
			
		||||
    <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
    <artifactId>spring-cloud-starter-zipkin</artifactId>
 | 
			
		||||
</dependency>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.2 分别在三个模块的application.yml 配置文件中指定zipkin的地址
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
spring:
 | 
			
		||||
  zipkin:
 | 
			
		||||
    base-url: http://localhost:9411/
 | 
			
		||||
  # 可以指定监控数据的采样率
 | 
			
		||||
  sleuth:
 | 
			
		||||
    sampler:
 | 
			
		||||
      probability: 1
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、启动项目
 | 
			
		||||
 | 
			
		||||
分别启动,eureka,zuul,consumer,producer,zuul ,访问 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,通过他们可以组织一次完整调用链的父子关系。
 | 
			
		||||
 | 
			
		||||
@@ -1,388 +1,408 @@
 | 
			
		||||
# spring-cloud-zuul
 | 
			
		||||
 | 
			
		||||
## 一、zuul简介
 | 
			
		||||
 | 
			
		||||
### 1.1 API 网关
 | 
			
		||||
 | 
			
		||||
api 网关是整个微服务系统的门面,所有的外部访问需要通过网关进行调度和过滤。它实现了请求转发、负载均衡、校验过滤、错误熔断、服务聚合等功能。
 | 
			
		||||
 | 
			
		||||
下图是直观的显示api Gateway 在微服务网关中的作用(图片引用自spring boot 官网)。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/apiGateway.png"/> </div>
 | 
			
		||||
 | 
			
		||||
### 1.2 zuul
 | 
			
		||||
 | 
			
		||||
spring cloud 中提供了基础Net flix Zuul 实现的网关组件,这就是Zuul,它除了实现负载均衡、错误熔断、路由转发等功能,还能与spring 其他组件无缝配合使用。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
[spring-cloud-feign](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-feign)用例已经实现通过feign实现服务间的调用,且提供了两个业务服务单元(consumer、producer),可以方便直观的测试zuul的路由、负载均衡、和错误熔断等功能,所以本用例在其基础上进行zuul的整合。
 | 
			
		||||
 | 
			
		||||
+ common: 公共的接口和实体类;
 | 
			
		||||
+ consumer: 服务的消费者,采用feign调用产品服务;
 | 
			
		||||
+ producer:服务的提供者;
 | 
			
		||||
+ eureka: 注册中心;
 | 
			
		||||
+ zuul: api网关。
 | 
			
		||||
 | 
			
		||||
聚合项目目录如下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-zuul.png"/> </div>
 | 
			
		||||
 | 
			
		||||
zuul 项目目录如下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、构建api 网关 zuul
 | 
			
		||||
 | 
			
		||||
#### 3.1 引入依赖
 | 
			
		||||
 | 
			
		||||
主要的依赖是 spring-cloud-starter-netflix-zuul
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-parent</artifactId>
 | 
			
		||||
        <version>2.0.8.RELEASE</version>
 | 
			
		||||
        <relativePath/>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>zuul</artifactId>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-freemarker</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--eureka-client-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--zuul-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <dependencyManagement>
 | 
			
		||||
        <dependencies>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
                <artifactId>spring-cloud-dependencies</artifactId>
 | 
			
		||||
                <version>${spring-cloud.version}</version>
 | 
			
		||||
                <type>pom</type>
 | 
			
		||||
                <scope>import</scope>
 | 
			
		||||
            </dependency>
 | 
			
		||||
        </dependencies>
 | 
			
		||||
    </dependencyManagement>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.2 在启动类上添加注解@EnableZuulProxy和@EnableDiscoveryClient
 | 
			
		||||
 | 
			
		||||
@EnableZuulProxy会自动设置Zuul服务器端点并在其中开启一些反向代理过滤器,以便将请求转发到后端服务器。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableZuulProxy
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class ZuulApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ZuulApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.3  指定注册中心、配置网关的路由规则
 | 
			
		||||
 | 
			
		||||
zuul 需要指定注册中心的地址,zuul 会从eureka获取其他微服务的实例信息,然后按照指定的路由规则进行请求转发。
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8090
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: zuul
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
# 网关的路由
 | 
			
		||||
zuul:
 | 
			
		||||
  routes:
 | 
			
		||||
    xxxx: #这个地方的值是可以任意的字符串
 | 
			
		||||
      path: /producer/**
 | 
			
		||||
      serviceId: producer
 | 
			
		||||
    consumer:
 | 
			
		||||
      path: /consumer/**
 | 
			
		||||
      serviceId: consumer
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.4  启动eureka、producer、consumer、zuul服务,访问 localhost:8090/consumer/sell/product 
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、错误熔断
 | 
			
		||||
 | 
			
		||||
#### 4.1  zuul 默认整合了 hystrix ,不用导入其他额外依赖
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-hystrix.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 4.2 创建 CustomZuulFallbackProvider并实现FallbackProvider 接口,同时用@Component声明为spring 组件,即可实现熔断时候的回退服务
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : zuul 的熔断器
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomZuulFallbackProvider implements FallbackProvider {
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 定义熔断将用于哪些路由的服务
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getRoute() {
 | 
			
		||||
        return "consumer";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
 | 
			
		||||
        return new ClientHttpResponse() {
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 返回响应的HTTP状态代码
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public HttpStatus getStatusCode() throws IOException {
 | 
			
		||||
                return HttpStatus.SERVICE_UNAVAILABLE;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 返回HTTP状态代码
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public int getRawStatusCode() throws IOException {
 | 
			
		||||
                return HttpStatus.SERVICE_UNAVAILABLE.value();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 返回响应的HTTP状态文本
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public String getStatusText() throws IOException {
 | 
			
		||||
                return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void close() {
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 将消息正文作为输入流返回
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public InputStream getBody() throws IOException {
 | 
			
		||||
                return new ByteArrayInputStream("商城崩溃了,请稍后重试!".getBytes());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 将消息正文作为输入流返回
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public HttpHeaders getHeaders() {
 | 
			
		||||
                HttpHeaders httpHeaders = new HttpHeaders();
 | 
			
		||||
                httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
 | 
			
		||||
                return httpHeaders;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
正确返回了内容、同时返回的http状态码也和我们设置的一样。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-broker.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、zuul  过滤器
 | 
			
		||||
 | 
			
		||||
创建自定义过滤器继承自CustomZuulFilter,当我们访问网关的时候,如果判断session 中没有对应的 code,则跳转到我们自定义的登录页面。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义filter过滤器
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomZuulFilter extends ZuulFilter {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 返回过滤器的类型
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String filterType() {
 | 
			
		||||
        return FilterConstants.PRE_TYPE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 返回过滤器的优先级顺序
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int filterOrder() {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从此方法返回“true”意味着应该调用下面的 run()方法
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean shouldFilter() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * ZuulFilter的核心校验方法
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object run() throws ZuulException {
 | 
			
		||||
        RequestContext currentContext = RequestContext.getCurrentContext();
 | 
			
		||||
        HttpServletRequest request = currentContext.getRequest();
 | 
			
		||||
        String code = (String)request.getSession().getAttribute("code");
 | 
			
		||||
        if (StringUtils.isEmpty(code)){
 | 
			
		||||
            // 设置值为false 不将请求转发到对应的服务上
 | 
			
		||||
            currentContext.setSendZuulResponse(false);
 | 
			
		||||
            // 设置返回的状态码
 | 
			
		||||
            currentContext.setResponseStatusCode(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
 | 
			
		||||
            HttpServletResponse response = currentContext.getResponse();
 | 
			
		||||
            try {
 | 
			
		||||
                // 跳转到登录页面
 | 
			
		||||
                response.sendRedirect("/index");
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                e.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
index.ftl:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <title>Title</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<form action="/login" method="post">
 | 
			
		||||
    <input name="username" type="text">
 | 
			
		||||
    <button id="btn">输入临时用户名后登录!</button>
 | 
			
		||||
</form>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 六、负载均衡
 | 
			
		||||
 | 
			
		||||
#### zuul 默认集成了ribbon 实现了负载均衡。只要启动多个实例即可查看到负载均衡的效果。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-ribbon.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 这里我们直接在idea 中启动多个实例来测试:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-config.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 负载均衡测试结果:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer-8040.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer-8030.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 七、附:关于版本问题可能导致的 zuul 启动失败
 | 
			
		||||
 | 
			
		||||
如果出现以下错误导致启动失败,是 spring boot 版本不兼容导致的错误,Finchley SR2版本 spring cloud 中的 zuul 和 spring boot 2.1.x 版本存在不兼容。如果出现这个问题,则将 spring boot 将至 2.0.x 的版本即可,用例中采用的是 2.0.8 版本。在实际的开发中应该严格遵循spring 官方的版本依赖说明。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
APPLICATION FAILED TO START
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
Description:
 | 
			
		||||
 | 
			
		||||
The bean 'counterFactory', defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulCounterFactoryConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulMetricsConfiguration.class] and overriding is disabled.
 | 
			
		||||
 | 
			
		||||
Action:
 | 
			
		||||
 | 
			
		||||
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**spring cloud 版本说明**:
 | 
			
		||||
 | 
			
		||||
| Release Train | Boot Version |
 | 
			
		||||
| ------------- | ------------ |
 | 
			
		||||
| Greenwich     | 2.1.x        |
 | 
			
		||||
| Finchley      | 2.0.x        |
 | 
			
		||||
| Edgware       | 1.5.x        |
 | 
			
		||||
| Dalston       | 1.5.x        |
 | 
			
		||||
 | 
			
		||||
更多组件的版本说明可以在[spring cloud overview](https://spring.io/projects/spring-cloud#overview) 页面查看。
 | 
			
		||||
# spring-cloud-zuul
 | 
			
		||||
 | 
			
		||||
## 目录<br/>
 | 
			
		||||
<a href="#一zuul简介">一、zuul简介</a><br/>
 | 
			
		||||
    <a href="#11-API-网关">1.1 API 网关</a><br/>
 | 
			
		||||
    <a href="#12-zuul">1.2 zuul</a><br/>
 | 
			
		||||
<a href="#二项目结构">二、项目结构</a><br/>
 | 
			
		||||
<a href="#三构建api-网关-zuul">三、构建api 网关 zuul</a><br/>
 | 
			
		||||
        <a href="#31-引入依赖">3.1 引入依赖</a><br/>
 | 
			
		||||
        <a href="#32-在启动类上添加注解@EnableZuulProxy和@EnableDiscoveryClient">3.2 在启动类上添加注解@EnableZuulProxy和@EnableDiscoveryClient</a><br/>
 | 
			
		||||
        <a href="#33--指定注册中心配置网关的路由规则">3.3  指定注册中心、配置网关的路由规则</a><br/>
 | 
			
		||||
        <a href="#34--启动eurekaproducerconsumerzuul服务访问-localhost8090/consumer/sell/product">3.4  启动eureka、producer、consumer、zuul服务,访问 localhost:8090/consumer/sell/product </a><br/>
 | 
			
		||||
<a href="#四错误熔断">四、错误熔断</a><br/>
 | 
			
		||||
        <a href="#41--zuul-默认整合了-hystrix-不用导入其他额外依赖">4.1  zuul 默认整合了 hystrix ,不用导入其他额外依赖</a><br/>
 | 
			
		||||
        <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/>
 | 
			
		||||
        <a href="#zuul-默认集成了ribbon-实现了负载均衡。只要启动多个实例即可查看到负载均衡的效果。">zuul 默认集成了ribbon 实现了负载均衡。只要启动多个实例即可查看到负载均衡的效果。</a><br/>
 | 
			
		||||
        <a href="#这里我们直接在idea-中启动多个实例来测试">这里我们直接在idea 中启动多个实例来测试:</a><br/>
 | 
			
		||||
        <a href="#负载均衡测试结果">负载均衡测试结果:</a><br/>
 | 
			
		||||
<a href="#七附关于版本问题可能导致的-zuul-启动失败">七、附:关于版本问题可能导致的 zuul 启动失败</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
## 一、zuul简介
 | 
			
		||||
 | 
			
		||||
### 1.1 API 网关
 | 
			
		||||
 | 
			
		||||
api 网关是整个微服务系统的门面,所有的外部访问需要通过网关进行调度和过滤。它实现了请求转发、负载均衡、校验过滤、错误熔断、服务聚合等功能。
 | 
			
		||||
 | 
			
		||||
下图是直观的显示api Gateway 在微服务网关中的作用(图片引用自spring boot 官网)。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/apiGateway.png"/> </div>
 | 
			
		||||
 | 
			
		||||
### 1.2 zuul
 | 
			
		||||
 | 
			
		||||
spring cloud 中提供了基础Net flix Zuul 实现的网关组件,这就是Zuul,它除了实现负载均衡、错误熔断、路由转发等功能,还能与spring 其他组件无缝配合使用。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 二、项目结构
 | 
			
		||||
 | 
			
		||||
[spring-cloud-feign](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-feign)用例已经实现通过feign实现服务间的调用,且提供了两个业务服务单元(consumer、producer),可以方便直观的测试zuul的路由、负载均衡、和错误熔断等功能,所以本用例在其基础上进行zuul的整合。
 | 
			
		||||
 | 
			
		||||
+ common: 公共的接口和实体类;
 | 
			
		||||
+ consumer: 服务的消费者,采用feign调用产品服务;
 | 
			
		||||
+ producer:服务的提供者;
 | 
			
		||||
+ eureka: 注册中心;
 | 
			
		||||
+ zuul: api网关。
 | 
			
		||||
 | 
			
		||||
聚合项目目录如下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-zuul.png"/> </div>
 | 
			
		||||
 | 
			
		||||
zuul 项目目录如下:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 三、构建api 网关 zuul
 | 
			
		||||
 | 
			
		||||
#### 3.1 引入依赖
 | 
			
		||||
 | 
			
		||||
主要的依赖是 spring-cloud-starter-netflix-zuul
 | 
			
		||||
 | 
			
		||||
```xml
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <parent>
 | 
			
		||||
        <groupId>org.springframework.boot</groupId>
 | 
			
		||||
        <artifactId>spring-boot-starter-parent</artifactId>
 | 
			
		||||
        <version>2.0.8.RELEASE</version>
 | 
			
		||||
        <relativePath/>
 | 
			
		||||
    </parent>
 | 
			
		||||
 | 
			
		||||
    <artifactId>zuul</artifactId>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <java.version>1.8</java.version>
 | 
			
		||||
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-freemarker</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-test</artifactId>
 | 
			
		||||
            <scope>test</scope>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--eureka-client-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!--zuul-->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
        <plugins>
 | 
			
		||||
            <plugin>
 | 
			
		||||
                <groupId>org.springframework.boot</groupId>
 | 
			
		||||
                <artifactId>spring-boot-maven-plugin</artifactId>
 | 
			
		||||
            </plugin>
 | 
			
		||||
        </plugins>
 | 
			
		||||
    </build>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <dependencyManagement>
 | 
			
		||||
        <dependencies>
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>org.springframework.cloud</groupId>
 | 
			
		||||
                <artifactId>spring-cloud-dependencies</artifactId>
 | 
			
		||||
                <version>${spring-cloud.version}</version>
 | 
			
		||||
                <type>pom</type>
 | 
			
		||||
                <scope>import</scope>
 | 
			
		||||
            </dependency>
 | 
			
		||||
        </dependencies>
 | 
			
		||||
    </dependencyManagement>
 | 
			
		||||
 | 
			
		||||
</project>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.2 在启动类上添加注解@EnableZuulProxy和@EnableDiscoveryClient
 | 
			
		||||
 | 
			
		||||
@EnableZuulProxy会自动设置Zuul服务器端点并在其中开启一些反向代理过滤器,以便将请求转发到后端服务器。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@SpringBootApplication
 | 
			
		||||
@EnableZuulProxy
 | 
			
		||||
@EnableDiscoveryClient
 | 
			
		||||
public class ZuulApplication {
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        SpringApplication.run(ZuulApplication.class, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.3  指定注册中心、配置网关的路由规则
 | 
			
		||||
 | 
			
		||||
zuul 需要指定注册中心的地址,zuul 会从eureka获取其他微服务的实例信息,然后按照指定的路由规则进行请求转发。
 | 
			
		||||
 | 
			
		||||
```yaml
 | 
			
		||||
server:
 | 
			
		||||
  port: 8090
 | 
			
		||||
# 指定服务命名
 | 
			
		||||
spring:
 | 
			
		||||
  application:
 | 
			
		||||
    name: zuul
 | 
			
		||||
# 指定注册中心地址
 | 
			
		||||
eureka:
 | 
			
		||||
  client:
 | 
			
		||||
    serviceUrl:
 | 
			
		||||
      defaultZone: http://localhost:8010/eureka/
 | 
			
		||||
# 网关的路由
 | 
			
		||||
zuul:
 | 
			
		||||
  routes:
 | 
			
		||||
    xxxx: #这个地方的值是可以任意的字符串
 | 
			
		||||
      path: /producer/**
 | 
			
		||||
      serviceId: producer
 | 
			
		||||
    consumer:
 | 
			
		||||
      path: /consumer/**
 | 
			
		||||
      serviceId: consumer
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### 3.4  启动eureka、producer、consumer、zuul服务,访问 localhost:8090/consumer/sell/product 
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 四、错误熔断
 | 
			
		||||
 | 
			
		||||
#### 4.1  zuul 默认整合了 hystrix ,不用导入其他额外依赖
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-hystrix.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 4.2 创建 CustomZuulFallbackProvider并实现FallbackProvider 接口,同时用@Component声明为spring 组件,即可实现熔断时候的回退服务
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : zuul 的熔断器
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomZuulFallbackProvider implements FallbackProvider {
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 定义熔断将用于哪些路由的服务
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getRoute() {
 | 
			
		||||
        return "consumer";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
 | 
			
		||||
        return new ClientHttpResponse() {
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 返回响应的HTTP状态代码
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public HttpStatus getStatusCode() throws IOException {
 | 
			
		||||
                return HttpStatus.SERVICE_UNAVAILABLE;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 返回HTTP状态代码
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public int getRawStatusCode() throws IOException {
 | 
			
		||||
                return HttpStatus.SERVICE_UNAVAILABLE.value();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 返回响应的HTTP状态文本
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public String getStatusText() throws IOException {
 | 
			
		||||
                return HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Override
 | 
			
		||||
            public void close() {
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 将消息正文作为输入流返回
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public InputStream getBody() throws IOException {
 | 
			
		||||
                return new ByteArrayInputStream("商城崩溃了,请稍后重试!".getBytes());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 将消息正文作为输入流返回
 | 
			
		||||
             */
 | 
			
		||||
            @Override
 | 
			
		||||
            public HttpHeaders getHeaders() {
 | 
			
		||||
                HttpHeaders httpHeaders = new HttpHeaders();
 | 
			
		||||
                httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
 | 
			
		||||
                return httpHeaders;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
正确返回了内容、同时返回的http状态码也和我们设置的一样。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-broker.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 五、zuul  过滤器
 | 
			
		||||
 | 
			
		||||
创建自定义过滤器继承自CustomZuulFilter,当我们访问网关的时候,如果判断session 中没有对应的 code,则跳转到我们自定义的登录页面。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
/**
 | 
			
		||||
 * @author : heibaiying
 | 
			
		||||
 * @description : 自定义filter过滤器
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
public class CustomZuulFilter extends ZuulFilter {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 返回过滤器的类型
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String filterType() {
 | 
			
		||||
        return FilterConstants.PRE_TYPE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 返回过滤器的优先级顺序
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int filterOrder() {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从此方法返回“true”意味着应该调用下面的 run()方法
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean shouldFilter() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * ZuulFilter的核心校验方法
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object run() throws ZuulException {
 | 
			
		||||
        RequestContext currentContext = RequestContext.getCurrentContext();
 | 
			
		||||
        HttpServletRequest request = currentContext.getRequest();
 | 
			
		||||
        String code = (String)request.getSession().getAttribute("code");
 | 
			
		||||
        if (StringUtils.isEmpty(code)){
 | 
			
		||||
            // 设置值为false 不将请求转发到对应的服务上
 | 
			
		||||
            currentContext.setSendZuulResponse(false);
 | 
			
		||||
            // 设置返回的状态码
 | 
			
		||||
            currentContext.setResponseStatusCode(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
 | 
			
		||||
            HttpServletResponse response = currentContext.getResponse();
 | 
			
		||||
            try {
 | 
			
		||||
                // 跳转到登录页面
 | 
			
		||||
                response.sendRedirect("/index");
 | 
			
		||||
            } catch (IOException e) {
 | 
			
		||||
                e.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
index.ftl:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!doctype html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <title>Title</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<form action="/login" method="post">
 | 
			
		||||
    <input name="username" type="text">
 | 
			
		||||
    <button id="btn">输入临时用户名后登录!</button>
 | 
			
		||||
</form>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 六、负载均衡
 | 
			
		||||
 | 
			
		||||
#### zuul 默认集成了ribbon 实现了负载均衡。只要启动多个实例即可查看到负载均衡的效果。
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-ribbon.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 这里我们直接在idea 中启动多个实例来测试:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-config.png"/> </div>
 | 
			
		||||
 | 
			
		||||
#### 负载均衡测试结果:
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer-8040.png"/> </div>
 | 
			
		||||
 | 
			
		||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-consumer-8030.png"/> </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 七、附:关于版本问题可能导致的 zuul 启动失败
 | 
			
		||||
 | 
			
		||||
如果出现以下错误导致启动失败,是 spring boot 版本不兼容导致的错误,Finchley SR2版本 spring cloud 中的 zuul 和 spring boot 2.1.x 版本存在不兼容。如果出现这个问题,则将 spring boot 将至 2.0.x 的版本即可,用例中采用的是 2.0.8 版本。在实际的开发中应该严格遵循spring 官方的版本依赖说明。
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
APPLICATION FAILED TO START
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
Description:
 | 
			
		||||
 | 
			
		||||
The bean 'counterFactory', defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulCounterFactoryConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/cloud/netflix/zuul/ZuulServerAutoConfiguration$ZuulMetricsConfiguration.class] and overriding is disabled.
 | 
			
		||||
 | 
			
		||||
Action:
 | 
			
		||||
 | 
			
		||||
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**spring cloud 版本说明**:
 | 
			
		||||
 | 
			
		||||
| Release Train | Boot Version |
 | 
			
		||||
| ------------- | ------------ |
 | 
			
		||||
| Greenwich     | 2.1.x        |
 | 
			
		||||
| Finchley      | 2.0.x        |
 | 
			
		||||
| Edgware       | 1.5.x        |
 | 
			
		||||
| Dalston       | 1.5.x        |
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
        <a href="#1在resources文件夹下新建数据库配置文件jdbcproperties">1、在resources文件夹下新建数据库配置文件jdbc.properties</a><br/>
 | 
			
		||||
        <a href="#2配置Jdbc数据源并定义事务管理器">2、配置Jdbc数据源并定义事务管理器</a><br/>
 | 
			
		||||
        <a href="#3新建查询接口及其实现类这里我查询的表是mysql和oracle中的字典表">3、新建查询接口及其实现类</a><br/>
 | 
			
		||||
        <a href="#4新建测试类进行测试">4.新建测试类进行测试</a><br/>
 | 
			
		||||
        <a href="#4新建测试类进行测试">4、新建测试类进行测试</a><br/>
 | 
			
		||||
## 正文<br/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -166,7 +166,7 @@ public class OracleDaoImpl implements OracleDao {
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.新建测试类进行测试
 | 
			
		||||
#### 4、新建测试类进行测试
 | 
			
		||||
 | 
			
		||||
```java
 | 
			
		||||
@RunWith(SpringRunner.class)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user