Feign替代RestTemplate
利用RestTemplate
发起远程调用的代码的缺点
String url = "http://user-service/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
Feign是一个声明式的http客户端
,我们只需要把发请求需要的信息声明出来如请求方式,请求路径,请求参数等
信息,剩下的由Fegin帮我们完成http请求的发送
第一步引入依赖
:在order-service
模块的pom文件中引入Feign的依赖spring-cloud-starter-openfeign
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
第二步开启Feign的功能
:在order-service
模块的启动类上
添加@EnableFeignClients
注解
注解属性 | 功能 |
---|---|
@EnableFeignClients(basePackages = “cn.itcast.feign.clients”) | 指定FeignClient所在的包,默认扫描启动类所在的包及其子包 |
@EnableFeignClients(clients = {UserClient.class}) | 指定需要加载的FeignClient字节码,用哪个加载哪个 |
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
第三步编写Feign客户端代码
:在order-service
模块的com.itcast.order.client
包下新建UserClient
接口,基于SpringMVC的注解来声明远程调用的信息
@SpringBootApplication注解
扫描到@FeignClient
注解时就会为UserClient
接口创建代理对象FeignClient
不在扫描包的范围(启动类所在的包及其子包),那么就无法为其创建对象即无法实现自动装配请求信息 | 举例 |
---|---|
请求的服务名称 | user-service |
请求的方式 | GET或POST |
请求的路径 | 如/user/{id} |
请求的参数 | 如用户的d |
请求响应的返回值类型 | 如User对象 |
// 封装了对user-service服务的远程调用的信息
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
第四步编写业务逻辑代码: 在order-service
模块的OrderService类中的queryOrderById方法
使用Feign客户端代替RestTemplate
第五步: 使用浏览器发起请求http://localhost:8080/order/101实现Fegin的远程调用
user-service
服务对应的多个实例的控制台信息,可以发现Feign不仅实现了远程调用还实现了负载均衡功能(因为内部集成了Ribbo组件
)@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 注入Feign的客户端
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1. 查询订单
Order order = orderMapper.findById(orderId);
// 2. 利用Feign的客户端发起http请求访问user-service查询用户信息
User user = userClient.findById(order.getUserId());
// 3. 封账user对象到order对象的user属性中
order.setUser(user);
// 4. 返回order对象
return order;
}
}
Feign的自定义配置
Feign可以支持很多的自定义配置但一般情况下默认值就能满足我们的使用,如果需要自定义配置只需要创建自定义的@Bean覆盖默认的Bean
即可
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别,包含四种不同的级别 | NONE(默认值,不记录任何日志信息,提升性能) BASIC(仅记录请求的方法,URL以及响应状态码和执行时间) HEADERS(在BASIC的基础上额外记录了请求和响应头的信息) FULL(记录所有请求和响应的明细包括头信息、请求体、元数据,调试时使用) |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制(某个服务实例查询不到就换个服务实例) | 请求失败的重试机制(默认是没有),不过会使用Ribbon的重试 |
基于order-service
模块的application,yml
配置文件自定义日志配置
# 针对某个微服务的配置
feign:
client:
config:
user-service: # 针对配置的服务名称
loggerLevel: FULL # 日志级别
# 针对所有服务的配置
feign:
client:
config:
default: # default就是全局配置针对所有服务
loggerLevel: FULL # 日志级别
基于Java代码自定义日志配置: 先声明一个配置类(不用加注解), 然后声明一个Logger.Level
的对象
全局生效
: 将配置类放到启动类的@EnableFeignClients注解中@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
局部生效
: 将配置类放到对应@FeignClient注解中@FeignClient(value = "user-service", configuration = DefaultFeignConfiguration.class)
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; //日志级别设置为 BASIC
}
}
Feign的使用优化
Feign底层发起http请求需要依赖于其他框架, 其底层客户端实现包括三种,提高Frign的性能
主要手段就是使用连接池代替默认的URLConnection
URLConnection
: Feign底层默认会使用JDK自带的客户端但不支持连接池Apache HttpClient
: 支持连接池OKHttp
: 支持连接池优化Feign性能
的两种方式
BASIC或NONE
HttpClient或OKHttp
代替URLConnection第一步引入依赖: 在order-service
模块的pom文件中引入Apache的HttpClient依赖
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
第二步配置连接池: 在order-service
模块的application.yml文件
中添加配置,开启httpclient功能设置相关的连接池参数
feign:
client:
config:
default: # default全局的配置
logger-level: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持(默认是true,即如果引入了HttpClient依赖就可以使用)
max-connections: 200 # 请求的最大的连接数
max-connections-per-route: 50 # 分配给每个请求路径的最大连接数
继承(面向契约思想)
由于Feign的客户端方法
与服务提供者user-servie
模块中UserController
中对应的接口方法的代码除了方法名不同其余几乎一模一样
order-service模块
基于Feign的客户端UserClient
中定义的方法发起请求,user-servie模块
接收并处理order-service模块
发起的请求@FeignClient(value = "user-service",configuration = DefaultFeignConfiguration.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
第一步: 通过继承来共享Feign客户端和Controller中相同的代码
,先定义一个API接口, 通过定义接口方法并基于SpringMVC注解的方式声明发送Http请求的信息
public interface UserAPI{
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
第二步: Feign的客户端UserClient
继承定义好的UserAPI接口,而UserController
实现UserAPI
接口
@FeignClient(value = "user-service")
public interface UserClient extends UserAPI{}
@RestController
public class UserController implents UserAPI{
public User findById(@PathVariable("id") Long id){
// ...实现业务逻辑
}
}
基于继承方式的优缺点
优点: 简单并且实现了代码共享
缺点: 服务提供方和服务消费方紧耦合, 参数列表中的注解映射并不会继承,所以Controller中需要再次声明方法、参数列表、注解
基于抽取的最佳实践
将Feign的客户端
抽取为独立模块,并且把接口有关的POJO和默认的Feign配置
都放到这个模块中,服务消费者引用该依赖包后即可使用
第一步: 创建一个新的module
如feign-api模块
并在pom.xml文件中引入feign的starter依赖spring-cloud-starter-openfeign
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
第二步: 将order-service
模块中编写的UserClient、User、DefaultFeignConfiguration
都转移到feign-api模块中
package cn.itcast.feign.clients;
@FeignClient(value = "userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
package cn.itcast.feign.config;
public class DefaultFeignConfiguration {
@Bean
public Logger.Level logLevel(){
return Logger.Level.BASIC;
}
}
package cn.itcast.feign.pojo;
@Data
public class User {
private Long id;
private String username;
private String address;
}
第三步: 在order-service
模块中的pom.xml文件中引入我们自己编写的依赖feign-api
<dependency>
<groupId>cn.itcast.demogroupId>
<artifactId>feign-apiartifactId>
<version>1.0version>
dependency>
在OrderService
业务类中自动注入UserClient
失败问题:编译没有报错但是无法注入成功说明Spring没有为UserClient
接口创建代理对象
order-service模块
的@EnableFeignClients注解
是在cn.itcast.order
包下显然无法扫描到cn.itcast.feign.clients
包下的UserClient
上的注解package cn.itcast.order;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
// 方式一:指定Feign应该扫描的包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
// 方式二: 指定需要要加载的FeignClient接口
@EnableFeignClients(clients = UserClient.class,defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
/* @Bean
public IRule randomRule() {
return new RandomRule();
}*/
}