带你入门SpringCloud服务发现 | Eurka搭建和使用

80143853 2019-09-08

前言

服务注册与发现是微服务中最为基础的环节,而 Eureka 就是一个可以帮助你实现服务注册与发现的选择之一。如果你对 Eureka 和服务发现了解甚少,那么该篇博客将会帮助到你。文中通过具体操作带你了解如下内容:

  1. 什么是服务注册与发现
  2. 什么是 Eureka
  3. SpringCloud Eureka 单台环境搭建
  4. SpringCloud Eureka 高可用环境搭建
  5. SpringCloud Eureka + RestTemplate + Ribbion 的使用
  6. SpringCloud Feign + SpringCloud Eureka 的使用

阅读本文需要你熟悉SpringBoot项目的基本使用即可,还有一点需要注意的是在操作过程中尽量和我本地环境一致,因为环境不一致可能会带来一些问题。我本地环境如下:

SpringBoot Version: 2.1.0.RELEASE
SpringCloud Version: Greenwich.RELEASE
Apache Maven Version: 3.6.0
Java Version: 1.8.0_144
IDEA:Spring Tools Suite (STS)

服务注册与发现介绍

上面讲到服务发现是微服务中最为基础的环节,什么是服务发现呢 ?我们可以从单体架构说起,单体架构各个服务都在一起,是不需要被发现的。但是在微服务的架构中会出现很多的服务。服务与服务之间怎么获取调用地址URL 呢 ?例如 一个商城网站有商品服务和订单服务。查询订单服务时需要从商品服务中获取商品的信息。

如果有1个订单服务和1 个商品服务,订单服务服务在配置文件中将商品服务的 URL 配置在配置文件中即可。如果出现 2个商品服务,你可能会想到将2个商品服务URL 配置到订单服务中。如果出现上千个服务呢?你可能发现这种方式不是一个好的方案。同时还有一个问题就是:商品服务宕机后。如何将宕机的服务从配置文件中剔除。

我们可以将商品服务的 URL 地址信息都注册到统一的服务上,在调用时拉取所有商品服务的 URL 信息。订单服务可以自定义负载均衡策略来选取一个 URL 地址进行调用,如果注册中心发现某个商品服务已经宕机,可以将其从注册中心剔除。而今天的主角 Eureka 就是通过这种机制来完成服务发现的。

接下来就正式开始今天的主角 Eureka 的详细介绍!

Eureka 是什么 ?

在介绍操作前,首先来了解一下 Eureka 是什么?Eureka 是 Netflix 公司开源的一个基于 REST 服务的服务发现框架,主要用于AWS云,用于定位服务,以实现中间层服务器的负载平衡和故障转移。SpringCloud 对 Netflix 众多的开源框架都封装到其子项目spring-cloud-netflix 中,而 Eureka 就是其中的一个。SpringCloud 可以通过 Eureka 来完成微服务的服务注册与发现,当然你也可以使用其他的服务发现框架在 SpringCloud 中。

具体使用和介绍可以访问 Eureka 的 GitHub wiki 进行查看
https://github.com/Netflix/eu...
https://github.com/Netflix/eu...

闲话少说,开始我们的 Eureka 环境搭建!

SpringCloud Eureka 单台环境搭建

Eureka Server 端搭建

按下图所示打开SpringBoot Starter构建的窗口 File--New--Spring Starter Project

带你入门SpringCloud服务发现 | Eurka搭建和使用

勾选 Eureka Server

带你入门SpringCloud服务发现 | Eurka搭建和使用

如果不勾选 Eureka Server 也可以在SpringBoot项目中引入 spring-cloud-starter-netflix-eureka-server 依赖即可。代码如下:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

在 SpringBoot Application上声明 @EnableEurekaServer

带你入门SpringCloud服务发现 | Eurka搭建和使用

在 application.properties 添加配置信息

server.port=8761
spring.application.name=EUREKASERVER
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone= http://localhost:8761/eureka/
eureka.server.enable-self-preservation=false

server.port: 配置服务端的端口号。
spring.application.name: 配置服务端应用名称.。
eureka.client.register-with-eureka: 是否将自己注册到服务端,Eureka 服务端需要配置成false 默认是 true
eureka.client.fetch-registry:是否从其他Eureka服务端获取注册信息,默认为true。如过是单台服务器可以设置为false。

