当前位置: 代码迷 >> 综合 >> Feign 的使用
  详细解决方案

Feign 的使用

热度:82   发布时间:2023-09-27 16:14:33.0

Feign 是 Netflix 开发的声明式、模板化的 HTTP 客户端,它可以帮助我们更加便捷、优雅地调用 HTTP API

前言

  • 本文中涉及到的 Spring Cloud 内容,可以查看我的相关博客

  • 使用的 Feign 版本为 1.4.3

  • 服务端指 Eureka Server 所在微服务,客户端指提供数据的微服务,消费端指获取数据的微服务

1、Eureka 整合 Feign

添加 Feign 依赖

compile('org.springframework.cloud:spring-cloud-starter-feign')

创建 Feign 接口,添加 @FeignClient 注解

//name是客户端的虚拟主机名
@FeignClient(name = "microservice-provider-user")
@Service
public interface UserFeignClient {
    //这里写客户端的访问路径@GetMapping("/{id}")User findById(@PathVariable("id") Long id);
}

其中的 microservice-provider-user 是任意一个客户端的虚拟主机名,用于创建 Ribbon 负载均衡器

下面修改 Controller 代码

@RestController
public class BaseController {
    
// @Autowired
// private RestTemplate restTemplate;@Autowiredprivate UserFeignClient userFeignClient;//之前我们是使用 RestTemplate 调用,需要拼接字符串
// @GetMapping("/user/{id}")
// public User findById(@PathVariable Long id){
    
// return this.restTemplate.getForObject("http://microservice-provider-user/"+id,User.class);
// }//相比于 RestTemplate ,Feign 明显地更加简洁@GetMapping("/user/{id}")public User findById_feign(@PathVariable Long id){return this.userFeignClient.findById(id);}
}

启动类上添加注解

@EnableFeignClients

这样我们就可以使用 Feign 来调用微服务的 API ,取代使用拼接方式访问的 RestTemplate

2、自定义 Feign 配置

创建 Feign 配置类


/*** !!不能在主应用程序的上下文的@Component中,即不能在启动类所在包中*/
@Configuration
public class FeignConfiguration {
    /** * 将契约改为feign原生的默认契约,这样可以使用feign自带的注解。* !!修改为默认契约后,启动应用时下面接口会报错,所以建议不要使用*/@Beanpublic Contract feignContract(){return new Contract.Default();}
}

修改 Feign 接口如下

//使用 configuration 属性指定配置类
@FeignClient(name = "microservice-provider-user",configuration = FeignConfiguration.class)
@Service
public interface UserFeignClient {
    /*** 经过测试,如果启用上面的默认契约,这里在启动应用时会报错* 下面两种方式都是可以的,RequestLine 是 Feign 的自带注解*/
// @RequestLine("GET/{id}")
// User findById(@Param("id") Long id);@GetMapping("{id}")User findById(@PathVariable("id") Long id);
}

类似地可以自定义 Feign 的编码器、解码器等(这些未实验)。例如调用需要 HTTP Basic 认证的接口,配置类 FeignConfiguration 中加入:

//过滤器 Http Basic 认证
@Bean
public BasicAuthorizationInterceptor basicAuthorizationInterceptor(){return new BasicAuthorizationInterceptor("user","password");
}

3、自建 Feign

在某些场景下,自定义的 Feign 满足不了需求,此时可用 Feign Builder API 手动创建 Feign

首先,在 客户端 微服务上建立 Spring Security 配置

导入 Spring Security 依赖

compile( 'org.springframework.boot:spring-boot-starter-security')

