来看一下之前我们使用RestTemplate调用时编写的Contrriller代码:
// 2.利用RestTemplate发起HTTP请求
// 2.1 url地址
String url = "http://userserver/user/" + order.getUserId();
// 2.2 发送http请求,实现远程调用
User user = restTemplate.getForObject(url, User.class);
// 封装user到Order
存在下面的问题:
Feign是一个声明式的http客户端,官方地址:春云开假 (spring.io)
起作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
声明式,这个概念在之前我们学习Spring中的事务管理的时候,曾说过有一个叫做声明式事务的东西,它的作用就是简化我们应用事务的过程。在使用声明式事务之前,我们开启事务需要手动的管理事务,也就是手动进行事务的开启,回滚等一系列操作,但是声明式事务我们只需要指定需要应用事务的地方,剩下的关于事务的细节全部都交给Spring去管理即可,声明式就是这样的作用。
使用Feign的步骤如下:
package cn.itcast.order;
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
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
然后就是使用Feign,首先我们需要编写Feign的客户端:
package cn.itcast.order.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("userserver")
public interface UserClient {
@GetMapping("/user/{id}")
User FindById(@PathVariable Long id);
}
为了减少学习成本,其实是为了迎合SpringBoot用户的使用习惯,在Feign的客户端中,默认使用SpringMVC的编码习惯,比如:
使用这种方式,维护参数列表就是维护方法中的参数,比之前的编码要简单一些。
然后我们再看一下使用Feign调用之后的service代码:
package cn.itcast.order.service;
import cn.itcast.order.Feign.UserClient;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 使用Feign进行远程调用
User user = userClient.FindById(order.getUserId());
// 封装user到Order
order.setUser(user);
// 4.返回
return order;
//
}
}
看着比之前就简单了很多,并且远程调用就像是方法的调用一样。
然后我们去浏览器访问Controller:
可以看到是能正常的访问的,并且我们可以多访问几次,然后看一下控制台的日志,查看一下两台服务器的访问情况:
可以看到两台服务器都被访问过,这就说明,Feign除了做了远程调用,还做了负载均衡。原因就是在Feign的内部,集成了ribbon的依赖,我们可以打开Feign的依赖看一下:
这里存在一个ribbon的依赖。
Feign的使用步骤
Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
一般我们需要配置的就是日志级别。
配置Feign的配置有两种方式:
feign:
client:
config:
default: # 这里的defaule就是全局配置,如果这里写的是服务名称,则是针对某个微服务的配置
logger-level: FULL
feign:
client:
config:
userserver: # 这里的defaule就是全局配置,如果这里写的是服务名称,则是针对某个微服务的配置
logger-level: FULL
先来看一下默认的日志显示:
就只有一个SQL语句的日志,然后我们修改配置,重启服务器之后,再来访问一下浏览器,看这次的日志输出内容是什么:
这次的日志就非常的多了,除了上面的SQL语句日志之外,这里还有很大一堆的日志都是Feign的。
package cn.itcast.order.Feign;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class FeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC;
}
}
@EnableFeignClients(defaultConfiguration = FeignConfig.class)
@FeignClient(value = "userserver",configuration = FeignConfig.class)
然后我们重启服务器,再次在浏览器中访问接口,这次我们再来看日志:
现在就清爽多了,也就两条日志,这就是BASIC日志级别。
Feign底层的客户端实现
因此优化Feign的性能主要包括:
引入依赖:
配置连接池:
feign:
client:
config:
default: # defaule全局的配置
logger-level: BASIC # 日志级别,BASIC就是基本的请求和相应信息
httpclient:
enabled: true # 开启Feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径最大的连接数,这个连接数并不是固定的,而是根据实际业务进行压力测试得来的最优解
方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
首先我们来看一下我们之前写的使用Feign做的远程调用代码,和被调用的方法:
这么一对比,是不是一模一样,至于为什么一样就不用多说了吧,因为一个暴露接口,一个请求接口,那么这些基本的参数,比如请求方式,请求参数,请求路径必须都一样才能请求到啊,这就是为什么这两个方法一模一样的原因。那么方法既然一模一样,那么能不能抽取出来,作为一个共同的标准去引用他。作为同一个方法,当我要编写一个向外暴露的接口的时候,就不用自己写了,去继承这个标准就可以了,同样的,当我要访问一个接口的时候,具体的逻辑我也不用写了,去继承这个标准就可以了,因为被请求的那边也是继承这个标准,自然是一样的。这就是继承的基本逻辑,用一个共同的标准,统一请求方和被请求方两者的代码。
但是这种方式有一定的缺点:
方式二(抽取):将FeignClient抽取为的独立模块,并且把接口有关的POJO,默认的Feign配置都放到这个模块中,提供给消费者使用:
在一开始的时候,我们有一个服务的提供者和服务的消费者,当我的消费者要调用提供者的接口的时候,我要自己写一个方法去调用,就要做配置,写代码,调用。当我的消费者数量多了之后,每一个消费者要调用这个提供者的时候,都要自己写一套代码做调用,非常的麻烦。
这时候,我们就可以将这些重复的操作,比如做配置,写代码,这些过程抽取出来,作为一个feign-api组件,除了配置,代码,还有接收代码的实体类,以及所有有关的配置,方法,类都放在这个feign-api里面,当我们有某个服务要用的时候,直接引入这个feign-api组件,直接调用组件中的方法就可以了,这也是一种方式。
实现最佳实践方式二的步骤如下:
当我们都做完之后,会出现一个问题,就是在启动的时候,有时候在编译期间就已经会报错了,就是找不到我们对应的UserClient的实例对象:
这是因为,在之前我们能自动注入,是因为我们在UserClient上写的@FeignClient在被SpringBoot扫描到之后,会自动的将对应的接口实例化成一个对象,然后注入到容器中,在我们要用的时候自动注入,但是此时我们的SpringBoot的启动类与我们的UserClient并不在一个类里面,所以无法扫描到这个注解,也就无法自动注入,这时候,我们需要在启动类的@EnableFeignClients上加上一个属性:basePackageClasses,他指向我们的UserClient的Class文件:
@EnableFeignClients(defaultConfiguration = FeignConfig.class,basePackageClasses = UserClient.class)
然后再回去看刚才报错的地方:
此时报错就没有了。
然后我们正常启动项目,在浏览器访问之前的接口,如果能正常访问,则表示我们的Feign已经正常运行了。