SpringCloud 03 —— 客户端Feign

上一章我们讲解和分析了Ribbon 的功能和作用,提供了分布式架构之前调用的负载均衡策略,使我们分布式架构必须要考虑的,使用了Spring自带的RestTemplate。而RestTemplate使用的是HttpClient发送请求。本章我们将介绍另一个重要的REST客户端Feign

Feign介绍

Feign是GitHub 上的一个开源项目,目的之简化Web Service 客户端的开发,以Java接口注解的方式调用Http请求,而不用像Spring自带的RestTemplate直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
Spring Cloud 将Feign 整合到了Netflix项目中,当与Eureka、Ribbon整合时,Feign 就具备了负载均衡的能力,在Spring Cloud 的高度整合下,使用该框架调用Spring Cloud集群服务,会大大降低开发工作量。

Feign基础

新建模块cloud-feign,导入maven依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

新建启动类

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class FeignClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeignClientApplication.class, args);
    }
}

在启动类上加入@EnableFeignClients注解,如果Feign的定义跟启动类不在一个包名下,还需要制定路径,如@EnableFeignClients(basePackages = "group.zhouning.xxx.xxx")

修改application.yml配置指定项目名称和Eureka的地址

server:
  port: 8771
spring:
  application:
    name: cloud-feign
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/

定义Feign 请求的接口

这里我们配合前面Eureka的例子,做一次远程调用

//value 就是服务的名称
@FeignClient(value = "register-eureka-client")
public interface EurekaFeignService {
    //feign中你可以有多个@RequestParam,但只能有不超过一个@RequestBody
    @GetMapping("/hello")
    String hello();
}

注解@FeignClient(value = "register-eureka-client")里面配置的value 就是前面服务的名称

注意

  • 如果你在项目里面设置了统一的请求路径(server.servlet.context-path),需要将@FeignClient注解调整@FeignClient(value = "register-eureka-client",path = "xxx")
  • Feign 里面定义的接口,有多个@RequestParam,但只能有不超过一个@RequestBody
    在定义接口的时候,如果返回的是用户自定义的实体,建议抽取出来,在Controller中实现接口,将抽取出来的接口单独打包,需要调用的项目依赖此包即可,每个项目不用重新定义一遍

定义控制层

@RestController
@Slf4j
public class EurekaFeignController {

    @Resource
    private EurekaFeignService eurekaFeignService;

    @GetMapping("/feignInfo")
    public String feignInfo() {
        String message = eurekaFeignService.hello();
        log.info(message);
        return message;
    }
}

由于需要配合的项目是cloud-client-eureka,因此我们仍然按照之前的方式启动项目,先启动eureka注册,再启动eureka-client,最后启动feign-client。访问127.0.0.1:8771/feignInfo验证,返回hello。

Feign 自定义日志

我们在开发时候一般出现问题,需要将出错信息、异常信息以及正常的输入输出打印出来,以供我们更好的排查和解决问题,比如想看到接口的性能,就需要看Feign 的日志,那么如何让Feign 的日志展示出来呢?

添加日志的配置信息:

新建FeignConfig类,并设置日志级别的输出信息

public class FeignConfig {

    /**
     * 输出的日志级别
     * @return
     */
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

通过源码可知日志等级有

  • NONE: 不输出日志
  • BASIC: 只输出请求方法的URL 和响应状态码以及接口的请求时间
  • HEADERS :将 BASIC信息和请求头信息输出
  • FULL :输出完 的请求信息

对应的源码如下所示

代码路径:feign.Logger
public static enum Level {
    NONE,
    BASIC,
    HEADERS,
    FULL;

    private Level() {
    }
}

@FeignClient修改配置

之后将配置信息添加到Feign 的接口配置上面

//value 就是服务的名称,相当于调用server名为register-eureka-client的hi,configuration添加配置
@FeignClient(value = "register-eureka-client",configuration = FeignConfig.class)
public interface EurekaFeignService {
    //feign中你可以有多个@RequestParam,但只能有不超过一个@RequestBody
    @GetMapping("/hi")
    String hello();
}

在application.yml中设置日志级别
group.zhouning是设置包路径,在这个路径里面的debug信息都会被捕获到

logging:
  level:
    group.zhouning: debug

重启应用并调用服务接口,再次访问127.0.0.1:8771/feignInfo可以看到在控制台的debug日志输出。

Feign Basic 认证配置

一般我们在调用服务间的接口时,接口上都会设置需要的权限信息,而一般的权限认证有通过token校验的、也有通过用户名密码校验的等方式。比如我们在Feign 请求中我们可以配置Basic 认证,如下

/**
 * 设置Spring Security Basic认证的用户名密码
 * @return
 */
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor(){
    return new BasicAuthRequestInterceptor("user","12345");
}

那Spring Security Basic认证又是什么呢?

Spring Security 是Spring官方提供的安全框架,是一种比较重的权限校验框架,当然还有一种比较轻量型的框架shiro。关于Spring Security 的使用,我们后面会专门开一个模块来讲解。

接着上面说,如果我不是Basic认证又该怎么办?
那当然是自定义我们的拦截器了,请请求之前做认证操作,然后往请求头中设置认证之后的信息,下面通过实现RequestInterceptor接口可以自定义自己的认证。

@NoArgsConstructor
public class FeignAuthRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        //编写自己的业务逻辑...
    }
}

然后在FeignConfig配置中添加Bean

/**
 * 自定义认证逻辑
 * @return
 */
@Bean
public FeignAuthRequestInterceptor basicAuthRequestInterceptor(){
    return new FeignAuthRequestInterceptor();
}

Feign 的超时配置

服务的请求见,肯定需要设置服务请求之间的超时时间,不可能一直在哪里等待,那feign 的超时时间如何配置?

类型备注默认值
connectTimeout连接超时时间默认 2000毫秒
readTimeout读取超时时间默认 5000毫秒

代码路径com.netflix.client.config.DefaultClientConfigImpl

方式一:

添加代码配置,但是这个不能注册到全局,需要在FeignClient上指定

/**
 * 设置连接超时时间和响应超时时间,默认值是10000和60000
 * @return
 */
@Bean
public Request.Options options(){
    return new Request.Options(5000,1000);
}

方式二:

添加application.yml的配置

需要注意的是connectTimeout和readTimeout必须同时配置,要不然不会生效,这种方式可以全局配置,至于为什么请看后面。

feign:
  client:
    config:
      default:
        connectTimeout: 10000
        readTimeout: 10000
      service-name:
        connectTimeout: 10000
        readTimeout: 10000
#  或者
#ribbon:
#  ReadTimeout: 60000
#  ConnectTimeout: 60000
#service-name:
#  ribbon:
#    ReadTimeout: 30000
#    ConnectTimeout: 30000

以上配置default是全局的配置,service-name是配置具体服务的配置

配置生效的原因

当然有人会说,我直接配置Ribbon 不也是可以的吗?毕竟Feign 集成了Ribbon,但是在Feign 的实现中,Feign的配置如果不是默认的是优先Ribbon 生