5.Feign与ReflectiveFeign

前言

Feign对象作为feign框架的启动门户, 提供构建和运行框架的统一入口, 也是feign框架的核心组件之一

核心逻辑

Feign类结构

java 复制代码
public abstract class Feign {
    public static Builder builder() {
        return new Builder();
    }
  	// 获取方法唯一标识
    public static String configKey(Class targetType, Method method) {...}
    
    // 创建接口实例
    public abstract <T> T newInstance(Target<T> target);
    
    // 建造者模式用来构建feign
    public static class Builder extends BaseBuilder<Builder, Feign> {
        ...
    }
    
    // 解码器
    public static class ResponseMappingDecoder implements Decoder {
        ...
    }
}

Feign提供的方法不算多

  1. 首先是一个获取建造者对象Builder的方法
  2. 提供了一个获取方法唯一标识的方法configKey
  3. 定义了一个抽象方法newInstance, 用于创建接口的实例
  4. Builder作为Feign的静态内部类, 用来真正创建Feign对象, 自然它是少不了可以传入自定义组件个性化我们的框架
  5. ResponseMappingDecoder, 解码器静态代理对象, 没啥用

configKey

java 复制代码
/**
 * 获取方法唯一标识; 格式: 简单类名#方法名(参数名1, 参数名2)
 */
public static String configKey(Class targetType, Method method) {
    // 简单类名
    builder.append(targetType.getSimpleName());
    // 简单类名#方法名(
    builder.append('#').append(method.getName()).append('(');
    // 获取方法参数的泛型类型
    for (Type param : method.getGenericParameterTypes()) {
      // 从当前类中获取参数的类型
      param = Types.resolve(targetType, targetType, param);
      // 参数的原始类型; 例如, List<String> 返回List, 如果不是泛型类型,则返回参数本身
      // 简单类名#方法名(参数名,
      builder.append(Types.getRawType(param).getSimpleName()).append(',');
    }
    // 去掉末尾的逗号
    if (method.getParameterTypes().length > 0) {
      builder.deleteCharAt(builder.length() - 1);
    }
    // 简单类名#方法名(参数名1, 参数名2)
    return builder.append(')').toString();
  }

这个方法比较简单, 但是也需要对泛型有一些了解, 这里是为了返回方法的签名, 格式为: 简单类名#方法名(参数名1, 参数名2), 在打印日志时方便标记。

Builder静态内部类继承了BaseBuilder, 用于设置一些全局变量以及提供构建目标对象的模板方法(一个常用的模板方法模式)

BaseBuilder

BaseBuilder类定义设计

这里我们学习一下这种BaseBuilder抽象类的设计意图

java 复制代码
public static class Builder extends BaseBuilder<Builder, Feign> {
    
}

public abstract class BaseBuilder<B extends BaseBuilder<B, T>, T> implements Cloneable {
    /**
   * 子类实例
   */
  private final B thisB;
}

BaseBuilder设计成了抽象一个父类, 它希望子类以链式调用的方式设置一些属性值所以要借助B thisB

举个例子

java 复制代码
static class Parent {
        
        private String name;

        private Integer age;

        public Parent name(String name) {
            this.name = name;
            return this;
        }
        
        public Parent age(Integer age) {
            this.age = age;
            return this;
        }
    }


    static class Child extends Parent {
        private String gender;
        
        private String address;
        
        public Child gender(String gender) {
            this.gender = gender;
            return this;
        }
        
        public Child address(String address) {
            this.address = address;
            return this;
        }
    }

链式调用

java 复制代码
public static void main(String[] args) {
    Parent parent = new Child();
    ((Child)parent.name("qiao")).gender("male");
}

这里是先调用父类的name方法, 然后需要强转到子类, 再调用子类的gender方法; 那如果我的调用顺序是 parent.name -> child.gender -> parent.age -> child.address呢, 那么调用链将会是如下样子

java 复制代码
((Child)(((Child)parent.name("qiao")).gender("male").age(18))).address("湖北");

