五、利用Feign实现声明试Rest调用
1、前景回顾
还记得我在《基于Ribbon实现负载均衡》里面的服务调用吗?如下:(例举其中一种)
/**
* getForEntity方式一
* @param userName
* @param pwd
* @param address
* @return
*/
public Domain getDomain12(String userName, String pwd, String address)
{
String url="http://app-8801/domain/get003?userName={0}&pwd={1}&address={2}";
ResponseEntity<Domain> responseEntity=restTemplate.getForEntity(url,Domain.class,userName,pwd,address);
return responseEntity.getBody();
}
我是通过注入RestTemplate实现服务的调用的。我是通过拼接字符串的方式构造Url的,例子中只有3个参数,感觉就有点受不了,现实环境中URL中有十几个参数的,那么这种方式就变得非常繁琐、低效了,代码也难以维护。Feign为我们解决了这问题。
2、Feign 简介
Feign是Netflix开发声明式、模板化的http客户端。Feign可以帮我们非常快速便捷、优雅的调用HTTP API。
在Springcloud中,Feign只要创建多个接口(其实就是服务调用),并在接口上添加一些注解,就可以实现服务调用了。Feign支持多种注解,Feign自带的注解和JAX-RS注解等。重要的是,SpringCloud对Feign进行了增强,试的Feign支持SpringMVC的注解,并整合了Eureka 与Ribbon,使我们在开发中更加便捷。
3、Feign实现APP2-8901 对 APP-8801的调用
我们只需要对调用方进行修改。
-
1.首先我们需要在App2 项目的pom.xml中引入Feign:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
并在启动类上面添加@EnableFeignClients 注解,启用Feign。
-
2.创建Feign 接口
/** * @Author: pangfei * @description:此处取代了 restTemplate方式的服务调用,采用基于Feign的模板化服务调用方式 * 每个服务可以设置单独的调用接口 * Feign 具有重构的特性,我们可以将每个服务提供的服务,抽象成一个接口,然后提供api jar 供 服务提供方和服务消费方来实现。 * 及利用SpringCloud Feign 的继承的特性来实现rest接口的定义。 * 此处由于只是demo,并未以rest 接口的形式去实现,如后期服务较多,可以尝试以rest接口的形式实现。可以进一步减少代码量的开发。 * 其中也有一定的缺陷:服务本身会依赖,api jar的接口,api接口的变动,会影响到服务本身。因此后期运营此方式,会有牵一发动全身的后果, * 所以要尽量做好前后版本的兼容。 * fallback 是基于feign的服务降级 * @Date: Create in 14:40 2018/1/15 */ @FeignClient(name = "app-8801",fallback = ServiceFallback.class) public interface App8801Feign { @GetMapping("/domain/get001/{id}") public Domain getDomain001(@PathVariable("id")@CacheKey("id") String id); @GetMapping("/domain/get002/{id}") public Domain getDomain002(@PathVariable("id") String id); //多参数调用 可以是两种 @GetMapping("/domain/get003") public Domain getDomain003(@RequestParam("userName") String userName,@RequestParam("pwd")String pwd,@RequestParam("address")String address); @GetMapping("/domain/get004") public Domain getDomain004(@RequestParam Map<String,Object> map); @PostMapping("/domain/post001") public Domain postDomain001(@RequestBody Domain domain); @PostMapping("/domain/post002") public Domain postDomain002(@RequestBody Domain domain); }
通过代码,我们发现,其跟我们平时写的接口多了一个注解 @FeignClient(name = "app-8801",fallback = ServiceFallback.class) 和每个方法中出现Springmvc的注解。
其中 @FeignClient 中的name 就是需要调用的服务名称,因为与Eureka 和ribbon 整合,所以ribbon 会吧app-8801解析成Eureka 注册表中的服务。如果不想用Eureka 可以参考《基于Ribbon实现负载均衡》中脱离Eureka 使用ribbon。 @FeignClient 还可以指定URL属性 例如@FeignClient(name="app-8801",url="localhost:8801/")
在SpringMVC中类似@RequestParam("userName")、@RequestHeader("userName") 中的username 有时候是可以省略的,但在Feign接口中,不可省略,不然会抛出非法参数异常。 对于post方法中的Domain 类,其类中必须要有默认构造函数,不然Springcloud Feign 根据JSON 字符串转换Domain会抛出异常。
大家发现注解中有一个 fallback = ServiceFallback.class ,这是下一节 Hystrix 容错我会讲到的服务降级,这里是Feign 对Hystrix 服务降级的支持。
3.创建service层(非必须,个人行为)
我在controller与服务调用 中间增加一层Service层,不见直接调用也可以。 代码如下:
/**
* @Author: pangfei
* @description:
* @Date: Create in 15:39 2018/1/16
*/
@Service("feignService")
public class FeignServiceImpl{
final Logger LOGGER= LoggerFactory.getLogger(FeignServiceImpl.class);
final Logger SERVICECAllLOGGER= LoggerFactory.getLogger("logstash");
@Value("${spring.application.name}")
private String appName;
@Autowired
private App8801Feign app8801Feign;//注入Feign接口
public Domain getDomain01( String id)
{
LOGGER.info(appName+"--执行getDomian001操作");
return app8801Feign.getDomain001(id); //服务调用
}
public Domain getDomain02(String id)
{
return app8801Feign.getDomain002(id); //服务调用
}
public Domain getDomain03(String userName, String pwd, String address)
{
return app8801Feign.getDomain003(userName,pwd,address); //服务调用
}
public Domain getDomain04(String userName, String pwd, String address)
{
Map<String,Object> map=new HashMap<>();
map.put("userName",userName);
map.put("pwd",pwd);
map.put("address",address);
return app8801Feign.getDomain004(map); //服务调用
}
public Domain postDomain001(Domain domain){
return app8801Feign.postDomain001(domain); //服务调用
}
public Domain postDomain002(Domain domain){
return app8801Feign.postDomain002(domain); //服务调用
}
}
代码中包含get与post类型调用。
4.创建Controller 进行调用service层
/**
* @Author: pangfei
* @description:
* @Date: Create in 11:27 2018/1/15
*/
@RestController
public class DemoController {
@Autowired
FeignServiceImpl feignService;
/**
*以feign方式的调用
*/
@GetMapping("/get001/{id}")
public Domain getDomain01(@PathVariable("id") String id)
{
return feignService.getDomain01(id); //service层服务调用
}
@GetMapping("/get002/{id}")
public Domain getDomain02(@PathVariable("id") String id)
{
return feignService.getDomain02(id);//service层服务调用
}
@GetMapping("/get003")
public Domain getDomain03(String userName, String pwd, String address)
{
return feignService.getDomain03(userName,pwd,address);//service层服务调用
}
@GetMapping("/get004")
public Domain getDomain04(String userName, String pwd, String address)
{
return feignService.getDomain04(userName,pwd,address);//service层服务调用
}
@PostMapping("/post001")
public Domain postDomain001(@RequestBody Domain domain){
return feignService.postDomain001(domain);//service层服务调用
}
@PostMapping("/post002")
public Domain postDomain002(@RequestBody Domain domain){
return feignService.postDomain002(domain);
}
}
启动app2应用,执行 http://localhost:8901/get001/11 会得到如下
发起调用请求:
两个app-8801 打印出来的
我们发现已经成功请求,并且实现了负载均衡。
大家注意我打印的信息:
app-8801--执行getDomian011操作 traceId=11ddc0da579a038a, spandId=2e798e6e1068b865,
parantSpandId=11ddc0da579a038a, spanName=http:/domain/get001/11
后面我讲到《基于Sleuth的服务跟踪的实现》时,我向大家介绍。
4、请求压缩与日志配置
SpringCloud Feign 支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗。 下面是开启压缩的配置:
feign.compression.request.enabled=true #feign对压缩的支持
feign.compression.response.enabled=true #feign对压缩的支持
feign.compression.request.min-request-size=2048 #用于设置请求的最小阈值
feign.compression.request.mime-types=text/xml,application/xml,application/json #用于设置支持的媒体类型
logging.level.com.example.app2.feign.App8801Feign=DEBUG #将对 App8801Feign 的feign的客户端日志级别设置为只对DEBUG做出相应
至此,对feign的基本使用已经结束, 后面我们还会碰到feign 与其他组件的配置使用,比如Hystrix与Sleuth。