Spring Could系列(三)Rest风格访问——OpenFeign

Spring Could OpenFeign

一、OpenFeign的概述

OpenFeign是Spring Could基于Feign发明的。OpenFeign是一个声明式的Rest风格的网络访问客户端,它具有可插入式注释支持,包括feign注释和 JAX-RS 注释。 OpenFeign还支持可插入式编码器和解码器。春云增加了对Spring MVC 注释的支持,以及默认在Spring Web 中使用相同内容的支持。春云集成了eureka、Spring Could断路器以及Spring Could负载平衡器以及Ribbon,毫无疑问,在使用时提供负载平衡 。它和我们的RestTemplate的机制有点像,但是肯定跟强大,因此在分布式微服务中,它的作用主要在于网络请求这方面。

二、OpenFeign的使用
1.引入依赖
		<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

利用idea的maven工具查看该依赖包含了哪些依赖:
在这里插入图片描述

可以看出其中除了有Spring和自己的依赖外,还有hystrix(熔断)和ribbon的依赖,所以也就对应了概述中描述,他有负载均衡和熔断等功能。

2.主启动类
@SpringBootApplication
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

在上面加@EnableFeignClients注解

3.创建服务访问接口
@Component
@FeignClient(name = "cloud-provider-service")
public interface FeignService {

    @GetMapping("/payment/timeout")
    public String timeout() throws InterruptedException ;

    @GetMapping("/payment/ok")
    public String ok();

}

这个是openFeign的和RestTemplate最大的不同点之一,可以看到我们通过@FeignClient注解表明这个接口是访问接口,这个接口我们也不用写实现类,他有默认的实现类,类似于Mybatis的Mapper,而且还有一个特点是它支持Spring Mvc中的注解的使用,在里面调用服务端的接口,其中@FeignClient的name属性只要写服务端在微服务中的名字就可以直接调用服务端了,当然这要得益于服务注册中心如Eureka。

4.在controller注入服务访问接口
@RestController
@Slf4j
public class OrderController {


    @Resource
    FeignService feignService;

    @GetMapping("/consumer/payment/timeout")
    public String timeout() throws InterruptedException {
        log.info("timeout..");
        return feignService.timeout();
    }

    @GetMapping("/consumer/payment/ok")
    public String ok() {
        log.info("ok..");
        return feignService.ok();
    }

}

这样我们就可以通过我们自己的接口访问到提供服务的节点了,这样更符合我们的编码规范,controller层调service,也是代码的耦合度降低,当然肯定远远不止这些好处。

三、OpenFeign的配置
1.使用@FeignClient()中的configuration属性
@Component
@FeignClient(name = "cloud-provider-service",configuration = TimeoutConfig.class)
public interface FeignService {

    @GetMapping("/payment/timeout")
    public String timeout() throws InterruptedException ;

    @GetMapping("/payment/ok")
    public String ok();

}

TimeoutConfig.java:

public class TimeoutConfig {
    @Bean
    Logger.Level level() {
        return Logger.Level.FULL;
    }
}

这样FeignService 的请求日志等级都是FULL,当然也可以配置feign.Decoder,feign.Encoder,feign.Contract,同理。

2.Spring配置类配置

上面配置当然直接在配置类中配置也可以,但是此时对这个接口中请求不生效,而且日志其实还要配置,后面会展示,可以看作是全局的。

@Configuration
public class FeignConfig {

    @Bean
    Logger.Level level() {
        return Logger.Level.NONE;
    }

}

3.配置文件配置
  • 指定@FeignClient中name的配置
    application.yml
feign:
    client:
        config:
        #指定了名字
            feignName: cloud-provider-service
                connectTimeout: 5000
                readTimeout: 5000
                loggerLevel: full
                errorDecoder: com.example.SimpleErrorDecoder
                retryer: com.example.SimpleRetryer
                defaultQueryParameters:
                    query: queryValue
                defaultRequestHeaders:
                    header: headerValue
                requestInterceptors:
                    - com.example.FooRequestInterceptor
                    - com.example.BarRequestInterceptor
                decode404: false
                encoder: com.example.SimpleEncoder
                decoder: com.example.SimpleDecoder
                contract: com.example.SimpleContract
                capabilities:
                    - com.example.FooCapability
                    - com.example.BarCapability
                metrics.enabled: false
  • 针对所有的feignClient的默认配置

application.yml

feign:
    client:
        config:
            default:
                connectTimeout: 5000
                readTimeout: 5000
                loggerLevel: basic
3.配置文件和配置类的优先级

默认配置文件的优先级更高,也就配置文件中的配置优先生效,但是要改变这个特点可以配置:
feign.client.default-to-properties=false

4.让同一个名字相同不同的feignClient具有不同配置,利用@feignClient的contextId区别

FooClient.java

@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
    //..
}

BarClient.java

@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
    //..
}

两个接口的@FeignClient中name一样,他们访问同一个微服务,但是他们的配置类不一样,为了区分,用contextId区分。

5.超时处理

openFiegn处理超时,主要配置以下:

  • connectTimeout:设置建立连接所用的时间的超时,超时会报出异常
  • readTimeout:设置建立连接后从服务器读取到可用资源所用的超时时间
feign:
    client:
        config:
            default:
                connectTimeout: 5000
                readTimeout: 5000
          
四、手动创建feign客户端

前面的演示,都是基于声明式的建立OpenFeign的客服端,很简洁,但是Spring Could这么强大,肯定也提供了手动创建。以下摘自官网例子:

@Import(FeignClientsConfiguration.class)
class FooController {

    private FooClient fooClient;

    private FooClient adminClient;