创建配置类(可以全部copy)

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    @Overrideprotected void configure(HttpSecurity http) throws Exception{//所有的请求,都需要经过HTTP basic认证http.authorizeRequests().anyRequest().authenticated().and().httpBasic();}@Beanpublic PasswordEncoder passwordEncoder(){//明文编码器。这是一个不做任何操作的密码编码器,是Spring提供给我们做明文测试的return NoOpPasswordEncoder.getInstance();}//在下面@Autowiredprivate CustomUserDetailsService userDetailService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{auth.userDetailsService(this.userDetailService).passwordEncoder(this.passwordEncoder());}@Componentclass CustomUserDetailsService implements UserDetailsService{/*** 模拟两个账户* ① 账号 user,密码123,角色是user-role* ② 账号 admin,密码123,角色是admin-role*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {if("user".equals(username)) {return new SecurityUser("user", "123", "user-role");}else if("admin".equals(username)) {return new SecurityUser("admin", "123", "admin-role");}elsereturn null;}}class SecurityUser implements UserDetails {private static final long serialVersionUID = 1L;public SecurityUser(String username, String password, String role) {super();this.username = username;this.password = password;this.role = role;}public SecurityUser() {}private Long id;private String username;private String password;private String role;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {Collection<GrantedAuthority> authorities = new ArrayList<>();SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role);authorities.add(authority);return authorities;}//关键点:以下四个方法返回值返回true@Overridepublic boolean isAccountNonExpired() {
   return true;}@Overridepublic boolean isAccountNonLocked() {
   return true;}@Overridepublic boolean isCredentialsNonExpired() {
   return true;}@Overridepublic boolean isEnabled() { return true;}@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return this.username;}}
}

修改 Controller,打印当前登录的用户信息

