Feign 원리 해석 (1) Feign 호출 대상의 생성

34125 단어 Feign 원리 해석
  • Feign.Builder 체인 호출
  •   public static class Builder {
    
        private final List<RequestInterceptor> requestInterceptors =
            new ArrayList<RequestInterceptor>();
        private Logger.Level logLevel = Logger.Level.NONE;
        private Contract contract = new Contract.Default();
        private Client client = new Client.Default(null, null);
        private Retryer retryer = new Retryer.Default();
        private Logger logger = new NoOpLogger();
        private Encoder encoder = new Encoder.Default();
        private Decoder decoder = new Decoder.Default();
        private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
        private Options options = new Options();
        private InvocationHandlerFactory invocationHandlerFactory =
            new InvocationHandlerFactory.Default();
        private boolean decode404;
    
        public Builder logLevel(Logger.Level logLevel) {
          this.logLevel = logLevel;
          return this;
        }
    
        public Builder contract(Contract contract) {
          this.contract = contract;
          return this;
        }
    
        public Builder client(Client client) {
          this.client = client;
          return this;
        }
    
        public Builder retryer(Retryer retryer) {
          this.retryer = retryer;
          return this;
        }
    
        public Builder logger(Logger logger) {
          this.logger = logger;
          return this;
        }
    
        public Builder encoder(Encoder encoder) {
          this.encoder = encoder;
          return this;
        }
    
        public Builder decoder(Decoder decoder) {
          this.decoder = decoder;
          return this;
        }
    
        /**
         * Allows to map the response before passing it to the decoder.
         */
        public Builder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
          this.decoder = new ResponseMappingDecoder(mapper, decoder);
          return this;
        }
    
        /**
         * This flag indicates that the {@link #decoder(Decoder) decoder} should process responses with
         * 404 status, specifically returning null or empty instead of throwing {@link FeignException}.
         *
         *  All first-party (ex gson) decoders return well-known empty values defined by {@link
         * Util#emptyValueOf}. To customize further, wrap an existing {@link #decoder(Decoder) decoder}
         * or make your own.
         *
         *  This flag only works with 404, as opposed to all or arbitrary status codes. This was an
         * explicit decision: 404 -> empty is safe, common and doesn't complicate redirection, retry or
         * fallback policy. If your server returns a different status for not-found, correct via a
         * custom {@link #client(Client) client}.
         *
         * @since 8.12
         */
        public Builder decode404() {
          this.decode404 = true;
          return this;
        }
    
        public Builder errorDecoder(ErrorDecoder errorDecoder) {
          this.errorDecoder = errorDecoder;
          return this;
        }
    
        public Builder options(Options options) {
          this.options = options;
          return this;
        }
    
        /**
         * Adds a single request interceptor to the builder.
         */
        public Builder requestInterceptor(RequestInterceptor requestInterceptor) {
          this.requestInterceptors.add(requestInterceptor);
          return this;
        }
    
        /**
         * Sets the full set of request interceptors for the builder, overwriting any previous
         * interceptors.
         */
        public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
          this.requestInterceptors.clear();
          for (RequestInterceptor requestInterceptor : requestInterceptors) {
            this.requestInterceptors.add(requestInterceptor);
          }
          return this;
        }
    
        /**
         * Allows you to override how reflective dispatch works inside of Feign.
         */
        public Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
          this.invocationHandlerFactory = invocationHandlerFactory;
          return this;
        }
    
        public <T> T target(Class<T> apiType, String url) {
          return target(new HardCodedTarget<T>(apiType, url));
        }
    
        public <T> T target(Target<T> target) {
          return build().newInstance(target);
        }
    
        public Feign build() {
          SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
              new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                                   logLevel, decode404);
          ParseHandlersByName handlersByName =
              new ParseHandlersByName(contract, options, encoder, decoder,
                                      errorDecoder, synchronousMethodHandlerFactory);
          return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
        }
      }
    

    builder를 통해 Feign 호출 대상을 만들 때 로그 단계, 재시도 횟수, 인코딩 클래스 등을 설정하고 target () 방법으로 Feign 인터페이스 대상을 만들 수 있습니다. 다음은 Feign이 제공하는 Target 인터페이스를 볼 수 있습니다.
    public interface Target<T> {
    
      /* The type of the interface this target applies to. ex. {@code Route53}. */
      Class<T> type();
    
      /* configuration key associated with this target. For example, {@code route53}. */
      String name();
    
      /* base HTTP URL of the target. For example, {@code https://api/v2}. */
      String url();
    
      /**
       * Targets a template to this target, adding the {@link #url() base url} and any target-specific
       * headers or query parameters. 

    For example:
    *
    
       * public Request apply(RequestTemplate input) {
       *     input.insert(0, url());
       *     input.replaceHeader("X-Auth", currentToken);
       *     return input.asRequest();
       * }
       * 
    * relationship to JAXRS 2.0 This call is similar to {@code
    * javax.ws.rs.client.WebTarget.request()}, except that we expect transient, but necessary
    * decoration to be applied on invocation.
    */
    public Request apply(RequestTemplate input);
    public static class HardCodedTarget implements Target {
    private final Class type;
    private final String name;
    private final String url;
    public HardCodedTarget(Class type, String url) {
    this(type, url, url);
    }
    public HardCodedTarget(Class type, String name, String url) {
    this.type = checkNotNull(type, "type");
    this.name = checkNotNull(emptyToNull(name), "name");
    this.url = checkNotNull(emptyToNull(url), "url");
    }
    @Override
    public Class type() {
    return type;
    }
    @Override
    public String name() {
    return name;
    }
    @Override
    public String url() {
    return url;
    }
    /* no authentication or other special activity. just insert the url. */
    @Override
    public Request apply(RequestTemplate input) {
    if (input.url().indexOf("http") != 0) {
    input.insert(0, url());
    }
    return input.request();
    }
    @Override
    public boolean equals(Object obj) {
    if (obj instanceof HardCodedTarget) {
    HardCodedTarget other = (HardCodedTarget) obj;
    return type.equals(other.type)
    && name.equals(other.name)
    && url.equals(other.url);
    }
    return false;
    }
    @Override
    public int hashCode() {
    int result = 17;
    result = 31 * result + type.hashCode();
    result = 31 * result + name.hashCode();
    result = 31 * result + url.hashCode();
    return result;
    }
    @Override
    public String toString() {
    if (name.equals(url)) {
    return "HardCodedTarget(type="+ type.getSimpleName() + ", url="+ url + ")";
    }
    return "HardCodedTarget(type="+ type.getSimpleName() + ", name="+ name + ", url="+ url
    + ")";
    }
    }
    public static final class EmptyTarget implements Target {
    private final Class type;
    private final String name;
    EmptyTarget(Class type, String name) {
    this.type = checkNotNull(type, "type");
    this.name = checkNotNull(emptyToNull(name), "name");
    }
    public static EmptyTarget create(Class type) {
    return new EmptyTarget(type, "empty:"+ type.getSimpleName());
    }
    public static EmptyTarget create(Class type, String name) {
    return new EmptyTarget(type, name);
    }
    @Override
    public Class type() {
    return type;
    }
    @Override
    public String name() {
    return name;
    }
    @Override
    public String url() {
    throw new UnsupportedOperationException("Empty targets don't have URLs");
    }
    @Override
    public Request apply(RequestTemplate input) {
    if (input.url().indexOf("http") != 0) {
    throw new UnsupportedOperationException(
    "Request with non-absolute URL not supported with empty target");
    }
    return input.request();
    }
    @Override
    public boolean equals(Object obj) {
    if (obj instanceof EmptyTarget) {
    EmptyTarget other = (EmptyTarget) obj;
    return type.equals(other.type)
    && name.equals(other.name);
    }
    return false;
    }
    @Override
    public int hashCode() {
    int result = 17;
    result = 31 * result + type.hashCode();
    result = 31 * result + name.hashCode();
    return result;
    }
    @Override
    public String toString() {
    if (name.equals("empty:"+ type.getSimpleName())) {
    return "EmptyTarget(type="+ type.getSimpleName() + ")";
    }
    return "EmptyTarget(type="+ type.getSimpleName() + ", name="+ name + ")";
    }
    }
    }
    세 가지 인터페이스 방법이 있는데 type()는 Feign 호출 대상 인터페이스의class 대상이고name()는Target 대상의 유일한 표식이며 url()는 http 호출을 시작하는base url이다.Feign은 두 가지 기본 구현을 제공했다. 하나는 url 하드 인코딩이고 하나는 url이 없는 빈 Target이다. 우리가 평소에 사용하는 것은 보통HardCoded Target 즉 하드 인코딩이다.build () 방법으로 Reflective Feign 대상을 만들었습니다. 다음은 Reflective Feign 대상의 newInstance (Target target) 방법을 보십시오.
      public <T> T newInstance(Target<T> target) {
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    
        for (Method method : target.type().getMethods()) {
          if (method.getDeclaringClass() == Object.class) {
            continue;
          } else if(Util.isDefault(method)) {
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
          } else {
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
          }
        }
        InvocationHandler handler = factory.create(target, methodToHandler);
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
    
        for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
          defaultMethodHandler.bindTo(proxy);
        }
        return proxy;
      }
    

    다음과 같은 작업을 수행했습니다.
  • 1.Feign 호출 인터페이스에서 해결 방법
  • 2.인터페이스를 만드는 프록시 대상 (jdk 동적 프록시)
  • 3.기본 방법이 있으면 이 프록시 대상에도 연결합니다
  • 생성된 proxy 대상은 Feign이 http 호출을 하는 실제 대상입니다

    좋은 웹페이지 즐겨찾기