Spring声明式调用Feign原理以及扩展应用
微服务的远程调用
在如今微服务大行其道的大环境下,服务间的远程调用就成为微服务不得不解决的问题。在保证性能的情况下,同时需要敏捷开发,易扩展的能力成为重要指标。
在整个SpringCloud生态环境下,Feign的声明式远程调用成为当下微服务的主要技术栈,那么接下来就让我们详细了解Feign的使用与原理
Feign的原理
服务间的调用可以将服务分为服务生产者与消费者,但是两者之间并没有严格区分,因为在服务设计拆分时,往往会存在服务间相互调用的情况(根据微服务原则,拆分需尽量严格分为上下游应用,表面此种情况)。所以在区分生产者与消费者是,只是针对一次远程调用。
比如做应用公共平台业务时,我们有一个订单服务order-service,同时有一个第三方服务third-service,当我们在下单时,记录订单数据的同时需要根据所选合作方同步第三方相关下单数据,并根据第三方服务接口返回的信息来更新相关订单状态。所以在下单的这个操作下order-service就是消费者,而第三方服务就是生产者third-service。
- 首先快速搭建一个简单生产者,即third-service,利用springBoot,三分钟搞定。
直接访问得到结果报文:
{“code”:0,“message”:“处理成功”,“data”:{“orderNo”:2131232112,“orderStatus”:“S”,“orderDate”:“20220202”}} - 现在开始搭建一个消费端服务,基本与服务端搭建差不多,只是消费端这边就需要使用到Feign
@FeignClient(name="third-service",url="127.0.0.1:8080")
public interface ThirdServiceFeign {
@PostMapping("/order")
ResponseResult<OrderRspDto> order(@RequestBody OrderReqDto reqDTO);
}
然后在直接调用该接口方法,即可完成远程调用。
@RestController
public class NewController {
@Autowired
private ThirdServiceFeign thirdServiceFeign;
@GetMapping("/ordered")
public String order(){
OrderReqDto reqDto = new OrderReqDto();
reqDto.setOrdeNo("123");
reqDto.setOrderAmout("222");
ResponseResult<OrderRspDto> order = thirdServiceFeign.order(reqDto);
System.out.println(order);
return "success";
}
}
这就是feign接口的基本使用,本质也是Http请求调用,只是通过Feign来针对我们定义的接口进行了动态代理生产了代理类,实际我们调用到的方法是封装好了的http调用,okhttp等。
Feign原理
其实让我们自己实现一个类似Feign的简单远程方法调用能力其实也不难。
首先我们是直接接口调用,接口不能实例化,那么需要调用其方法的话就需要我们自己定义方法拦截器,使得其实际调用时,先是调用的生成的代理类,在代理类中,我们就可以实现http接口的调用能力,然后对请求参数与返回参数做相应的处理序列化和反序列化。
了解了上面这一点,那么再来理解Feign其实就很简单了。
Feign的扩展
想这种调用第三方服务的情况,有时候在测试环境下往往需要我们自己模拟对方接口的返回参数这个时候,我们就需要自己来重写远程服务返回的接口报文来满足我们正常的测试情况。
- 替换Decoder
Decoder是用来装饰或者解析接口影响数据的,在这个接口里我们可以自己顶一个Decoder然后再对应的FeignClient上注入,这样就能将Feign接口的返回参数进行替换了。
public class MyConfiguration {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Bean
public Decoder feignDecoder() {
return new MyConfiguration.MyDecoder(new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))));
}
class MyDecoder implements Decoder{
final Decoder delegate;
public MyDecoder(Decoder delegate) {
Objects.requireNonNull(delegate, "Decoder must not be null. ");
this.delegate = delegate;
}
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
System.out.println(response);
String str="{\"msgCd\":\"CHN00000\",\"msgInfo\":\"交易成功\",\"output\":{\"noticeResultCode\":\"00000\",\"noticeDescription\":\"交易成功\"}}";
return JSON.parseObject(str);
}
}
- 替换client
既然是进行远程http调用,那么使用的client可能就需要根据情况来定了,我们可以自定义一个Client实现Feign的Client接口,然后将该client注入Spring容器中,或者配置在@FeignClient的Configuration指定的配置上也可以完成替换,注意一点,一个是全局,一个是单个FeignClient。 - SpringAop的能力
Spring的Aop本身就是面向切面编程了,针对这个需求,我们只需要在对应的FeignClient进行切面织入,利用Aop的Around通知,在Aop的方法中判断接口以及参数,来决定是否需要调用远程方法或者直接从本地数据库获取数据返回。
@Aspect
@Component
@Order(0)
public class MyAspect {
@Pointcut("within(com.rech.service.ServiceFeign+)")
public void myPonit(){};
@Around("myPonit()")
public Object mock(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName="";
Class<?> returnType =null;
Signature signature = joinPoint.getSignature();
if(signature instanceof MethodSignature){
MethodSignature ms= (MethodSignature)signature;
returnType = ms.getMethod().getReturnType();
methodName= ms.getMethod().getName();
}
//自定义逻辑什么情况下模拟数据,
//不满足就走原来的方法调用
if("order".equals(methodName)){
String str="{\"msgCd\":\"CHN00000\",\"msgInfo\":\"交易成功\",\"output\":{\"noticeResultCode\":\"00000\",\"noticeDescription\":\"交易成功\"}}";
return JSON.parseObject(str, returnType);
}
return joinPoint.proceed();
}
}
推荐使用第三种方法,简单快速,本就是执行在测试环境的需求,对切面对性能的影响也可以忽略不计。
总结
在没有与ribbon客户端负载均衡结合的情况下,简单描述了下Feign接口响应的基本扩展,当然对对应请求肯定也是有扩展的,如实现RequestInterceptor接口也可以对请求参数做处理。简单来说只要我们熟悉spring框架的原理,以及SpringMVC,对Feign进行扩展实际是很简单的。