@GetMapping("{id}")
public User findById(@PathVariable long id){Object principal= SecurityContextHolder.getContext().getAuthentication().getPrincipal();if(principal instanceof UserDetails){UserDetails user=(UserDetails) principal;Collection<? extends GrantedAuthority> collection=user.getAuthorities();//打印当前用户信息for(GrantedAuthority c:collection){BaseController.LOGGER.info("用户:{},角色:{}",user.getUsername(),c.getAuthority());}}//这里从数据库获取 User,我用的是 MyBatisreturn userMapper.findById(id);
}

启动服务端和客户端测试,会弹出登录对话框

分别使用 user / 123 和 admin / 123 登录,会输出类似以下的日志:

2018-03-19 15:53:43.605  INFO 4404 --- [nio-8001-exec-3] c.i.port.controller.BaseController       : 用户:user,角色:user-role
2018-03-19 15:53:43.605  INFO 4404 --- [nio-8001-exec-3] c.i.port.controller.BaseController       : 用户:admin,角色:admin-role

用户信息是保存在了 Session 中,所以注销用户的方法就是重启浏览器

现在修改消费端微服务
  • 去掉 Feign 接口 UserFeignClient 上的 @FeignClient 注解

  • 去掉启动类上的 @EnableFeignClients 注解

修改 Controller 如下:

@Import(FeignClientsConfiguration.class)
@RestController
public class BaseController {
    private UserFeignClient userFeignClient;private UserFeignClient adminFeignClient;@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")@Autowiredpublic BaseController(Decoder decoder, Encoder encoder, Client client, Contract contract){this.userFeignClient= Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract).requestInterceptor(new BasicAuthRequestInterceptor("user","123")).target(UserFeignClient.class,"http://microservice-provider-user/");this.adminFeignClient= Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract).requestInterceptor(new BasicAuthRequestInterceptor("admin","123")).target(UserFeignClient.class,"http://microservice-provider-user/");        }@GetMapping("/user-user/{id}")public User findById_user(@PathVariable Long id){return this.userFeignClient.findById(id);}@GetMapping("/user-admin/{id}")public User findById_admin(@PathVariable Long id){return this.userFeignClient.findById(id);}
}

其中,@Import 导入的是 Spring Cloud 为 Feign 默认提供的配置类。两个方法各司其职,分别登录 user 和 admin,使用同一个接口 UserFeignClient

启动服务端、客户端、消费端(端口号8010),访问http://localhost:8010/user-user/1 和http://localhost:8010/user-admin/1,可以看到客户端微服务打印登录信息

3、Feign 的其他支持

对继承的支持

使用继承,可以将一些公共操作分组到一些父接口中,从而简化 Feign 的开发

创建基础接口 : UserService.java

public interface UserService{@RequestMapping(method = RequestMethod.GET,value = "/user/{id}")User getUser(@PathVariable("id") long id);
}

服务提供者 Controller : UserResource.java

@RestController
public class UserResource implements UserService{
    //...
}

服务消费者 : UserClient.java

@FeignClient("user")
public interface UserClient extends UserService{
    
}
对压缩的支持

一些场景下,可能需要对请求后响应进行压缩,此时可使用以下的属性启用 Feign 的压缩功能

feign.compression.request.enabled=true
feign.compression.response.enabled=true

更详细的配置

feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

其中
- feign.compression.request.mime-types 用于支持的媒体类型列表,默认是 text/xml、application/xml 以及 application/json
- feign.compression.request.min-request-size 用于设置请求的最小阈值,默认是2048

4、Feign 的日志

把项目回归到 第2条( 自定义 Feign 配置 ) 的状态:( 如果你拒绝,可以直接跳到下面黑体字 )
- 加上启动类注解
- 加上 Feign 接口的注解
- UserFeignClient 使用 @Autowire 自动导入

配置类 FeignConfiguration 中加入:

@Bean
public Logger.Level feignLoggerLevel(){//设置为输出详细信息return Logger.Level.FULL;
}

application.xml 中添加如下:

logging:level:# 将Feign接口的日志级别设置为DEBUGcn.itscloudy.consumer.feign.UserFeignClient: DEBUG

其中 cn.itscloudy.consumer.feign.UserFeignClient 是你 Feign 接口的路径

然后启动服务端、客户端以及消费端,测试可以发现 Feign 请求的各种细节非常详细地记录了下来
Feign 的使用
把上面方法返回值设为 Logger.Level.BASIC,再次测试,控制台只打印了请求方法、请求的 URL 等

如果,项目是自建 Feign 的状态,即第3条的状态,需要以下步骤

首先,自建 MyLogger 类继承 feing.Logger.ErrorLogger (因为经过测试,只有 ErrorLogger 才能输出信息)

public class MyLogger extends feign.Logger.ErrorLogger{
    @Overrideprotected void log(String configKey, String format, Object... args) {//所有关键信息都在 args 里了,可以自己输出看下,然后自定义格式输出所需信息for(Object o:args){System.out.println(o.toString());}
// super.log(configKey,format,args);}
}

构建 UserFeignClinet 时加上 .logger 指定 Logger ,加上 .logLevel 指定输出级别。

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Autowired
public BaseController(Decoder decoder, Encoder encoder, Client client, Contract contract){this.userFeignClient= Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract).logger(new MyLogger()).logLevel(feign.Logger.Level.FULL).requestInterceptor(new BasicAuthRequestInterceptor("user","123")).target(UserFeignClient.class,"http://microservice-provider-user/");
}

这里的 Logger 使用 MyLogger,当然也可以直接使用 ErrorLogger ,但是输出格式是全红
Feign 的使用
然后,application.yml 也还需要有所添加,添加内容上面有叙述,不再赘述。
( 如果针对这一点有更好解决方法,欢迎告知 )

5、构造 Feign 多参数请求

GET

@FeignClient(name = "microservice-provider-user")
@Service
public interface UserFeignClient {
    //方法一
// @GetMapping("/")
// User findUser(@RequestParam("id") Long id),@RequestParam("username") username);//方法二@GetMapping("/")User findUser(@RequestParm Map<String,Object> map);
}

使用方法二调用,可使用以下类似方法

public User getUser(int id,String username){HashMap<String,Object> map=new HashMap<>();map.put("id",id);map.put("username",username);return this.userFeignClient.findUser(map);
}

POST

@FeignClient(name = "microservice-provider-user")
@Service
public interface UserFeignClient {
    //使用@RequestBody注解@PostMapping("/")User findUser(@RequestBody User user);
}

后记

以上代码大部分经过了我的测试

引用的内容源自《Spring Cloud与Docker微服务架构实战》周立/著

  相关解决方案