9 Feign学习
9.1 Feign简介
Feign是Netflix开发的声明式、模板化的HTTP客户端,其灵感来自Retrofit、JAXRS-2.0以及WebSocket。Feign可帮助我们更加快捷、优雅地调用HTTP API。
在Spring Cloud中,使用Feign非常简单,创建一个接口,并在接口上添加一些注解,代码就完成了。
Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud 对Feign进行了增强,是Feign支持了SpringMVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
9.2为服务消费者整合Feign
前面我们使用RestTemplate(通过整合Ribbon实现负载均衡)调用Restful API,下面我们使用Feign,实现声明式的RESTful API调用。
9.2.1 准备项目
复制项目microservice-consumer-movie,修改名字microservice-consumer-movie-feign,将artifactId修改为microservice-consumer-movie-feign
9.2.2 添加Feign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
9.2.3 创建一个Feign接口
创建一个Feign接口并添加@FeignClient注解
@FeignClient(name="user-provider")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id") Integer id);
}
@FeignClient注解中的name属性是一个任意的客户端名称,用于创建Ribbon负载均衡器。
在本例中,由于使用了Eureka,所以Ribbon会把microservice-provider-user解析成Eureka Server服务注册表中的服务。
当然,如果不使用Eureka,可以使用url属性指定请求的URL
9.2.4 修改controller
修改MovieController,让其调用Feign
@RestController
@Slf4j
public class MovieController {
@Resource
private UserFeignClient userFeignClient;
@GetMapping("/user/{id}")
public User findById(@PathVariable Integer id) {
return userFeignClient.getById(id);
}
}
9.2.5 修改启动类
添加@EnableFeignClients注解
@SpringBootApplication
@EnableFeignClients
public class MovieApplication {
public static void main(String[] args) {
SpringApplication.run(MovieApplication.class, args);
}
}
9.2.6 启动测试
先启动注册中心Eureka-server
然后启动两个提供者 microservice-provider-user
java -jar microservice-provider-user-1.0-SNAPSHOT.jar
java -Dserver.port=9999 -jar microservice-provider-user-1.0-SNAPSHOT.jar
注意:服务提供者使用单机注册中心,不是集群
然后启动服务消费者
访问controller 进行测试(microservice-consumer-moive-feign)
可以看到,程序不但实现了声明式的REST APT调用,同时还实现了客户端侧的负载均衡。
9.3 自定义Feign配置
许多场景下,我们需要自定义Feign的配置,例如配置日志级别、定义拦截器等。
Spring cloud允许使用Java代码或者自定义Feign的配置,两者是等价的。
9.3.1 使用java代码自定义Feign的配置
在Spring Cloud中,Feign的默认配置类是FeignClientsConfiguration,该类定义了Feign默认使用的编码器、解码器、所使用的契约等。
Spring Cloud允许通过注解@FeignClient的Configuration属性自定义Feign的配置,自定义的配置优先级比FeignClientsConfiguration的优先级高。
在spring cloud 中,feign默认的契约是SpringMvcContract,因此他可以使用SpringMVC的注解。
下面来自定义Feign的注解,让它使用Feign自带的注解进行工作。
9.3.1.1 准备工作
复制microservice-consumer-movie-feign 成microservice-consumer-movie-feign-custom
修改artifactId 为microservice-consumer-movie-feign-custom
9.3.1.2 edu.xja.config包中加入配置类
/**
* 该类为Feign的配置类
* 注意:该类可以不写@Configuration注解,如果加了@configuration注解,那么该类不能放在主应用程序上下文@ComponentScan所扫描的包中
*/
public class FeignConfig {
/**
* 将契约改为feign原生的默认契约。这样就可以使用feign自带的注解了。
* @return 默认的原生契约
*/
@Bean
public Contract feignContract(){
return new Contract.Default();
}
}
9.3.1.3 修改edu.xja.feign下面的UserFeignClient
@FeignClient(name="user-provider",configuration = FeignConfig.class)
public interface UserFeignClient {
/**
* 使用feign自带的注解@RequestLine
* @param id
* @return
*/
@RequestLine("GET /user/{id}")
User getById(@Param("id") Integer id);
}
类似的,还可以自定义Feign的编码器、解码器、日志打印,甚至为Feign添加拦截器。
启动测试
先启动注册中心
再启动两个用户服务
再启动microservice-consumer-movie-feign-custom
访问movieController测试
9.3.2 使用全局配置
注解@EnableFeignClients为我们提供了defaultConfiguration属性,用来指定默认的配置类。
比如:
@EnableFeignClients(defaultConfiguration=DefaultRibbonConfig.class)
9.3.3 使用属性自定义配置
9.3.3.1 配置指定名称的Feign Client
对于指定名称的Feign Client(本例中Feign Client的名称为feign2),配置如下:
feign:
client:
config:
user-provider:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
encoder: feign.form.spring.SpringFormEncoder
requestInterceptors:
- com.example.feign2.interceptor.RequestHeaderInterceptor
9.3.3.1 通用配置(client 是feign下面的,由于markdown导致前面空格丢失)
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
encoder: feign.form.spring.SpringFormEncoder
requestInterceptors:
- com.example.feign2.interceptor.RequestHeaderInterceptor
属性配置的方式比java代码配置方式优先级高。
9.5 Feign对压缩的支持
在一些场景下,可能需要对请求和响应进行压缩,此时可使用以下属性启用Feign的压缩功能。
feign.compression.request.enabled=true
feign.compression.response.enabled=true
如果写在yml文件也可以,idea有自动提示
9.6 Feign日志
在很多场景下,需要了解Feign处理请求的具体细节,那么如何满足这种需求呢?
Feign对日志的处理非常灵活,可为每个Feign客户端指定日志记录策略,每个Feign客户端都会创建一个logger。默认情况下,logger的名称是Feign接口的完整类名。需要注意的是,Feign的日志打印只会对DEBUG级别做出响应。
我们可以每个Feign客户端配置各自的Logger.Level对象,告诉Feign记录哪些日志。Logger.Level的值有以下选择:
NONE: 不记录任何日志(默认值)
BASIC: 仅记录请求方法、URL、响应状态代码以及执行时间。
HEADERS: 记录basic级别的基础上,记录请求和响应的header。
FULL: 记录请求和响应的header、body和元数据。
9.6.1 使用编码方式设置日志级别
复制工程microservice-consumer-movie-feign到microservice-consumer-movie-feign-log
编写Feign配置类
// 如果在主启动类下,改配置注解@Configuration可以注释
@Configuration
public class FeignLogConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
}
注意把config放到扫描范围的外面
疑问:如果放到扫描范围内,加@Configuration和不加的区别
我的理解是,feign 也会扫描配置文件,
修改Feign接口,指定配置类
@FeignClient(name = “user-provider", configuration = FeignLogConfiguration.class)
public interface UserFeignClient {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
}
修改application.yml,指定Feign接口的日志级别为DEBUG
logging:
level:
edu.xja.feign.UserClientFeign: DEBUG # 将Feign接口的日志级别设置成DEBUG,因为Feign的Logger.Level只对DEBUG作出响应。
启动测试
9.6.2 使用属性配置日志级别
feign:
client:
config:
user-provider:
loggerLevel: headers
logging:
level:
edu.xja.feign.UserClientFeign: DEBUG # 将Feign接口的日志级别设置成DEBUG,因为Feign的Logger.Level只对DEBUG作出响应。
9.7 使用Feign构造多参数请求
之前的示例我们的请求的参数只有一个,那么Feign如何构造多参数请求呢?
9.7.1 GET请求多参数的URL
假设url中包含多个参数,比如http://user-provider/user/get?ID=1&username=zhangsan
用户服务提供端
UserDao加入方法:
/**
* 根据用户名和年龄获取用户
* @param username
* @param age
* @return
*/
User getByUsernameAndAge(String username,Integer age);
User.xml 加入
<select id="getByUsernameAndAge" resultType="User">
select
id,username, age, name,balance
from t_user
where username = #{username} and age=#{age}
</select>
UserService和UserServiceImpl 分别加入方法,自己加入
UserController 中加入方法
@GetMapping("/getByUsernameAndAge")
public User getByUsernameAndAge(User user) {
return userService.getByUsernameAndAge(user.getUsername(),user.getAge());
}
修改microservice-consumer-movie-feign工程中的UserClientFeign类,加入下面代码
错误写法(requestBody只能是post方法)
@GetMapping("/user/getByUsernameAndAge")
User getByUsernameAndAge(@RequestBody User user);
MovieController中加入方法用于测试
@GetMapping("/getByUsernameAndAge")
public User getByUsernameAndAge(){
User user=new User();
user.setUsername("zhaoliu");
user.setAge(20);
return userClientFeign.getByUsernameAndAge(user);
}
启动相关服务测试
启动注册中心eureka-server
启动服务提供者 microservice-provider-user
启动服务消费者 microservice-consumer-movie-feign
浏览器器访问: 进行测试
正确写法:
- get方式参数注解只有:@PathVariable和@RequestParam
- post方式参数注解只有:@RequestBody,如果不写,默认这一个,服务提供者必须也是@RequestBody,不然接收不到参数
- 如果其他方式,可以自定义,比如文件上传
方法一:
@GetMapping("/user/getByUsernameAndAge")
User getByUsernameAndAge1(@RequestParam("username") String username,@RequestParam("age") int age);
@GetMapping("/getByUsernameAndAge1")
public User getByUsernameAndAge1(){
return userClientFeign.getByUsernameAndAge1("zhaoliu",20);
}
方法二:
多参数的URL也可以使用Map构建。当目标url参数非常多时,可以使用这种方式简化Feign接口的编写。
@GetMapping("/user/getByUsernameAndAge")
User getByUsernameAndAge2(@RequestParam Map<String,Object> userMap);
@GetMapping("/getByUsernameAndAge2")
public User getByUsernameAndAge2(){
Map<String,Object> map=new HashMap<>();
map.put("username","zhaoliu");
map.put("age",20);
return userClientFeign.getByUsernameAndAge2(map);
}
9.7.2 POST请求包含多个参数
@PostMapping("/user/add")
int addUser(@RequestBody User user);
Controller里加入
@PostMapping("/add")
public int getByUsernameAndAge2(@RequestBody User user){
return userClientFeign.addUser(user);
}
再次强调,被调用方,也就是服务提供者,入参也是被@RequestBody修饰