eureka.server.enable-self-preservation:开启自我保护模式,默认为 true 表示为开启。开发中为了方便可以将其设置为false。
eureka.client.service-url.defaultZone:设置 Eureka 服务端的地址,如果是单体服务就配置该 Eureka 单台服务的 ip+端口号+/eureka。需要注意的是 /eureka 一定要加上。

自我保护模式介绍:

默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。

自我保护机制的工作机制是如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:

1、Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。\
2、Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。\
3、当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。

如果 eureka.client.register-with-eureka=true 或者不配置该信息会,Eureka 服务端本身也注册到服务端如下图所示:

带你入门SpringCloud服务发现 | Eurka搭建和使用

Eureka Client 端搭建

首先在 SpringBoot Starter 构建的窗口 勾选 Eureka Discovery ClientSpring Web Starter 如下图所示:
带你入门SpringCloud服务发现 | Eurka搭建和使用

需要注意的是如果没有勾选 Spring Web Starter 客户端注册是注册不到服务端的。

如果不勾选,也可以直接在SpringBoot项目中引入下面2个依赖来完成。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

然后在 SpringBoot Application 上声明 @EnableDiscoveryClient 如下图所示:
带你入门SpringCloud服务发现 | Eurka搭建和使用

最后在 application.properties 配置如下信息:

server.port=8760
spring.application.name=EUREKACLIENT
eureka.client.service-url.defaultZone= http://localhost:8762/eureka/

客户端的 eureka.client.service-url.defaultZone 配置和Eureka Server(Eureka注册中心)配置一样。

访问 Eureka Server(Eureka注册中心)端如下图所示中Instances currently registered With Eureka中查看注册的客户端。

带你入门SpringCloud服务发现 | Eurka搭建和使用

如果你按照上述方式搭建并未成功,可以请参考我在GitHub 项目 spring-cloud-get-started 仓库中模块名为: spring-cloud-eureka-single-service(Eureka Server)和 spring-cloud-eureka-single-service-client( Eureka Client)进行对比查看是否配置有误。

spring-cloud-get-started 项目地址:https://github.com/zhuoqianmi...

搭建高可用的 Eureka 服务端

在生产环境中为保证 Eureka 服务的高可用,我们需要搭建多台 Eureka Server(Eureka注册中心)。Eureka Server(Eureka注册中心)搭建集群还是比较简单的,只需要让各个服务端互相注册即可。官网搭建集群框架图如下:

带你入门SpringCloud服务发现 | Eurka搭建和使用

我来举个例子,假如你有2台 Eureka Server 服务,名称分别为 Eureka1、Eureka2。

Eureka1的配置: 需要将 eureka.client.service-url.defaultZone 配置成 Eureka2 的服务地址。配置代码如下:
eureka.client.service-url.defaultZone=http://eureka2:8762/eureka/

Eureka2 的配置 需要将 eureka.client.service-url.defaultZone 配置成 Eureka1 的服务地址。配置代码如下:
eureka.client.service-url.defaultZone=http://eureka1:8761/eureka/

假如你有3台 Eureka 服务名称分别为 Eureka1、Eureka2、Eureka3。
Eureka1的配置: 需要将 eureka.client.service-url.defaultZone 配置上 Eureka2 和 Eureka3的服务地址并用逗号分隔。
eureka.client.service-url.defaultZone=http://eureka2:8762/eureka/,http://eureka3:8763/eureka/
Eureka2的配置: 需要将 eureka.client.service-url.defaultZone 配置上 Eureka1 和 Eureka3的服务地址并用逗号分隔。
eureka.client.service-url.defaultZone=http://eureka1:8761/eureka/,http://eureka3:8763/eureka/
Eureka3的配置: 需要将 eureka.client.service-url.defaultZone 配置上 Eureka1 和 Eureka2的服务地址并用逗号分隔。
eureka.client.service-url.defaultZone=http://eureka1:8761/eureka/,http://eureka2:8762/eureka/

需要注意的是如果配置了 eureka.client.fetch-registry 需要将其改为 true,如果没有配置可以忽略。eureka.client.fetch-registry 表示可以从其他 Eureka Server 获取注册信息。
还有一点需要注意的是,eureka.client.serviceUrl.defaultZone 配置不能使用 localhost。
如果你使用的是Windows 系统,并且只有一台机器的情况下,需要在 HOSTS 中配置三个域名信息。
带你入门SpringCloud服务发现 | Eurka搭建和使用
带你入门SpringCloud服务发现 | Eurka搭建和使用

我本地代码服务端和客户端搭建具体配置:

客户端和服务端引入依赖和添加@EnableXXX的注解比较简单,我这里就不在详细说明了。可以参考 SpringCloud Eureka 单台环境搭建 中的介绍。

主要需要注意的是 application.properties 中的配置,以下是我本地服务端和客户端配置详细信息:

Eureka服务1的配置

server.port=8761
spring.application.name=MULTISERVICEEUREKASERVER1
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://MULTISERVICEEUREKASERVER2:8762/eureka/,http://MULTISERVICEEUREKASERVER3:8763/eureka/
eureka.server.enable-self-preservation=falses

Eureka服务2的配置

server.port=8762
spring.application.name=MULTISERVICEEUREKASERVER2
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://MULTISERVICEEUREKASERVER1:8761/eureka/,http://MULTISERVICEEUREKASERVER3:8763/eureka/
eureka.server.enable-self-preservation=false

Eureka服务3的配置

server.port=8763
spring.application.name=MULTISERVICEEUREKASERVER3
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://MULTISERVICEEUREKASERVER1:8761/eureka/,http://MULTISERVICEEUREKASERVER2:8762/eureka/
eureka.server.enable-self-preservation=false

客户端的配置

server.port=8760
spring.application.name=MULTISERVICEEUREKACLIENT
eureka.client.service-url.defaultZone=http://MULTISERVICEEUREKASERVER1:8761/eureka/,http://MULTISERVICEEUREKASERVER2:8762/eureka/,http://MULTISERVICEEUREKASERVER3:8763/eureka/

Eureka服务1 控制台界面信息:
带你入门SpringCloud服务发现 | Eurka搭建和使用

Eureka服务2 控制台界面信息:
带你入门SpringCloud服务发现 | Eurka搭建和使用

Eureka服务3 控制台界面信息:
带你入门SpringCloud服务发现 | Eurka搭建和使用

如果你按照上述方式搭建并未成功,可以参考我在 GitHub 项目 spring-cloud-get-started 仓库中模块名为:
spring-cloud-eureka-multi-service-server1(MULTISERVICEEUREKASERVER1)
spring-cloud-eureka-multi-service-server2(MULTISERVICEEUREKASERVER2)
spring-cloud-eureka-multi-service-server3(MULTISERVICEEUREKASERVER3)
spring-cloud-eureka-multi-service-client(MULTISERVICEEUREKACLIENT)
进行对比查看是否配置有误。

spring-cloud-get-started 项目地址:https://github.com/zhuoqianmi...

SpringCloud Eureka + RestTemplate + Ribbion 使用

上面我们介绍如何搭建单机版和高可用版的 Eureka 环境,接下来我们来介绍如果通过 RestTemplate + Ribbion 从 Eureka Server 调用我们的 Eureka 客户端服务的 API 接口。

调用的流程是:消费端从 Eureka Server 获取我们要调用具体服务生产者(Eureka 客户端)的所有 URL 地址信息,然后通过 Ribbion 的负载策略选取一个服务地址。最后通过RestTemplate 进行调用具体服务 API 接口。

我通过一台 Eureka Server( Eureka注册中心服务)端 和 2个商品服务(Eureka客户端),外加一个服务消费者订单服务(Eureka客户端)。通过订单服务从 Eureka 注册中心服务获取2个商品服务(Eureka客户端)地址URL并通过 Ribbion 负载策略选取其中一个商品服务URL 进行服务的调用。

Eureka Server 注册中心服务的搭建:

添加 spring-cloud-starter-netflix-eureka-server 依赖,然后再 SpringBoot Application 上声明 @EnableEurekaServer,最后在 application.properties 中添加相关配置信息。

spring-cloud-starter-netflix-eureka-server 依赖配置如下:

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

application.properties 具体配置信息如下:

server.port=8761
spring.application.name=EUREKASERVER
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.server.enable-self-preservation=false

2个 商品服务 (Eureka 客户端)搭建:

添加 spring-cloud-starter-netflix-eureka-client 依赖,然后再 SpringBoot Application 上声明 @EnableDiscoveryClient ,最后在 application.properties 中添加相关配置信息。

分别在商品服务1 和商品服务2 中定义一样的 RestFul 接口,并通过返回不同的商品名称以示区分。

spring-cloud-starter-netflix-eureka-client 依赖配置如下:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

商品服务1 配置信息:

server.port=8760
spring.application.name=PRODUCTSERVER
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