当这种互相穿插的越多, 这种强转就会有很多次, 这种调用形式简直惨不忍睹, 下面我们看看用泛型传入当前对象的样子

java 复制代码
static class Parent<Child extends Parent> {

    private Child child;

    public Parent() {
        child = (Child) this;
    }

    private String name;

    private Integer age;

    public Child name(String name) {
        this.name = name;
        return child;
    }

    public Child age(Integer age) {
        this.age = age;
        return child;
    }
}


static class Child extends Parent<Child> {

    private String gender;

    private String address;

    public Child gender(String gender) {
        this.gender = gender;
        return this;
    }

    public Child address(String address) {
        this.address = address;
        return this;
    }
}

链式调用

java 复制代码
public static void main(String[] args) {
    Parent<Child> parent = new Child();
    parent.name("qiao").gender("male").age(18).address("湖北");
}

这样就简单多了

当然我们也可以把下面这段给简化一下, 改成private Child child = (Child) this;即可

java 复制代码
private Child child;

public Parent() {
    child = (Child) this;
}

这种设计在mybatis-plus和netty中也都有体现

java 复制代码
// mybatis-plus
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T>
    implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R> {
    /**
     * 指向子类
     */
    protected final Children typedThis = (Children) this;
}

// netty
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
    // 指向子类
    private B self() {
        return (B) this;
    }
}

组件认识

java 复制代码
public abstract class BaseBuilder<B extends BaseBuilder<B, T>, T> implements Cloneable {
    /**
   * 子类实例
   */
  private final B thisB;
    
    /**
   * 请求拦截器
   */
  protected final List<RequestInterceptor> requestInterceptors =
      new ArrayList<>();
    
  /**
   * 响应结果拦截器
   */
  protected final List<ResponseInterceptor> responseInterceptors = new ArrayList<>();
    
    /**
   * 日志级别
   */
  protected Logger.Level logLevel = Logger.Level.NONE;

  /**
   * 对代理类和其中的方法签名做处理的约定的校验
   */
  protected Contract contract = new Contract.Default();
    
  /**
   * 重试器; 提供请求失败或者处理返回结果错误时的补偿
   */
  protected Retryer retryer = new Retryer.Default();
    
  /**
   * 日志记录器
   */
  protected Logger logger = new NoOpLogger();
    
  /**
   * 编码器; 对参数的编码
   */
  protected Encoder encoder = new Encoder.Default();
    
  /**
   * 解码器; 对返回值的解码
   */
  protected Decoder decoder = new Decoder.Default();
    
    /**
   * 解码响应结果后是否理解关闭流
   */
  protected boolean closeAfterDecode = true;

  /**
   * 是否对void方法返回值做处理
   */
  protected boolean decodeVoid = false;

  /**
   * @QueryMap和@HeaderMap参数的编码器
   */
  protected QueryMapEncoder queryMapEncoder = QueryMap.MapEncoder.FIELD.instance();
  /**
   * 请求异常时, 对错误信息进行解码的解码器
   */
  protected ErrorDecoder errorDecoder = new ErrorDecoder.Default();

  /**
   * 请求头参数
   */
  protected Options options = new Options();

  /**
   * 用来创建jdk代理处理对象InvocationHandler的工厂
   */
  protected InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();

  /**
   * 404异常时, 是否忽略; true:忽略, false:抛出异常
   */
  protected boolean dismiss404;

  /**
   * 重试异常时, 抛出异常类型的策略; NONE:直接抛异常, UNWRAP:抛出原始异常
   */
  protected ExceptionPropagationPolicy propagationPolicy = NONE;

  /**
   * 对当前builder对象中字段进行增强, 允许用户扩展当前Builder类中的配置项
   */
  protected List<Capability> capabilities = new ArrayList<>();
    
  /**
   * 模板方法
   */
  public final T build() {
    // 先对当前build类中的字段进行增强
    return enrich().internalBuild();
  }
}

