Feign原理及其使用

场景

当使用多服务时,经常会遇到服务之间的相互调用。
一个服务如果要调用另一个服务的接口,需要:
① 定义一个请求,并设置目标地址。
② 为这个请求设置参数。
③ 为这个请求设置请求头等属性。
④ 发送请求并接收结果。
⑤ 将结果转换为本地对象。

以上流程非常繁琐,即使借助RestTemplate这样的辅助类,每次调用接口都要写这样一堆代码,非常不友好。

Feign流程

为了解决上述场景的问题,现在要将这个流程封装成请求工具类,从而方便地进行服务间的接口调用。
于是设计可简化为:
① 为工具类设置目标地址和请求参数。
② 工具类负责构造请求并发送。
③ 工具类将收到的返回自动转换为本地对象。

这就是Feign的思路。
① 在本地定义一个service,在该service上使用注解声明远程目标接口。
② 使用@FeignClient标记该service
③ 在本地的逻辑中引入该service,并直接使用其接口。

原理

Feign实现的原理核心是动态代理。
使用@FeignClient注解会令Feign创建动态代理。当有其他服务需要调用该被注解的类或接口时,Feign就会根据注解的情况动态构造请求并发送,然后接收结果并返回。
即:A工程提供接口,B工程使用Feign来调用A工程的接口。
A工程接口可通过浏览器或postman等常规方式调用。整个过程中A并不关心B是怎么来调用自身接口的。

使用

Feign可用于微服务中多个服务间的交互,或者多个不同项目间的交互。
依然引用上述A工程和B工程的例子。A工程使用8080端口,B工程使用8081端口。
A工程正常实现即可,不需要引入Feign。B工程作为请求的发出者,需引入Feign及其相关依赖。
设A工程提供了一个接口:

127.0.0.1:8080/test/t1

无参数。
其返回值为:this is project A.

依赖

在B工程的pom.xml中引入依赖。
通常,Feign使用cloud版本,因此需要依赖spring-cloud


<dependency>
  <groupId>org.springframework.cloudgroupId>
  <artifactId>spring-cloud-dependenciesartifactId>
  <version>Hoxton.SR12version>
  <type>pomtype>
  <scope>importscope>
dependency>


<dependency>
  <groupId>org.springframework.cloudgroupId>
  <artifactId>spring-cloud-starter-openfeignartifactId>
  <version>2.1.1.RELEASEversion>
dependency>

注意spring-cloud的版本需要与SpringBoot的版本相匹配,否则可能会报*Error creating bean with name ‘configurationPropertiesBeans’*的错误。

开启Feign功能

在B工程的Application文件前增加注解@EnableFeignClients

@SpringBootApplication
@EnableFeignClients
public class TestBApplication {
	public static void main(String[] args) {
		SpringApplication.run(TestBApplication.class, args);
	}
}

定义请求service

在B工程中定义一个service,在前面添加@FeignClient注解。

@FeignClient(name = "feign-test",
              url = "http://localhost:8080")
public interface FeignService {
    @GetMapping(value = "/test/t1")
    String feignTest();
}

其中url属性指明了请求地址前缀。@GetMapping中指明了具体接口。
其他关于Fegin的参数见下文。

引用请求service

在B工程中的测试controller中引入service并使用:

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private FeignService feignService;

    @GetMapping("/doFeignTest")
    public String doFeignTest() {
        return feignService.feignTest();
    }
}

调用

在浏览器中调用:

127.0.0.1:8081/test/doFeignTest

浏览器会显示返回内容:this is project A.

传参

A工程的接口需要传入参数:

@GetMapping(value = "/test/t2")
String feignTest2(@RequestParam String s);

则B工程在service中也需要传入参数:

public interface FeignService {
    @GetMapping(value = "/test/t2")
    String feignTest2(@RequestParam String s);
}

由于是get请求,且参数唯一,这里使用了@RequestParam的方式传参。
这样即可将参数传给A工程。

返回对象

A工程接口/test/t3的返回是一个对象:

@Data
public class AStudent {
    String name;
    int age;
}

但对于B工程来说,接收到的返回就是一组数据流。B工程可定义自己的对象来进行接收:

@Data
public class BPerson {
    String name;
    int age;
}

使用该对象接收返回即可:

@FeignClient(...)
public interface FeignService {
    @GetMapping(value = "/test/t3")
    BPerson feignTest3();
}

熔断

所谓熔断,就是防止调用异常时程序无法继续执行。
在Fegin中,可以设置一个本地类作为熔断触发的回调。一旦调用出现异常,Fegin就会改为调用本地类的对应方法,从而获取一个替代的返回结果。这样可以让程序继续顺利执行。

开启熔断

首先要在配置文件application.properties中开启熔断:

feign.hystrix.enabled=true

