1、Feign简介
Feign是声明式Web Service客户端,它让微服务之间的调用变得更简单,类似controller调用service。SpringCloud集成了Ribbon和Eureka,可以使用Feigin提供负载均衡的http客户端
只需要创建一个接口,然后添加注解即可~
Feign,主要是社区版,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问两种方法
- 微服务名字 【ribbon】
- 接口和注解 【feign】
Feign能干什么?
- Feign旨在使编写Java Http客户端变得更容易
- 前面在使用Ribbon + RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一个客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步的封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它 (类似以前Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon 时,自动封装服务调用客户端的开发量。
Feign默认集成了Ribbon
- 利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
2、Feign的使用步骤
2.1 依据springcloud-consumer-ribbon-80模块复制,创建springcloud-consumer-feign-80模块,修改依赖
<dependencies>
<dependency>
<groupId>com.godfrey</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--Eureka: Ribbon需要从Eureka服务中心获取要拿什么-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.godfrey</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
Feign实现消费者模块调用服务提供者模块的原理和原来的Dubbo+Zookeeper类似,即需要使用注解实现远程注入,所以我们直接在springcould-api模块先添加Feign依赖:
<!--Feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2 在service包下新建一个接口DeptClientService,
这个接口中的方法定义自己随意,只是方法上面要像controller一样写上@RequestMapping或者它的变体@GetMapping、@PostMapping等,但是这个接口上面不需要使用注解@Controller或@RestController
这个接口上面需要使用注解@FeignClient(value = “服务集群在注册中心中的名称”)和注解@Component或者它的变体@Service;其中注解@FeignClient+value属性用于指定注册中心中哪一个服务中的
@Component
@FeignClient(value = "SPRINGCOULD-PROVIDER-DEPT")
public interface DeptClientService {
@PostMapping("/dept/add")
boolean addDept(Dept dept);
@GetMapping("/dept/queryById/{id}")
Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/queryList")
List<Dept> queryAll();
}
注意:还需要在这个接口上加上注解@Component/@Service,否则这个接口不会被spring托管/装配到spring容器中,那么我们在消费者model中使用这个类的时候就不能使用注解@Autowired实现对象自动注入到消费者的controller中(注意:我们是在springcould-api模块中定义的这个接口,并在这个接口上面加上了注解@Component,这个注解不是要在springcould-api模块中起作用,而是要在使用springcould-api模块的其他模块中起作用,即哪个模块导入了springcould-api模块,那么在这个model启动的时候,有注解@Component的这个接口就会被装配到spring容器中去,然后我们只需要在当前的这个模块中使用注解@Autowired就可以获取到这个接口在spring容器中的实例,就可以调用它内部的方法实现对应的功能)
通过Ribbon实现:—原来的controller:DeptConsumerController.java
@RestController
public class DeptConsumerController {
private RestTemplate restTemplate;
//Ribbon:我们这里的地址,应该是一个变量,通过服务名来访问
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
@Autowired
DeptConsumerController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@PostMapping("/consumer/dept/add")
public boolean addDept(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add/", dept, Boolean.class);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id) {
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> queryAll() {
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}
}
使用ribbon的时候,我们是直接将服务集群在注册中心中的服务名直接写死在代码中(private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";),通过拼接实现的消费者调用服务提供者对应的服务,拼接的后半段是服务对应功能在controller中暴露的API(/dept/add/,/dept/get/,/dept/get/)
通过Feign实现:—改造后controller:DeptConsumerController.java
@RestController
public class DeptConsumerController {
private DeptClientService service;
@Autowired
public DeptConsumerController(@Qualifier("SPRINGCOULD-PROVIDER-DEPT") DeptClientService service) {
this.service = service;
}
@PostMapping("/consumer/dept/add")
public boolean addDept(Dept dept) {
return service.addDept(dept);
}
@GetMapping("/consume/dept/queryById/{id}")
public Dept queryById(@PathVariable("id") Long id) {
return service.queryById(id);
}
@RequestMapping("/consumer/dept/queryAll")
public List<Dept> queryAll() {
return service.queryAll();
}
}
使用Feign的时候,我们只需要在消费者的controller中组合进刚刚在springcloud-api中编写的接口方法即可,并使用注解@Autowired实现依赖注入,下面的方法就可以调用接口中的方法了,使用Feign之后,消费者的controller中方法的实现更加符合controller调service层提供服务的情形,并目整个代码结构更加的清晰了,就像真的就是在调用消费者模块中的service提供的服务—样,但是我们要是到,消费者模块根本就没有service层的代码
最后还要在spring boot项目的入口程序处加上启动Feign的注解@EnableFeignClients(basePackages = “我们定义的service所在的包路径”),注意:这里不是实际存在于springcould-consumer-dept-feign的model下的包路径,而是我们导入的springcould-api的包路径
2.3 启动Feign模块测试效果
从上面的测试结果可以发现,Feign默认使用的也是轮询算法
3、小结
-
注意:通过上面使用Feign实现负载均衡我们可以发现,Feign做的事情就是在本地代码中,通过在springcould-api定义一个专门用于通过Feign实现负载均衡的接口,并在接口上写上注解@FeignClient以及这个注解的name/value属性,用于绑定注册中心中指定名称的服务,就像上面的例子中
-
绑定之后,接口中的方法和注册中心已经注册的服务中的功能提供的API通过注解@RequestMapping或其变体进行绑定
-
然后再将springcould-api模块导入其他的模块中进行复用,在需要使用服务者功能的消费者的controller中,通过注解@Autowired实现上面定义的接口的依赖注入,并在消费者的controller中直接像controller层调用service层一样,调用这个接口中的方法,实现消费者对于服务者提供的服务的消费
-
最后在使用这个接口的model的入口程序/主启动类上添加注解@EnableFeignClients(basePackages = “springcould-api中接口所在的包路径”)
所以Feign的核心就是将服务者提供的服务API进行本地化,存入消费者model中,然后再通过注解@EnableFeignClients和注解@FeignClient实现通过调用消费者模块中的本地化的服务API,调用到注册中心中真正服务提供者的API的作用(所以接口上的API映射需要和服务提供者的API保持一致),只需要4步就实现了Feign实现负载均衡
4、Feign和Ribbon如何选择?
根据个人习惯而定,如果喜欢REST风格使用Ribbon;如果喜欢社区版的面向接口风格使用Feign.
Feign 本质上也是实现了 Ribbon,只不过后者是在调用方式上,为了满足一些开发者习惯的接口调用习惯!
下面我们关闭springcloud-consumer-dept-80 这个服务消费方,换用springcloud-consumer-dept-feign(端口还是80) 来代替:(依然可以正常访问,就是调用方式相比于Ribbon变化了)