Feign 是Spring Cloud Netflix组件中的一个轻量级Restful的 HTTP 服务客户端,完结了负载均衡和 Rest 调用的开源结构,封装了Ribbon和RestTemplate, 完结了WebService的面向接口编程,进一步降低了项目的耦合度。
先来看咱们以前利用RestTemplate发起长途调用的代码:
String url = "http://user-service:8081/user/"+order.getUserId();
User user = restTemplate.getForObject(url, User.class);
以上的代码存在参数杂乱、URL难以维护等问题,如当我有一台服务地址换了,那么这时候就需求云同步修正url,那要是多台要修正的情况下那就得改很多台,当咱们服务多的时候这是个很麻烦的工作。
1.Feign替代RestTemplate
Fegin的运用过程如下:
1)引进依靠
咱们在order-service服务的pom文件中引进feign的依靠:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2)增加注解
在order-service的发动类增加注解敞开Feign的功用:
3)编写Feign的客户端
在order-service中新建一个接口,内容如下:
package cn.itcast.order.service.feign;
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("user-service")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryById(@PathVariable("id") Long id);
}
这个客户端首要是根据SpringMVC的注解来声明长途调用的信息,比方:
- 服务称号:user-service
- 恳求办法:GET
- 恳求路径:/user/{id}
- 恳求参数:Long id
- 回来值类型:User
这样,Feign就能够协助咱们发送http恳求,无需自己运用RestTemplate来发送了。底层会通过服务称号:user-service去映射到具体的user服务对应的url地址。
4)测验
修正order-service中的OrderService类中的queryOrderById办法,运用Feign客户端替代RestTemplate:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
@Autowired
UserFeignClient feignClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
User user = feignClient.queryById(orderId);
order.setUser(user);
// 4.回来
return order;
}
}
测验调用:
2.自界说装备
Feign能够支撑很多的自界说装备,如下表所示:
类型 | 效果 | 说明 |
---|---|---|
feign.Logger.Level | 修正日志等级 | 包含四种不同的等级:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 呼应成果的解析器 | http长途调用的成果做解析,例如解析json字符串为java目标 |
feign.codec.Encoder | 恳求参数编码 | 将恳求参数编码,便于通过http恳求发送 |
feign. Contract | 支撑的注解格式 | 默许是SpringMVC的注解 |
feign. Retryer | 失利重试机制 | 恳求失利的重试机制,默许是没有,不过会运用Ribbon的重试 |
一般情况下,默许值就能满足咱们运用,假如要自界说时,只需求创立自界说的@Bean覆盖默许Bean即可。
下面以日志为例来演示如何自界说装备。
2.1.装备文件办法
根据装备文件修正feign的日志等级能够针对单个服务:(注意:有时yml装备文件中有中文注释会报错)
feign:
client:
config:
user-service: # 针对某个微服务的装备
loggerLevel: FULL # 日志等级
也能够针对一切服务:
feign:
client:
config:
default: # 这儿用default便是大局装备,假如是写服务称号,则是针对某个微服务的装备
loggerLevel: FULL # 日志等级
而日志的等级分为四种:
- NONE:不记载任何日志信息,这是默许值。
- BASIC:仅记载恳求的办法,URL以及呼应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记载了恳求和呼应的头信息
- FULL:记载一切恳求和呼应的明细,包含头信息、恳求体、元数据。
2.2.Java代码办法
也能够根据Java代码来修正日志等级,先声明一个类,然后声明一个Logger.Level的目标:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志等级为BASIC
}
}
假如要大局生效,将其放到发动类的@EnableFeignClients这个注解中:
package cn.itcast.order;
import cn.itcast.order.config.DefaultFeignConfiguration;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
// 装备类的办法敞开大局日志记载
//@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class) // 敞开feign客户端的支撑
@EnableFeignClients // 敞开feign客户端的支撑
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
//......
}
假如是局部生效,则把它放到对应的@FeignClient这个注解中:
package cn.itcast.order.feign;
import cn.itcast.order.config.DefaultFeignConfiguration;
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
// 指定服务日志装备
//@FeignClient(value = "userservice",configuration = DefaultFeignConfiguration.class)
@FeignClient(value = "userservice")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryById(@PathVariable("id") Long id);
}
2.3.Feign运用优化
Feign底层发起http恳求,依靠于其它的结构。其底层客户端完结包含:
•URLConnection:默许完结,不支撑衔接池
•Apache HttpClient :支撑衔接池
•OKHttp:支撑衔接池
因此提高Feign的功能首要手段便是运用衔接池替代默许的URLConnection。
这儿咱们用Apache的HttpClient来演示。
1)引进依靠
在order-service的pom文件中引进Apache的HttpClient依靠:
<!--httpClient的依靠 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2)装备衔接池
在order-service的application.yml中增加装备:
feign:
client:
config:
default: # default大局的装备
loggerLevel: BASIC # 日志等级,BASIC便是根本的恳求和呼应信息
httpclient:
enabled: true # 敞开feign对HttpClient的支撑
max-connections: 200 # 最大的衔接数
max-connections-per-route: 50 # 每个路径的最大衔接数
max-connections解释:比方有以下情况,有一个服务A一起有可能会拜访B服务和C服务,这时候装备的最大衔接数指的便是A在拜访B和C时,总的衔接数不超过200。
max-connections-per-route解释:指的是A服务拜访B服务时的路径最大衔接数据为50,也便是200个衔接,A服务到B服务的拜访最多只会有50个衔接,当超出50个衔接时,其他衔接就会路由到B服务之外的服务中。
接下来,在FeignClientFactoryBean中的loadBalance办法中打断点:
Debug办法发动order-service服务,能够看到这儿的client,底层便是Apache HttpClient:
改成http衔接池后,从演示项目后台的恳求日志中能够发现会从原来的几十ms变成个位数ms,有爱好的小伙伴能够自己测验一下。
2.4.最佳实践-抽取feign-api接口
现在存在一个问题,咱们现在演示的是只有一个order-service调用user-service,那假如当有多个服务要去调userservice的时候呢,那是否需求在每个service里都去写一份长途调用user-service的代码?完结没必要是不是?所以,把这部分代码直接抽成一个module打成jar包,在需求调用的当地引进即可。
将Feign的Client抽取为独立模块,并且把接口有关的POJO、默许的Feign装备都放到这个模块中,提供给一切顾客运用。
行将UserClient、User、Feign的默许装备都抽取到一个feign-api包中,一切微服务引用该依靠包,即可直接运用。
1)抽取
首先创立一个module,命名为feign-api:
项目结构:
在feign-api中然后引进feign的starter依靠
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
然后,order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
2)在order-service中运用feign-api
首先,删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口。
在order-service的pom文件中中引进feign-api的依靠:
<dependency>
<groupId>cn.itcast.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
修正order-service中的一切与上述三个组件有关的导包部分,改成导入feign-api中的包
扫描包问题
最终,包的扫描要指定一下,否则发动会报错找不到userfeignclient
办法一:
指定Feign应该扫描的包:
@EnableFeignClients(basePackages = “cn.itcast.feign.clients”)
办法二:
指定需求加载的Client接口:
@EnableFeignClients(clients = {UserClient.class})
Feign完结原理
Feign的底层源码完结首要包含以下几个部分:
- 接口界说
Feign的接口界说类似于Java的接口界说,可是它运用了注解来描述HTTP恳求的参数和回来值。例如,@RequestMapping注解用于指定HTTP恳求的URL和恳求办法,@RequestParam注解用于指定HTTP恳求的参数,@RequestBody注解用于指定HTTP恳求的恳求体,@PathVariable注解用于指定HTTP恳求的路径参数等。
- 动态署理
Feign运用了Java的动态署理技术来生成HTTP恳求的完结类。当应用程序调用Feign接口的办法时,Feign会动态生成一个HTTP恳求的完结类,并将恳求参数传递给该完结类。该完结类会将恳求参数转换为HTTP恳求,并发送给长途服务。当长途服务回来呼应时,该完结类会将呼应转换为Java目标,并回来给应用程序。
- 编码器和解码器
Feign运用了编码器和解码器来将Java目标转换为HTTP恳求和呼应。编码器将Java目标转换为HTTP恳求的恳求体,解码器将HTTP呼应的呼应体转换为Java目标。Feign支撑多种编码器和解码器,例如JSON编码器和解码器、XML编码器和解码器等。
- 负载均衡
Feign能够与负载均衡器无缝集成,以完结服务的负载均衡。当应用程序调用Feign接口的办法时,Feign会将恳求发送给负载均衡器,负载均衡器会选择一个可用的服务实例,并将恳求转发给该实例。假如该实例不可用,则负载均衡器会选择另一个可用的服务实例,并将恳求转发给该实例。
- 断路器
Feign能够与断路器无缝集成,以完结服务的容错。当应用程序调用Feign接口的办法时,Feign会将恳求发送给断路器,断路器会检查服务实例的可用性。假如服务实例不可用,则断路器会回来一个默许的呼应,以避免应用程序出现异常。假如服务实例可用,则断路器会将恳求转发给该实例,并回来实例的呼应。