定义熔断回调类

前面定义的Feign service类是一个interface

@FeignClient(...)
public interface FeignService {
  @GetMapping(value = "/test/t1")
  String feignTest();
}

其下有一个接口feignTest()
熔断回调类需对该service进行派生,并重载feignTest()

@Component
public class FeignServiceCallback implements FeignService {

    @Override
    public String feignTest() {
        return "调用失败,返回熔断结果";
    }
}

设置熔断回调

在Feign service类中,为@FeignClient添加fallback参数:

@FeignClient(name = "feign-test",
        url = "http://localhost:8080",
        fallback = FeignServiceCallback.class)
public interface FeignService {
    ...
}

使用

这样,当Feign调用A工程的接口时,若出现问题,Fegin会调用FeignServiceCallback.feignTest(),返回:“调用失败,返回熔断结果”。

多服务下的使用方式

多服务下,会出现这样一种现象:A提供接口,B和C都需要调用该接口。那么按上述方式,B和C就都需要添加一个interface FeignService。若B和C调用的接口相同,那么B和C添加的interface FeignService也是相同的。那么,若还有D、E、F……等服务都需要调用A提供的接口,那每个服务都要添加一个interface FeignService,且都是相同的实现。
于是,考虑对interface FeignService进行复用。如果可以定义一个公共的interface FeignService,那么每个需要调用A接口的服务直接将interface FeignService引入即可使用。
基于该思路,将interface FeignService放在A中。A定义一个针对自身接口的interface FeignService,其他服务只要引入这个interface FeignService即可调用。
若A、B、C等都处于同一个工程下,那么B、C等可直接进行引入;但若处于不同的工程下,要引入,就得使用其他方式,例如打包为jar后复制过来。
实例:
① A所在的工程是一个多服务工程,A是其中一个子服务。现在添加一个新的子服务,称之为A_Api。在其中添加interface FeignService
② 对A_Api子服务进行打包,得到一个jar,称为A_Api.jar
③ B是另一个工程中的子服务。将A_Api.jar引入到B中,这样B就有了interface FeignService。于是B可以调用A的接口了。

在上述流程③中,B调用的interface FeignService是打包好的,那么意味着请求的url地址也打包在其中了。而实际部署环境可能会变。于是不在interface FeignService中配置url,仅配置服务名。然后借助nacos这样的中间件通过服务名来获取A服务的url。
关于B中引入A_Api.jar,直接将interface FeignService打包为jar然后复制到B中是一种方式。若有条件,更推荐搭建一个Maven,A将interface FeignService打包并上传到Maven,然后B在pom.mxl中引入。

interface FeignService 实现方式

@FeignClient(name = "service-a",
        fallback = FeignServiceCallback.class)
public interface FeignService {
    @GetMapping(value = "/test")
    String feignTest();
}

里面定义了一个方法,会访问service-a/test接口。
那这意味着A服务service-a必须提供一个对应的/test接口。
通常,接口都是由controller来提供。但这里也可以采用另一种方式:
service-a中定义一个class FeignServiceImpl implements FeignService

@Controller
public class FeignServiceImpl implements FeignService {
	
	@Resource
	private IAService aService;

    @Override
    @GetMapping(value = "/test")
    public String feignTest() {
        return aService.doTest();
    }
}

其中:

  • FeignServiceImplFeignService派生,因此必须实现其所有接口。这一点是java语法决定的。当然,这也刚好满足了与FeignService的接口一一对应的要求。
  • FeignServiceImpl位于A服务文件夹下,因此可引入并使用A服务的任意类。就像上面,引入了private IAService aService;
  • FeignServiceImpl为了能接收FeignService中Feign的调用,需要使用@GetMapping来为每个方法添加接口路径。在类前也添加了@Controller来充当controller的作用。于是对于FeignService来说,FeignServiceImpl就是个controller而已。

该方式相当于是提供了一种强制的规范。但从本质来说,FeignServiceImplFeignService派生并非是必要的。定义一个controller也能实现相同的作用。

@FeignClient常用属性

@FeignClient注解可包含多个属性,其常用的有:

  • value/name: 这两个属性作用相同,表示别名。多服务下注册到服务注册中心时使用的服务名,用于服务发现。
  • url: 指定@FeignClient调用的地址。
  • configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract。
  • fallback: 指定熔断的回调类。当调用远程接口失败或超时,会调用对应接口。fallback指定的类必须实现@FeignClient标记的接口。
  • fallbackFactory: 工厂类,用于生成fallback类实例。通过该属性可实现每个接口通用的调用失败处理逻辑,减少重复的代码。
  • path: 定义当前FeignClient的统一前缀。

你可能感兴趣的:(Java,Spring-Boot,java,spring,boot,spring,cloud,Feign)