Hi,大家好。
目标:3个SpringBoot的项目,一个作为EurekaServer,另外2个作为客户端,客户端A用四种方式调用客户端B来实现通信。
知识点:
- Eureka的基本配置
- 界面查看服务注册情况及判断是否真正注册成功
- 三种方式实现服务间调用
服务名 | 作用 | 简称 |
EurekaServer | Eureka服务器 | |
ParamsValidation | 客户端 | 客户端B |
EurekaClient2 | 客户端 | 客户端A |
注册效果:
客户端分别使用:
@DiscoveryClient、Ribbon的RestTemplate来调用、Feign三种方式调用,postman数据连接:
详细代码见git仓库,由于模仿独立的项目,所以每个项目都是一个独立的仓库(也没法混合提交到一个仓库里)。
git@github.com:cmhhcm/SecurityTest.git 客户端B
git@github.com:cmhhcm/EurekaClient2.git 客户端A
git@github.com:cmhhcm/EurekaServer.git Eureka服务端
完整代码克隆下来,切换到develop分支,即可运行调试。
特别声明,处于安全考虑贴图的地方内网IP一律简化为:localhost了。
一、三种方式实现服务间调用
这里展示下核心代码和配置:详细代码见:EurekaClient2
1.1 如果没有Eureka,我们如何调用?
没有eureka的时候,A服务调用B服务,一般把B服务的URL配置到A服务的配置文件里(便于修改),然后直接这么调用。
package com.cmh.eurekaclient2.service.impl;
import com.cmh.eurekaclient2.value.BookQueryCondition;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Component
@Slf4j
public class ParamsValidationOriginalClient {
public String invokeParamsValidationServiceByOriginal() {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity httpEntity = new HttpEntity(new BookQueryCondition(), headers);
ResponseEntity<String> exchange = restTemplate.exchange("http://localhost:8080/ParamsValidation/params_validation/book/list", HttpMethod.POST, httpEntity, String.class, "");
String result = "";
if (exchange != null) {
result = exchange.getBody();
log.info("invoked body is:{}", result);
}
return result;
}
}
反正这么调用很不爽,说不出来的不爽。之前用Dubbo的时候,是不存在这种问题的。
1.2 方式1-@DiscoveryClient
核心:
1)启动类配置@EnableDiscoveryClient
2)调用类,A调用B,那么A调用的地方注入discoverClient
package com.cmh.eurekaclient2.service.impl;
import com.cmh.eurekaclient2.value.BookQueryCondition;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Component
@Slf4j
public class ParamsValidationDiscoveryClient {
@Autowired
private DiscoveryClient discoveryClient;
public String invokeParamsValidationService() {
RestTemplate restTemplate = new RestTemplate();
List<ServiceInstance> instances = discoveryClient.getInstances("PARAMSVALIDATION");
if (instances == null || instances.size() == 0) {
log.info("instances is empty..");
return "";
}
String uri = instances.get(0).getUri().toString();
String serviceUrl = String.format("%s/ParamsValidation/params_validation/book/list", uri);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
BookQueryCondition condition = new BookQueryCondition();
HttpEntity entity = new HttpEntity(condition, headers);
log.info("uri is :{},formatted url is:{}", uri, serviceUrl);
ResponseEntity<String> resultExchange = restTemplate.exchange(serviceUrl, HttpMethod.POST, entity, String.class, "");
if (resultExchange == null) {
log.info("invoke result is null");
return "";
}
String body = resultExchange.getBody();
log.info("body is :{}", body);
return body;
}
}
1.3 方式2-Ribbon的RestTemplate
核心:
1、restTemplate注入的时候加了@LoadBalanced,作用:可以直接用服务实例名(忽略大小写的)访问,对调用方隐藏了IP+端口
2、
package com.cmh.eurekaclient2.service.impl;
import com.cmh.eurekaclient2.value.BookQueryCondition;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Slf4j
@Component
public class ParamsValidationRestTemplateClient {
@Autowired
RestTemplate restTemplate;
public String invokeParamsValidationService() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
BookQueryCondition queryCondition = new BookQueryCondition();
HttpEntity httpEntity = new HttpEntity(queryCondition, headers);
ResponseEntity<String> exchange = restTemplate.exchange("http://ParamsValidation/ParamsValidation/params_validation/book/list", HttpMethod.POST, httpEntity, String.class, "");
String result = "";
if (exchange != null) {
result = exchange.getBody();
log.info("rest template invoke result: {}", result);
}
return result;
}
}
感觉本质和DiscoverClient类似,都是拿到服务实例。
1.4 方式3- feign
记住使用的2个核心注解都是来自这个依赖,而不是spring-cloud-netflix-core,因为这个在Maven仓库已经标记为废弃了,调试时候发现总是提示@FeignClient校验有问题,找不到url。
依赖:
<!--Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.0.1</version>
</dependency>
核心:
1)启动类加@EnableFeignClients注解
2)调用客户端加@FeignClient注解
package com.cmh.eurekaclient2.service.impl;
import com.cmh.eurekaclient2.value.BookQueryCondition;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient("ParamsValidation")
public interface ParamsValidationFeignClient {
@PostMapping("ParamsValidation/params_validation/book/list")
String invokeParamsValidationService(@RequestBody BookQueryCondition condition);
}
二、对比
调用方式 | 优点 | 缺点 | 推荐与否 |
Feign | 屏蔽了很多细节 | 暂未发现 | 是 |
Ribbon的RestTemplate | 屏蔽了IP和端口 | 加了注解的restTemplate无法调用其他未注册到Eureka的接口了,需要新new | 比较推荐 |
DiscoverClient | 过程,否 | ||
原始HTTP调用 | 摒弃 |
三、遇到的问题
1、Feign方式开始未调通,报如下错,后来更换了依赖,调通了。
java.lang.NoSuchMethodError: ‘java.lang.String org.springframework.core.annotation.AnnotationAttributes.getAliasedString(java.lang.String, java.lang.Class, java.lang.Object)’
FeignClientsRegistrar.validate
FeignClientsRegistrar.java:202)
重点查看报错,源码提示找不到url,错误很明显,但是明明已经配置了url的还提示找不到…
解决办法:依赖使用OpenFeign即可。
2、netflix-core 在Maven仓已被标记废弃,推荐使用 spring-cloud-netflix-hystrix
四、总结
1、这里仅仅介绍的是运用
2、需要了解原理,知道思想性的东西
3、研究下Dubbo,做对比,没有对比就没有伤害。
好了,晚安。