这里就是feign框架所有的可以扩展的属性和组件了, 这里给分个类

解析接口/方法的参数们

  1. contract; 用来解析接口、方法、参数, 生成方法模板MethodMetadata
  2. encoder, 对参数进行编码
  3. queryMapEncoder, @QueryMap和@HeaderMap参数的编码器

请求时的参数们:

  1. requestInterceptors, 执行请求前可以对RequestTemplate做处理, 修改参数什么的
  2. retryer, 请求失败或者处理响应失败时候补偿的重试器
  3. options, 添加请求头参数

请求响应的参数们:

  1. responseInterceptors, 处理返回结果的拦截器
  2. retryer, 请求失败或者处理响应失败时候补偿的重试器
  3. decoder, 对返回值进行解码
  4. closeAfterDecode, 对响应结果解码后是否立即关闭响应流
  5. decodeVoid, 对返回类型为void方法是否进行解码响应结果
  6. errorDecoder, 请求异常时, 对错误信息进行解码的解码器
  7. dismiss404, 404异常时, 是否忽略; true:忽略, false:抛出异常
  8. propagationPolicy, 重试异常时, 抛出异常类型的策略; NONE:直接抛异常, UNWRAP:抛出原始异常

其它:

invocationHandlerFactory: 用来创建jdk代理处理对象InvocationHandler的工厂

capabilities: 1.对当前build对象中的字段进行增强 2.对处理响应结果的责任链executionChain进行增强, 做一些额外的扩展

logLevel: 日志级别, 用在请求和响应时

logger: 日志工具, 用在请求和响应时

对于模板方法build

  1. 对当前对象中的字段进行增强
  2. 使用抽象方法internalBuild构建目标接口的代理对象

enrich

java 复制代码
/**
   * 对Build对象中的所有字段进行enrich增强
   */
  @SuppressWarnings("unchecked")
  B enrich() {
    if (capabilities.isEmpty()) {
      return thisB;
    }

    try {
      B clone = (B) thisB.clone();

      // 遍历非capabilities字段
      getFieldsToEnrich().forEach(field -> {
        field.setAccessible(true);
        try {
          final Object originalValue = field.get(clone);
          final Object enriched;
          if (originalValue instanceof List) {
            // List<T>中的T 类型或者List中的具体元素类型
            Type ownerType =
                ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
            // 对某个字段增强
            enriched = ((List) originalValue).stream()
                .map(value -> Capability.enrich(value, (Class<?>) ownerType, capabilities))
                .collect(Collectors.toList());
          } else {
            enriched = Capability.enrich(originalValue, field.getType(), capabilities);
          }
          field.set(clone, enriched);
        } catch (IllegalArgumentException | IllegalAccessException e) {
          throw new RuntimeException("Unable to enrich field " + field, e);
        } finally {
          field.setAccessible(false);
        }
      });

      return clone;
    } catch (CloneNotSupportedException e) {
      throw new AssertionError(e);
    }
  }

/**
   * 获取当前类和父类中需要增强的所有字段, 排除capabilities、thisB、动态生成的字段、枚举类型、基本类型字段
   */
  List<Field> getFieldsToEnrich() {
    return Util.allFields(getClass())
        .stream()
        // 排除动态生成的, 例如lambda表达式生成的
        .filter(field -> !field.isSynthetic())
        // and capabilities itself
        .filter(field -> !Objects.equals(field.getName(), "capabilities"))
        // 当前类本身字段也排除
        .filter(field -> !Objects.equals(field.getName(), "thisB"))
        // 基本类型也排除
        .filter(field -> !field.getType().isPrimitive())
        // 枚举类型的字段也排除
        .filter(field -> !field.getType().isEnum())
        .collect(Collectors.toList());
  }

