上篇文章和大家分享了声明式微服务调用组件 Feign 的基本用法,相信大家已经了解到使用 Feign 的好处了,使用 Feign 有效地解决了使用 RestTemplate 时的代码模板化的问题,使服务之间的调用更加简单方便,同时也不易出错。不过,细心的读者可能也发现,上篇文章中我们学的 Feign 还是有一些明显的缺陷,例如,当我们在 provider 中定义接口时,可能是下面这样:
@RestController
public class GirlController {
@GetMapping("/girl")
public String girl(String name) {
return "love " + name + " !";
}
}
然后在feign-consumer中定义:
@FeignClient("provider")
public interface GirlService {
@GetMapping("/girl")
String gril(@RequestParam("name") String name);
}
可以看到provider 和 feign-consumer代码明显重复了,而且如果调用的参数和提供的参数不一致那么就会报错,如果不细心的话,难免会发生这样的事情。那么如何避免这样的事情发生呢?那么我们可以使用feign的继承。
准备工作
首先我们创建一个FeignAdvanced的普通maven项目,作为父项目,然后我们在父项目中创建一个eureka的SpringBoot项目作为子模块,关于创建eureka项目,我这里就不再多叙述了,因为非常的简单,大家可以参考我之前写的文章。
创建好eureka项目后,我们再创建一个commons的普通maven工程,作为子模块。
它的依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
</dependencies>
就一个简单的web依赖,然后我们在commons项目中创建一个GirlService的接口:
public interface GirlService {
@GetMapping("/girl")
String girl(@RequestParam String name);
}
在这个 GirlService 接口中,我们会将前面提到的 provider 和 feign-consumer 中公共的部分抽取出来定义在这里,然后在 provider 中调用这个接口,在 feign-consumer 中实现这个接口。
这个接口定义好了后,我们就要去实现我们的继承。
继承性
Feign 中继承,一共两个步骤:
- 在provider中实现公共接口
- 在feign-consumer中去调用provider中的接口
在provider中实现接口
然后我们创建一个provider的SpringBoot项目作为子项目,provider具体创建,我这里也不详细的说了,很简单,可以参考我前面的文章。创建好provider项目后我们将我们的commons加入依赖:
<dependencies>
<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.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.com.scitc</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
上面就是provider的依赖,然后我们需要将provider注册到eureka中,这一步很简单,可以参考我前面的文章。
然后我们在provider中定义一个GirlController 来 实现commons中定义的GirlService接口:
@RestController
public class GirlController implements GirlService {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public String girl(String name) {
log.info("provider提供girl的服务");
return "love" + name + "!";
}
}
这样就完成了我们的provider接口的开发。
在feign-consumer中调用接口
我们再创建一个feign-consumer的SpringBoot项目,作为子模块,它的依赖如下:
<dependencies>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.com.scitc</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
然后修改配置文件,将feign-consumer注册到eureka上,这里也不多做叙述。然后我们需要在feign-consumer的启动类加入@EnableFeignClients
注解,来开启Feign的支持:
@SpringBootApplication
@EnableFeignClients
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
然后我们在feign-consumer中添加一个FeignGirlService 接口 并继承 commons 依赖中的GirlService接口,如下:
@FeignClient("provider")
public interface FeignGirlService extends GirlService {
}
需要注意的是,这里的 FeignGirlService 接口直接继承自 GirlSerivce ,继承之后, FeignGirlService 自动具备了 GirlSerivce 中的接口,因此可以在使用 @FeignClient(“provider”) 注解绑定服务之后就可以直接使用了。
然后我再从feign-consumer中定一个LoveGirlController,在 LoveGirlController 中使用 FeignGirlService:
@RestController
public class LoveGirlController {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
FeignGirlService girlService;
@GetMapping("/girl")
public String girl(String name) {
log.info("consumer调用了provider提供的girl服务");
return girlService.girl(name);
}
}
然后我们启动eureka、provider、feign-consumer ,去访问我们的服务,调用结果如下:
优缺点分析
这种写法和我们以前的写法有什么区别,有什么缺点和优点?
- 使用继承特性,代码简洁明了,不易出错,不必担心接口返回值是否写对,接口地址是否写对。如果接口地址有变化,也不用 provider 和 feign-consumer 大动干戈,只需要修改 commons 模块即可,provider 和 feign-consumer 就自然变了;
- 前面提到的在 feign-consumer 中绑定接口时,如果是 key/value 形式的参数或者放在 header 中的参数,就必须要使用 @RequestParam 注解或者 @RequestHeader 注解,这个规则在这里一样适用。即在 commons 中定义接口时,如果涉及到相关参数,该加的@RequestParam 注解或者 @RequestHeader 注解一个都不能少;
- 当然,使用了继承特性也不是没有缺点。继承的方式将 provider 和 feign-consumer 绑定在一起,代码耦合度变高,一变俱变,此时就需要严格的设计规范,否则会牵一发而动全身,增加项目维护的难度。
日志配置
我们使用了Feign,如果想要看微服务之前的调用情况,那么就可以使用Feign的日志功能
,Feign的日志功能有四种:
NONE ,不开启日志记录,默认即此
BASIC ,记录请求方法和请求 URL ,以及响应的状态码以及执行时间
HEADERS ,在第2条的基础上,再增加请求头和响应头
FULL ,在第3条的基础上再增加 body 以及元数据
我们一般使用最强的就是 FULL了。
那么如何使用Feign的日志功能呢?非常的简单,只需要在启动类加一个bean就可以了如下:
@SpringBootApplication
@EnableFeignClients
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
@Bean
Logger.Level loggerLevel() {
return Logger.Level.FULL;
}
}
这里我们选择FULL 最强的,然后在application.yml或者application.properties中配置,这里我使用的是yml
logging:
level:
cn:
com:
scitc:
FeignGirlService: debug
这里 logging.level 是指日志级别的前缀,cn.com.scitc.FeignGirlService.FeignGirlService表示该 class 以 debug 级别输出日志。当然,类路径也可以是一个 package ,这样就表示该 package 下的所有 class 以 debug 级别输出日志。配置完成后,重启 feign-consumer 项目,访问其中任意一个接口,就可以看到请求日志,如下:
数据的压缩
数据的压缩,主要是解决传输效率,具体配置如下:
feign:
compression:
request:
enabled: true
mime-types: text/html,application/json
min-request-size: 2048
response:
enabled: true
前两行表示开启请求和响应压缩,第三行表示压缩的数据类型,默认是 text/html,application/json,application/xml
, 第四行表示压缩数据的下限,即当要传输的数据大于2048时才需要对请求进行压缩。
请求重试
当Feign出现问题的时候,我们可以尝试重新连接,前面我们使用的是Spring-retry 的依赖,但是在Feign中自带了请求重试功能,直接配置就可以使用:
ribbon:
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 1
OkToRetryOnAllOperations: false
其中MaxAutoRetries 代表最大的请求次数
其中MaxAutoRetriesNextServer代表最大重试的service个数
其中OkToRetryOnAllOperations代表是否开启任何异常都重试
那么我们也可以针对一个微服务进行请求重试的配置:
provider:
ribbon:
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 1
OkToRetryOnAllOperations: false
这个配置就是针对服务名称是provider的服务,注意这里的provider服务名字是spring.application.name中的名称。
我们也可以不通过配置文件来配置,直接用一个bean:
@Bean
public Retryer feignRetryer() {
Retryer.Default retryer = new Retryer.Default();
return retryer;
}
总结
本文主要向大家介绍了声明式微服务调用工具 Feign 的一些高级特性,例如继承机制、日志配置、请求压缩、请求重试等,并对继承特性的优缺点进行了分析。在实际开发中,灵活地使用这些属性,可以使我们的微服务以一个更高的效率运行。通过对这些特性的学习,相信大家对 Feign 将会有一个更深刻的认识。