商品服务1 API 接口代码如下:

@RestController
public class ProductController {
    
    @GetMapping("/product/{id}")
    public Product productInfo(@PathVariable(name="id")Long productId) {
        Product product = new Product();
        product.setId(productId);
        product.setName("product 1:"+"苹果");
        product.setPrice(12d);
        return product;
    }
}

商品服务 2 配置信息:

server.port=8770
spring.application.name=PRODUCTSERVER
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

商品服务 2 API 接口代码如下:

@RestController
public class ProductController {
    
    @GetMapping("/product/{id}")
    public Product productInfo(@PathVariable(name="id")Long productId) {
        Product product = new Product();
        product.setId(productId);
        product.setName("product 2:"+"苹果");
        product.setPrice(12d);
        return product;
    }
}

商品类的代码如下

public class Product {
    
    private Long id;
    private String name;
    private Double price;
    //省略 getter 和 setter
}

订单服务(Eureka客户端)搭建:

添加 spring-cloud-starter-netflix-eureka-client 依赖,然后再 SpringBoot Application 上声明 @EnableDiscoveryClient ,最后在 application.properties 中添加相关配置信息。

server.port=8762
spring.application.name=ORDERSERVICE
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

商品服务的调用需要使用到 RestTemplate,所以需要配置 RestTemplate 到Spring的上下文中。具体代码如下:

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate buildRestTemplate() {
        return new RestTemplate();
    }
}

通过 LoadBalancerClient 的 choose 方法来获取 ServiceInstance,ServiceInstance 就是2个 商品服务(PRODUCTSERVER)其中一个服务信息,LoadBalancerClient 是 SpringCloud 获取服务的抽象。具体实现是通过 spring-cloud-netflix-ribbon 项目中 RibbonLoadBalancerClient 来实现的。通过 ServiceInstance 获取商品服务(PRODUCTSERVER)的服务地址 URL。然后通过 RestTemplate 调用 API 接口。具体代码如下:

@RestController
public class ProductController {
    
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Autowired
    private RestTemplate restTemplate;
    
    @RequestMapping("/product/{id}")
    public String productInfo(@PathVariable(name="id")Long productId) {
        
        ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCTSERVER");
        URI uri = serviceInstance.getUri();
        String url = uri.toString()+"/product/"+productId;
        ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
        String message = forEntity.getBody();
        
        return message;
    }
}

我们也可以通过使用 @LoadBalanced 注解来完成上述操作,使用@LoadBalanced注解的方式相对上面使用 LoadBalancerClient 代码简洁了不少。具体操作如下:

在 RestTemplateConfig 构建方法上添加 @LoadBalanced 注解。具体代码如下:

@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate buildRestTemplate() {
        return new RestTemplate();
    }
}

在ProductController 中无需在注入 LoadBalancerClient,直接使用 RestTemplate 调用即可。具体代码如下:

@RestController
public class ProductController {
    
    @Autowired
    private RestTemplate restTemplate;
    
    @RequestMapping("/product/{id}")
    public String productInfo(@PathVariable(name="id")Long productId) {
        
        String url = "http://PRODUCTSERVER/product/"+productId;
        ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
        String message = forEntity.getBody();
        
        return message;
    }
}

测试:

整个测试过程需要启动 一台 注册中心服务、2台商品服务、1台订单服务,个人建议注册中心服务和商品服务通过java -jar的方式启动,而订单服务通过 IDEA 来启动。当然你可以可以都在IDEA 中进行启动所有的服务。

启动 Eureka 注册中心服务:
进入 Eureka 注册中心服务目录执行打包命令 mvn clean package -Dmaven.test.skip=true。会生成一个可执行的 jar 包如下图所示:
带你入门SpringCloud服务发现 | Eurka搭建和使用
然后进入 target目录 执行 java -jar jar包的名称,如下图所示:
带你入门SpringCloud服务发现 | Eurka搭建和使用
如下图所示:Eureka 注册服务启动成功!
带你入门SpringCloud服务发现 | Eurka搭建和使用

启动商品服务:
进入商品服务项目的目录,打包并且通过 java -jar 启动商品服务1项目。具体操作如下图所示:
带你入门SpringCloud服务发现 | Eurka搭建和使用
带你入门SpringCloud服务发现 | Eurka搭建和使用

商品 2服务操作和上述一致,这里就不再进行演示

最后访问服务注册服务,查看商品1和商品2服务是否注册到注册中心服务中,如下图所示表示注册成功!
带你入门SpringCloud服务发现 | Eurka搭建和使用