方法小结

  1. getFieldsToEnrich方法获取当前类和父类中所有的字段
  • 排除了Object类
  • 排除了增强字段capabilities和指向子类的thisB字段
  • 排除了基本类型字段
  • 排除了枚举字段
  1. 使用capabilities对list中的每个值进行增强处理, 或者对普通非list单个字段进行增强处理
    • 这里要求定义的Capability中的方法名为enrich, 并且返回值类型是对应增强的字段类型或者List泛型中的参数类型T
  2. Capability接口中默认提供了对一些字段增强的空实现

举个例子

java 复制代码
public class OptionsCapability implements Capability {

    @Override
    public Request.Options enrich(Request.Options options) {
        System.out.println("默认链接超时时长:" + options.connectTimeout());
        return Capability.super.enrich(options);
    }
}

public class InterceptCapability implements Capability {

    @Override
    public RequestInterceptor enrich(RequestInterceptor interceptor) {
        System.out.println("拦截器类名:" + interceptor.getClass().getSimpleName());
        return interceptor;
    }
}
@Test
void capabilityFunc() {
    DemoClient client = Feign.builder()
            .logLevel(feign.Logger.Level.FULL)
            .requestInterceptor(template-> {})
            .addCapability(new InterceptCapability())
            .addCapability(new OptionsCapability())
            .logger(new Slf4jLogger())
            .dismiss404()
            .target(DemoClient.class, "http://localhost:8080");
}

结果如下

java 复制代码
拦截器类名:DemoTest$$Lambda$359/0x00000008010ad200
默认链接超时时长:10

注意: 添加的capabilities, 也用作在了ResponseInterceptor.Chain字段上, 并且是每次调用的时候都做了增强处理

java 复制代码
/**
   * response拦截器组成链条
   */
  protected ResponseInterceptor.Chain responseInterceptorChain() {
    ResponseInterceptor.Chain endOfChain =
        ResponseInterceptor.Chain.DEFAULT;
    ResponseInterceptor.Chain executionChain = this.responseInterceptors.stream()
        .reduce(ResponseInterceptor::andThen)
        .map(interceptor -> interceptor.apply(endOfChain))
        .orElse(endOfChain);
	// 这里对ResponseInterceptor.Chain进行增强
    return (ResponseInterceptor.Chain) Capability.enrich(executionChain,
        ResponseInterceptor.Chain.class, capabilities);
  }

Feign.Builder

类定义

java 复制代码
public static class Builder extends BaseBuilder<Builder, Feign> {
    private Client client = new Client.Default(null, null);
    
    // 构建接口代理对象的入口; apiType:目标接口, url:请求目标地址
    public <T> T target(Class<T> apiType, String url) {
      return target(new HardCodedTarget<>(apiType, url));
    }
    
    public <T> T target(Target<T> target) {
      // 对build对象中的字段进行增强(如果有), 再执行internalBuild方法, 再执行Feign#newInstance
      return build().newInstance(target);
    }
    
    @Override
    public Feign internalBuild() {
      final ResponseHandler responseHandler =
          new ResponseHandler(logLevel, logger, decoder, errorDecoder,
              dismiss404, closeAfterDecode, decodeVoid, responseInterceptorChain());
      // 构建方法处理器的工厂; 用来创建创建对外调用的方法
      MethodHandler.Factory<Object> methodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors,
              responseHandler, logger, logLevel, propagationPolicy,
              new RequestTemplateFactoryResolver(encoder, queryMapEncoder),
              options);
      // invocationHandlerFactory默认是 InvocationHandlerFactory.Default
      // contract:用来生成方法模版 methodHandlerFactory:用来生成真正调用的方法对象 invocationHandlerFactory:用来调用生成的方法
      return new ReflectiveFeign<>(contract, methodHandlerFactory, invocationHandlerFactory,
          () -> null);
    }
  }
}