    @Autowired
    public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerCapability micrometerCapability) {
        this.fooClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .contract(contract)
                .addCapability(micrometerCapability)
                .requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
                .target(FooClient.class, "https://PROD-SVC");

        this.adminClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .contract(contract)
                .addCapability(micrometerCapability)
                .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
                .target(FooClient.class, "https://PROD-SVC");
    }
}

相当于创建了一个访问PROD-SVC微服务的客服端。

五、feign与断路器

我们知道,feign结合了hystrix的依赖,所以它肯定用hystrix的支持的

1.OpenFeign开启hystrix的支持
feign:
  hystrix:
    enabled: true

2.全局禁用OpenFeign使用hystrix
@Configuration
public class FooConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}

配置该类即可

3.OpenFeign使用短路器的实现回退

回退:也就是出现故障,异常或者超时等情况的时候,不让客户端一直等待,及时返回对用户友好的信息,而不是直接把异常打印给用户,也就一个兜底的返回。
要实现OpenFeign使用断路器实现回退,有如下步骤:

  • 创建一个实现Feign客服端接口的类
    HytrixService.java

@Component
public class HytrixService implements FeignService {
    @Override
    public CommonResult<Payment> get(Long id) {
        return null;
    }

    @Override
    public String timeout() {
        return "解耦,对该服务进行处理...";
    }
}

这里面方法的实现,就是对应这个请求出现异常等回退兜底的方法

  • 在feign客户端的@FeignClinet中添加fallback属性,注明上面的类
Component
@FeignClient(name = "cloud-provider-service",fallback = HytrixService.class)
public interface FeignService {

    @GetMapping("/payment/get/{id}")
    CommonResult<Payment> get(@PathVariable("id") Long id);

    @GetMapping("/payment/timeout")
    String timeout();
}
  • 实现效果
    我们在payment/timeout这个接口调用的微服务中写一个运行时异常int i = 1 / 0;按照断路器的作用,遇到异常应该不会返回正常的值,而是返回兜底方法的结果

服务的提供者,出现运行时异常

  @GetMapping("/payment/timeout")
    public String timeout() {
        int i = 1/0;
        //故意使程序暂停3s
        try {
            TimeUnit.SECONDS.sleep(3);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
        return port;
    }

消费者利用feign客户端访问服务提供者的接口:

@GetMapping("/consumer/payment/timeout")
    public String timeout() {
        return feignService.timeout();
    }

正常应该时返回端口号,出现异常应该有兜底方法,返回"解耦,对该服务进行处理…",这里int i=i/0,发生异常,应该走兜底方法:
在这里插入图片描述
可以看见确实走了回退方法。

六、OpenFeign日志

前面配置的时候也配置了日志,但不是主要讲解,其实除了上述的配置,还有一步,配置那个feign客户端以及作为什么日志级别打印出:

logging:
  level:
    com.kyg.could.service.FeignService: debug

指定是FeignService这个客户端,OpenFeign中这些请求的日志信息在spring 中以debug输出; OpenFeign中的日志级别:

  • NONE,无记录(默认)。
  • BASIC,仅记录请求方法和 URL 以及响应状态代码和执行时间。
  • HEADERS,记录基本信息以及请求和响应标题。
  • FULL,记录请求和响应的标题、正体和元数据。

配置好了之后,我们可以看效果:请求过后,控制台上有日志(这里设置feigin的级别为FULL,以debug级别输出):

在这里插入图片描述
可以看到请求的信息都以日志打印在控制台,并且都是debug级别。

七、OpenFeign中与@primary注解,以及@FeignClient的primay属性

在短路器中,我们创建了一个FeignSevice的实现类:

@Component
public class HytrixService implements FeignService {
    @Override
    public CommonResult<Payment> get(Long id) {
        return null;
    }

    @Override
    public String timeout() {
        return "解耦,对该服务进行处理...";
    }
}

并且这个类是注入到Spring 容器中的,以前我们没有实现自己创建实现类的时候,我们利用@AutoWire也能将FeignService 注入使用,意味着Spring中有默认的实现类,这时候,有两个实现类,按理来说,Spring会不知道找谁,原本在spring中,我们是用@primary来标注应该优先找谁的,我们这里没有配,但是为什么没有报错呢,是因为@FeignClient的primay属性

public @interface FeignClient {
    @AliasFor("name")
    String value() default "";

    /** @deprecated */
    @Deprecated
    String serviceId() default "";

    String contextId() default "";

    @AliasFor("value")
    String name() default "";

    String qualifier() default "";

    String url() default "";

    boolean decode404() default false;

    Class<?>[] configuration() default {};

    Class<?> fallback() default void.class;

    Class<?> fallbackFactory() default void.class;

    String path() default "";

    boolean primary() default true;
}

默认primary=true,就相当于优先找默认实现的,而不是我们自定义的,假设我们设置primary=true

@Component
@FeignClient(name = "cloud-provider-service",fallback = HytrixService.class,primary = false)
public interface FeignService {

    @GetMapping("/payment/get/{id}")
    CommonResult<Payment> get(@PathVariable("id") Long id);

    @GetMapping("/payment/timeout")
    String timeout();
}

启动则报错

Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type 'com.kyg.could.service.FeignService' available:
 expected single matching bean but found 2: hytrixService,com.kyg.could.service.FeignService

所有我们默认不用更改primary属性,除非自己真的能写出优于默认实现类的实现方法。

八、总结

以上这些知识都是阅读官网,结合自己看视频总结,可能不是很全,但是常用,写这些文章,主要还是逼着自己看官网学习,锻炼自我学习能力,同时也是对知识的总结,方便复习,也希望可以帮助到阅读的小伙伴,可能自己的理解可能也有偏差,望读者指教纠正,共勉!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值