五、利用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

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。