方法小结

  1. 定义了一个默认的全局变量Client, 它是通过HttpURLConnection来实现Http请求的对象
  2. 定义了两个target方法, 提供了创建目标接口代理对象的方法; 其中HardCodedTarget在之前的文章中有聊到过, 这里就不解释了
  3. 最后是internalBuild方法, 它是实现的父类BaseBuilder的抽象方法, 就这一个方法, 就把整个feign框架的组件都画出来了, 主要是构建了ReflectiveFeign对象, 然后它依赖了三个核心组件
    • Contract: 用来解析目标接口生成方法模板
    • SynchronousMethodHandler.Factory: 用来构建同步请求句柄
    • InvocationHandlerFactory: 用来创建feign接口动态代理回调对象的工厂(feign使用的是jdk动态代理,代理方法是InvocationHandler#invoke)

ReflectiveFeign

看名字就是跟发射有关的对象

看下类定义

java 复制代码
public class ReflectiveFeign<C> extends Feign {

    private final ParseHandlersByName<C> targetToHandlersByName;
    
    // 这里是InvocationHandlerFactory
    private final InvocationHandlerFactory factory;
    private final AsyncContextSupplier<C> defaultContextSupplier;
  
   /**
   * 创建目标接口的代理对象
   */
    public <T> T newInstance(Target<T> target, C requestContext) {...}
    
   /**
   * jdk动态代理的方法代理句柄
   */
    static class FeignInvocationHandler implements InvocationHandler {...}
    
   /**
   * 根据名称解析成Handler的对象
   */
    private static final class ParseHandlersByName<C> {...}
    
   /**
   * target目标校验器
   */
    private static class TargetSpecificationVerifier {...}
}

看了这个类的整个结构比较清晰, 就是用来校验、创建动态代理, 下面看具体实现

java 复制代码
/**
 * 创建目标接口的代理对象
*/
public <T> T newInstance(Target<T> target, C requestContext) {
    // 1.校验target类
    TargetSpecificationVerifier.verify(target);

    // 2.创建方法句柄, 并将方法和方法句柄映射起来
    Map<Method, MethodHandler> methodToHandler =
        targetToHandlersByName.apply(target, requestContext);
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 3. 创建jdk动态代理的方法代理句柄
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    // 4. 处理默认方法
    for (MethodHandler methodHandler : methodToHandler.values()) {
      if (methodHandler instanceof DefaultMethodHandler) {
        ((DefaultMethodHandler) methodHandler).bindTo(proxy);
      }
    }
    return proxy;
  }

方法小结

  1. 校验target目标类

    • 目标对象必须是一个接口, 也就是定义feign请求定义的类必须是一个接口
    • 如果方法返回值是CompletableFuture类型, 返回的CompletableFuture上必须有泛型参数, 且泛型参数不能是通配符类型, 也就是?, 例如 CompletableFuture<Student>是可以的, 但是``CompletableFuture<?>`类型不允许
  2. 创建方法的方法句柄, 并将方法和方法句柄映射起来;

    这里方法句柄是feign自定义的MethodHandler, 而jdk默认的是 java.lang.invoke.MethodHandle

  3. 创建jdk动态代理对象

  4. 处理默认方法(接口的中default修饰的方法)

关于接口中的default方法, 感兴趣的同学可以参考我的这篇文章接口中的default和static方法

FeignInvocationHandler

用来创建jdk动态代理的方法代理句柄的工厂, 实现了java.lang.reflect.InvocationHandler接口

BaseBuilder中的invocationHandlerFactory创建

java 复制代码
/**
 * 用来创建jdk代理处理对象InvocationHandler的工厂
 */
protected InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();

public interface InvocationHandlerFactory {
    static final class Default implements InvocationHandlerFactory {

    @Override
    public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
      return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
    }
  }
}

核心方法

java 复制代码
@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          // 如果是equals方法, 并且第一个参数不为null, 那么获取这个参数的代理方法句柄; 如果参数args[0]不是代理对象, 那么会抛异常
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          // args[0]不是代理对象直接返回false
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      } else if (!dispatch.containsKey(method)) {
        throw new UnsupportedOperationException(
            String.format("Method \"%s\" should not be called", method.getName()));
      }
	  // 调用方法句柄
      return dispatch.get(method).invoke(args);
    }

方法小结

  1. 如果接口中定义了equals方法, 如果第一个参数是代理类, 那么返回该代理的java.lang.reflect.InvocationHandler对象, 并且该InvocationHandlerFeignInvocationHandler类型, 且对象中的target属性和当前FeignInvocationHandler中的target属性相等才返回true (不太好理解, 尽量不要这么干就行了)
  2. 支持hashCode和toString方法
  3. 从解析的方法与方法句柄映射中获取方法句柄来执行

ParseHandlersByName

关于ParseHandlersByName 类, 它包装了ContractMethodHandler.Factory, 用来解析接口、方法生成MethodMetadata,并根据MethodMetadata生成方法句柄MethodHandler, 同时它也支持对default方法的处理

java 复制代码
public Map<Method, MethodHandler> apply(Target target, C requestContext) {
      final Map<Method, MethodHandler> result = new LinkedHashMap<>();

      // 校验target并获取类中方法的metadata
      final List<MethodMetadata> metadataList = contract.parseAndValidateMetadata(target.type());
      for (MethodMetadata md : metadataList) {
        final Method method = md.method();
        // 忽略Object类中的方法
        if (method.getDeclaringClass() == Object.class) {
          continue;
        }
        // 创建方法句柄
        final MethodHandler handler = createMethodHandler(target, md, requestContext);
        result.put(method, handler);
      }

      // 提供对default方法的支持
      for (Method method : target.type().getMethods()) {
        if (Util.isDefault(method)) {
          final MethodHandler handler = new DefaultMethodHandler(method);
          result.put(method, handler);
        }
      }

      return result;
    }

    private MethodHandler createMethodHandler(final Target<?> target,
                                              final MethodMetadata md,
                                              final C requestContext) {
      // 忽略方法
      if (md.isIgnored()) {
        return args -> {
          throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
        };
      }
      // 创建方法句柄
      return factory.create(target, md, requestContext);
    }
  }

总结

  1. Feign类作为feign框架的门户类, 提供了所有的框架可以自定义组件的设置入口
  2. Feign类提供了target方法用来构建feign接口的代理对象
  3. 其中的Builder静态内部类使用了父子泛型的方式供使用者可以以简单链式写法构建参数
  4. Builder提供了capabilities增强器允许使用者对框架内的字段进行增强(排除了Object类,capabilities字段,thisB,基本类型字段,枚举类型字段)
  5. 同时也允许我们对ResponseInterceptor.Chain字段进行增强
  6. ReflectiveFeign对象提供了将方法解析成方法句柄的功能, 并通过jdk动态代理将目标接口生成代理对象
  7. 限制了代理对象必须是接口, 并且当方法的返回值是CompletableFuture类型的时候, 必须指定其泛型类型(不能是通配符?类型)
  8. 支持接口中的方法是default方法, 也支持了hashCode, toString方法, 但是对于equals方法, 要求equals方法的第一个参数是jdk代理对象
相关推荐
字节流动17 分钟前
Android Java 版本的 MSAA OpenGL ES 多重采样
android·java·opengles
呜呼~225141 小时前
前后端数据交互
java·vue.js·spring boot·前端框架·intellij-idea·交互·css3
飞的肖1 小时前
从测试服务器手动热部署到生产环境的实现
java·服务器·系统架构
周伯通*2 小时前
策略模式以及优化
java·前端·策略模式
两点王爷2 小时前
Java读取csv文件内容,保存到sqlite数据库中
java·数据库·sqlite·csv
问道飞鱼2 小时前
【Springboot知识】Springboot进阶-实现CAS完整流程
java·spring boot·后端·cas
抓哇小菜鸡2 小时前
WebSocket
java·websocket
single5942 小时前
【c++笔试强训】(第四十五篇)
java·开发语言·数据结构·c++·算法
Q_19284999062 小时前
基于Spring Boot的电影网站系统
java·spring boot·后端
老鑫安全培训3 小时前
从安全角度看 SEH 和 VEH
java·网络·安全·网络安全·系统安全·安全威胁分析