启动订单服务:
通过下图的方式启动订单服务:
带你入门SpringCloud服务发现 | Eurka搭建和使用
带你入门SpringCloud服务发现 | Eurka搭建和使用
访问 http://localhost:8762/product/1 从订单服务中获取商品服务的信息,如下图所示它会以轮询的方式调用商品服务1 和商品服务2。
带你入门SpringCloud服务发现 | Eurka搭建和使用
带你入门SpringCloud服务发现 | Eurka搭建和使用

如果你按照上述方式搭建并未成功,可以参考我在GitHub 项目 spring-cloud-get-started 仓库中模块名为:
spring-cloud-eureka-ribbion-service ( Eureka Server 服务注册中心 EUREKASERVER )
spring-cloud-eureka-ribbion-product (商品服务1)
spring-cloud-eureka-ribbion-product2(商品服务2)
spring-cloud-eureka-ribbion-service-order-consume(订单服务)
spring-cloud-eureka-ribbion-service-order-consume-annotation(订单服务注解版)
进行对比查看是否配置有误。

spring-cloud-get-started 项目地址:https://github.com/zhuoqianmi...

SpringCloud Feign + SpringCloud Eureka 使用

调用 Eureka 客户端服务还可以通过 SpringCloud Feign 来进行操作。Feign的底层还是使用Ribbion。

接下来我们开始 SpringCloud Feign 是用操作流程介绍,看到这里你有没有发SpringCloud 使用的套路。这个套路拢共分三步:
1.引入组件 starter依赖。
2.Application 上添加@EnableXXX注解。
3.appcation.properties上进行添加相关配置信息。

SpringCloud Feign也绕不开这个套路,第一步及先在订单服务添加 Feign starter。具体依赖代码如下:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

第二步是在 SpringBoot Application上声明 @EnableFeignClients 如下图所示:

带你入门SpringCloud服务发现 | Eurka搭建和使用

第三步是在application.properties中配置 Eureka客户端的配置,具体配置信息如下:

server.port=8762
spring.application.name=ORDERSERVICE
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

第四步是在 RestTemplateConfig 构建方法上声明 @LoadBalanced

@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate buildRestTemplate() {
        return new RestTemplate();
    }
}

第五步是创建调用接口,并在接口上声明 @FeignClient(name="PRODUCTSERVER") 其中name的值是调用服务的名称。在声明调用的方法 @RequestMapping()中配置 URL 和商品服务 API 接口一样即可。

@FeignClient(name="PRODUCTSERVER")
public interface ProductClient {
    @GetMapping("/product/{id}")
    public Product productInfo(@PathVariable(name="id")Long productId);
}

第六步开始编写调用的测试类,在ProductController 中引入 PersonClient 然后调用ProductClient 调用方法即可。

@RestController
public class ProductController {
    
    @Autowired
    private ProductClient personClient;
    
    @RequestMapping("/product/{id}")
    public Product productInfo(@PathVariable(name="id")Long productId) {
        Product product = personClient.productInfo(productId);
        return product;
    }
}

测试:

Feign 使用 和 Ribbion 使用是一样的服务注册中心和商品服务,启动方式请参考上面Feign 使用操作介绍。

访问 http://localhost:8762/product/1 调用订单服务,和使用Ribbion方式效果一致。具体访问结果如下:

带你入门SpringCloud服务发现 | Eurka搭建和使用

带你入门SpringCloud服务发现 | Eurka搭建和使用

SpringCloud Feign + SpringCloud Eureka 使用SpringCloud Eureka + RestTemplate + Ribbion 使用 共用 Eureka 服务端和服务生产者的项目,如果搭建过程中遇到问题可以参考我在GitHub 项目 spring-cloud-get-started 仓库中模块名为: spring-cloud-eureka-feign-service-order-consume 进行对比即可。

spring-cloud-get-started 项目地址:https://github.com/zhuoqianmi...

小结

SpringCloud Eureka 搭建和使用只要你掌握了搭建三步法:1.引入组件 starter依赖、2.Application 上添加@EnableXXX注解、3.appcation.properties上进行添加相关配置信息。关于具体的配置可以结合 Spring 官网文档进行查阅。

参考文献

https://spring.io/guides/gs/s...
https://cloud.spring.io/sprin...
https://cloud.spring.io/sprin...
http://seymours.cn/articles/2...

相关推荐