liujie
liujie
Published on 2023-08-15 / 18 Visits
0
0

OpenFeign

OpenFeign

OpenFeign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用OpenFeign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。
Spring Cloud OpenFeign以将OpenFeign集成到Spring Boot应用中的方式。首先利用了OpenFeign的声明式方式定义Web服务客户端;其次还更进一步,通过集成Ribbon或Eureka实现负载均衡的HTTP客户端。

OpenFeign例子

controller类定义接口类

@RestController
@RequestMapping("/test")
public class TestController {
    @GetMapping("/get/{id}")
    public TestVO testGet(@PathVariable(name = "id")Long id) {
        if (id > 10) {
            throw new RuntimeException();
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new TestVO(id, "success from service-a");
    }
}

service-a的启动类

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

配置文件

server:
  port: 18001
  servlet:
    context-path: /service-a
spring:
  application:
    name: service-a
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

service-a1配置端口不一样,其他都一样

定义feign

@FeignClient(value = "service-a", fallbackFactory = DemoProviderFeignClientFallbackFactory.class)
@RequestMapping("/service-a/test")
public interface TestApi {

    @GetMapping("/get/{id}")
    TestVO testGet(@PathVariable(name = "id") Long id);
}

定义请求端

@RestController
@RequestMapping("/sample")
public class SampleController {

    @Autowired
    TestApi testApi;

    @GetMapping("/hello")
    public String hello() {
        return "hello world from service-b";
    }

    @GetMapping("/helloDelay")
    public String helloDelay(@RequestParam Long milliSecs) throws InterruptedException {
        Thread.sleep(milliSecs);
        return "hello delayed world from service-b";
    }

    @GetMapping("/get/{id}")
    public TestVO testGet(@PathVariable(name = "id")Long id) {
        return testApi.testGet(id);
    }
}

服务启动起来调用调用端结果负载均衡均匀地打在不同服务上

feign配置

feign:
  client:
    config:
      ## default 设置的全局超时时间,指定服务名称可以设置单个服务的超时时间
      default:
        connectTimeout: 1000
        readTimeout: 1000
        logger-level: BASIC
      service-a:
        connectTimeout: 10000
        readTimeout: 10000
        logger-level: FULL
  # Feign Apache HttpClient 配置项,对应 FeignHttpClientProperties 配置属性类
  httpclient:
    enabled: true # 是否开启。默认为 true
    max-connections: 200 # 最大连接数。默认为 200
    max-connections-per-route: 50 # 每个路由的最大连接数。默认为 50。router = host + port
  okhttp:
    enabled: false
  ## 开启压缩
  compression:
    request:
      enabled: true
      ## 开启压缩的阈值,单位字节,默认2048,即是2k,这里为了演示效果设置成1字节
      min-request-size: 1
      mime-types: text/xml,application/xml,application/json
    response:
      enabled: true
  sentinel:
    enabled: true

OpenFeign原理

Feign 是通过给 Java API 接口创建动态代理,从⽽⽣成调⽤远程 HTTP API 接口的实现类。

Feign 是通过和 Ribbon 集成,从⽽实现前者负责 HTTP 接口的声明与调⽤,后者负责服务实例的负载均衡。

Feign的设计

1. 基于⾯向接口的动态代理⽅式⽣成实现类

2. 根据Contract协议规则,解析接⼜类的注解信息,解析成内部表现

3. 基于 RequestBean,动态⽣成Request

4. 使⽤Encoder 将Bean转换成 Http报⽂正⽂(消息解析和转码逻辑)

5. 拦截器负责对请求和返回进⾏装饰处理

6. ⽇志记录

7. 基于重试器发送HTTP请求

8. 发送Http请求

feign相关问题

代理模式?

代理模式是给某⼀个对象提供⼀个代理对象,并由代理对象控制对原对象的引⽤。通俗的来讲代理模式就是我们⽣活中常见的中介。举个例⼦来说明:假如说我现在想买⼀辆⼆⼿车,虽然我可以⾃⼰去找车源,做质量检测等⼀系列的车辆过户流程,但是这确实太 浪费我得时间和精⼒了。我只是想买⼀辆车⽽已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源, 帮我办理车辆过户流程,我只是负责选择⾃⼰喜欢的车,然后付钱就可以了。

动态代理就是,在程序运⾏期,创建⽬标对象的代理对象,并对⽬标对象中的⽅法进⾏功能性增强的⼀种技术。在⽣成代理对象的过程中,⽬标对象不变,代理对象中的⽅法是⽬标对象⽅法的增强⽅法。可以理解为运⾏期间,对象中⽅法的动态拦截,在拦截⽅法的前后执⾏功能操作。代理类在程序运⾏期间,创建的代理对象称 之为动态代理对象。这种情况下,创建的代理对象,并不是事先在Java代码中定义好的。⽽是在运⾏期间,根据我们在动态代理对象中的“指⽰”,动态⽣成的。也就是说,你想获取哪个对象的代理,动态代理就会为你动态的⽣成这个对象的代理对象。动态代理可以对被代理对象的⽅法进⾏功能增强。有了动态代理的技术,那么就可以在不修改⽅法源码的情况下,增强被代理对象的⽅法的功能,在⽅法执⾏前后做任何你想做的事情。 虽然静态代理实现简单,且不侵⼊原代码,但是,当场景稍微复杂⼀些的时候,静态代理的缺点也会暴露出来。

1、 当需要代理多个类的时候,由于代理对象要实现与⽬标对象⼀致的接⼜,有两种⽅式:

- 只维护⼀个代理类,由这个代理类实现多个接⼜,但是这样就导致代理类过于庞⼤

- 新建多个代理类,每个⽬标对象对应⼀个代理类,但是这样会产⽣过多的代理类

2、 当接⼜需要增加、删除、修改⽅法的时候,⽬标对象与代理类都要同时修改,不易维护。 改进静态代理,让代理类动态的⽣成,也就是动态代理。

为什么类可以动态的⽣成?

Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

1. 通过⼀个类的全限定名来获取定义此类的⼆进制字节流

2. 将这个字节流所代表的静态存储结构转化为⽅法区的运⾏时数据结构

3. 在内存中⽣成⼀个代表这个类的 java.lang.Class 对象,作为⽅法区这个类的各种数据访问⼊⼜

由于虚拟机规范对这3点要求并不具体,所以实际的实现是⾮常灵活的,关于第1点,获取类的⼆进制字节流(class字节码)就有很多途径:

  • - 从ZIP包获取,这是JAR、EAR、WAR等格式的基础

  • - 从⽹络中获取,典型的应⽤是 Applet

  • - 运⾏时计算⽣成,这种场景使⽤最多的是动态代理技术,在 java.lang.reflflect.Proxy 类中,就是⽤了 ProxyGenerator.generateProxyClass 来为特定接⼜⽣成形式为$Proxy 的代理类的⼆进制字节流

  • - 由其它⽂件⽣成,典型应⽤是JSP,即由JSP⽂件⽣成对应的Class类

  • - 从数据库中获取等等

所以,动态代理就是想办法,根据接⼜或⽬标对象,计算出代理类的字节码,然后再加载到JVM中使⽤。

为什么要⽤代理模式?

1. 中介隔离作⽤:在某些情况下,⼀个客户类不想或者不能直接引⽤⼀个委托对象,⽽代理类对象可以在客户类和委托对象之间起到中介的作⽤,其特征是代理类和委托类实现相同的接⼜。

2. 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类⽽不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本⾝并不真正实现服务,⽽是同过调⽤委托类的相关⽅法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执⾏的前后加⼊⼀些公共的服务。例如我们想给项⽬加⼊缓存、⽇志这些功能,我们就可以使⽤代理类来完成,⽽没必要打开已经封装好的委托类。

动态代理的两种实现?

1. JDK 动态代理:

  • - 为了解决静态代理中,⽣成⼤量的代理类造成的冗余;

  • - JDK 动态代理只需要实现 InvocationHandler 接⼜,重写 invoke ⽅法便可以完成代理的实现,

  • - jdk的代理是利⽤反射⽣成代理类 Proxyxx.class 代理类字节码,并⽣成对象

  • - jdk动态代理之所以只能代理接⼜是因为代理类本⾝已经extends了Proxy,⽽java是不允许多重继承的,但是允许实现多个接⼜

优点:解决了静态代理中冗余的代理实现类问题。

缺点:JDK 动态代理是基于接⼜设计实现的,如果没有接⼜,会抛异常。

2. CGLIB 代理:

  • - 由于 JDK 动态代理限制了只能基于接⼜设计,⽽对于没有接⼜的情况,JDK⽅式解决不了;

  • - CGLib 采⽤了⾮常底层的字节码技术,其原理是通过字节码技术为⼀个类创建⼦类,并在⼦类中采⽤⽅法拦截的技术拦截所有⽗类⽅法的调⽤,顺势织⼊横切逻辑,来完成动态代理的实现。

  • - 实现⽅式实现 MethodInterceptor 接⼜,重写 intercept ⽅法,通过 Enhancer 类的回调⽅法来实现。

  • - 但是CGLib在创建代理对象时所花费的时间却⽐JDK多得多,所以对于单例的对象,因为⽆需频繁创建对象,⽤CGLib合适,反之,使⽤JDK⽅式要更为合适⼀些。

  • - 同时,由于CGLib由于是采⽤动态创建⼦类的⽅法,对于fifinal⽅法,⽆法进⾏代理。

优点:没有接⼜也能实现动态代理,⽽且采⽤字节码增强技术,性能也不错。

缺点:技术实现相对难理解些。


Comment