Feign 의 실현 원 리 를 상세히 해석 하 다.

16027 단어 FeignHTTP
Feign
Feign 은 HTTP 가 요청 한 경량급 클 라 이언 트 프레임 워 크 입 니 다.인터페이스+주석 을 통 해 HTTP 요청 호출 을 시작 합 니 다.자바 에서 HTTP 요청 메 시 지 를 봉인 하 는 방식 으로 직접 호출 하 는 것 이 아 닙 니 다.서비스 소비 자 는 서비스 제공 자의 연결 을 받 은 다음 에 스토리 보드 로 컬 연결 을 옮 기 고 법 적 으로 스토리 보드 를 옮 기 며 실제 적 으로 원 격 요 구 를 보 냈 다.보다 편리 하고 우아 하 게 HTTP 기반 API 를 옮 겨 서 Spring Cloud 에서 해결 할 수 있 습 니 다.오픈 소스 프로젝트 주소:Feign공식 설명 은 다음 과 같 습 니 다.
Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.
Feign
Feign 의 최 우선 목 표 는 HTTP 호출 의 복잡성 을 줄 이 는 것 이다.마이크로 서비스 가 호출 되 는 장면 에서 우 리 는 HTTP 프로 토 콜 을 기반 으로 하 는 서 비 스 를 많이 호출 합 니 다.만약 에 서비스 호출 이 HTTP 호출 서 비 스 를 제공 하 는 HTTP Client 프레임 워 크(예 를 들 어 Apache HttpComponnets,HttpURLConnection OkHttp 등)만 사용한다 면 우 리 는 어떤 문제 에 관심 을 가 져 야 합 니까?

이러한 HTTP 요청 프레임 워 크 에 비해 Feign 은 HTTP 요청 호출 프로 세 스 를 패키지 하고 사용자 에 게 인터페이스 프로 그래 밍 을 위 한 습관 을 강요 합 니 다(Feign 자체 가 인 터 페 이 스 를 위 한 것 이기 때 문 입 니 다).
실례
3.1 원생 사용 방식
Feign 의 GitHub 오픈 소스 프로젝트 를 가 져 오 는 Contributors 를 예 로 들 면,네 이 티 브 방식 으로 Feign 을 사용 하 는 절 차 는 다음 과 같다.(Gradle 을 사용 하여 의존 관 리 를 하 는 프로젝트 를 예 로 들 면)
첫 번 째 단계:관련 의존 도입:implementation'io.github.openfeign:feign-core:11.0'
프로젝트 의 build.gradle 파일 의존 성명 에 dependencies 에서 이 의존 성명 을 추가 하면 됩 니 다.
두 번 째 단계:HTTP 요청 인터페이스 설명
자바 의 인터페이스 와 Feign 의 네 이 티 브 주석@RequestLine 을 사용 하여 HTTP 요청 인 터 페 이 스 를 설명 합 니 다.여기 서 Feign 이 사용자 에 게 HTTP 호출 디 테 일 을 봉 하여 HTTP 호출 의 복잡성 을 크게 줄 이 고 인 터 페 이 스 를 정의 하면 됩 니 다.

세 번 째 단계:Feign 클 라 이언 트 초기 화 설정
마지막 설정 은 클 라 이언 트 를 초기 화 합 니 다.이 단 계 는 요청 주소,인 코딩(Encoder),디 코딩(Decoder)등 을 설정 합 니 다.

인 터 페 이 스 를 정의 하고 주해 방식 으로 인터페이스의 정 보 를 설명 하면 인터페이스 호출 을 시작 할 수 있다.마지막 요청 결 과 는 다음 과 같 습 니 다.

3.2 스프링 클 라 우 드 사용 방식 결합
마찬가지 로 Feign 의 GitHub 오픈 소스 프로젝트 를 가 져 온 Contributors 를 예 로 들 면 Spring Cloud 와 결합 한 사용 방식 은 다음 과 같다.
첫 번 째 단계:관련 starter 의존 도입:org.springframework.cloud:spring-cloud-starter-openfeign
프로젝트 의 build.gradle 파일 의존 성명 에 dependencies 에서 이 의존 성명 을 추가 하면 됩 니 다.
두 번 째 단계:프로젝트 의 시작 클래스 XXXApplication 에@EnableFeignClient 주 해 를 추가 하여 Feign 클 라 이언 트 기능 을 사용 합 니 다.

세 번 째 단계:HTTP 호출 인 터 페 이 스 를 만 들 고 성명@FeignClient 주 해 를 추가 합 니 다.
마지막 설정 은 클 라 이언 트 를 초기 화 합 니 다.이 단 계 는 요청 주소(url),인 코딩(Encoder),디 코딩(Decoder)등 을 설정 합 니 다.원래 사용 방식 과 달리 현재 저 희 는@FeignClient 를 통 해 설정 한 Feign 클 라 이언 트 속성 을 설명 하고 요청 한 URL 도 Spring MVC 에서 제공 하 는 설명 입 니 다.

테스트 클래스 는 다음 과 같다.

실행 결 과 는 다음 과 같 습 니 다.

@Autowired 를 통 해 방금 정 의 된 인 터 페 이 스 를 주입 한 다음 에 직접 사용 하여 HTTP 요청 을 할 수 있 습 니 다.사용 이 편리 하고 간결 하지 않 습 니까?
4.탐색 Feign
위의 첫 번 째 원생 이 사용 한 예 를 보면 인 터 페 이 스 는 구체 적 인 실현 유형 이 없 지만 테스트 유형 에서 인 터 페 이 스 를 직접 호출 하 는 방법 으로 인터페이스의 호출 을 완성 할 수 있다.우 리 는 자바 에서 인 터 페 이 스 를 직접 사용 할 수 없다 는 것 을 알 고 있다.이 는 Feign 이 뒤에서 인 터 페 이 스 를 묵묵히 생 성 한 대리 실현 유형 이 라 고 대담 하 게 추측 할 수 있 기 때문이다.방금 테스트 클래스 debug 에서 인터페이스 가 실제 사용 하 는 실현 클래스 가 무엇 인지 확인 할 수 있 습 니 다.

debug 결과 에서 알 수 있 듯 이 프레임 워 크 는 인터페이스의 프 록 시 구현 클래스 HardCodedTarget 의 대상$Proxy 14 를 생 성하 여 인터페이스 요청 호출 을 완 료 했 습 니 다.방금 추측 과 일치 합 니 다.Feign 은 주로 HTTP 요청 호출 을 봉 인 했 습 니 다.전체 구 조 는 다음 과 같 습 니 다.

테스트 클래스 코드 는 GitHub github=Feign.builder().target(GitHub.class,"https://api.github.com")에 만 있 습 니 다.Feign 프레임 워 크 의 기능 을 사 용 했 기 때문에 우 리 는 여기 서 소스 코드 를 깊이 들 어가 서 클릭 하면 Feign 추상 류 가 제공 하 는 방법 임 을 알 수 있 습 니 다.마찬가지 로 우 리 는 추상 류 도 초기 화 할 수 없다 는 것 을 알 기 때문에 서브 클래스 가 있 을 것 입 니 다.만약 에 위의 debug 코드 를 자세히 살 펴 보면 Reflective Feign 류 가 있 습 니 다.이 종 류 는 추상 류 Feign 의 하위 클래스 입 니 다.추상 적 인 feign.Feign 의 일부 소스 코드 는 다음 과 같 습 니 다.

public abstract class Feign {
    
  ...  

  public static Builder builder() {
    return new Builder();
  }

  public abstract <T> T newInstance(Target<T> target);

  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 QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
    private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
    private Options options = new Options();
    private InvocationHandlerFactory invocationHandlerFactory =
        new InvocationHandlerFactory.Default();
    private boolean decode404;
    private boolean closeAfterDecode = true;
    private ExceptionPropagationPolicy propagationPolicy = NONE;
    private boolean forceDecoding = false;
    private List<Capability> capabilities = new ArrayList<>();

    //           
    public Builder logLevel(Logger.Level logLevel) {
      this.logLevel = logLevel;
      return this;
    }

    //            (  ) 
    public Builder contract(Contract contract) {
      this.contract = contract;
      return this;
    }

    //       Client(     JDK   HttpURLConnection)
    public Builder client(Client client) {
      this.client = client;
      return this;
    }

    //      
    public Builder retryer(Retryer retryer) {
      this.retryer = retryer;
      return this;
    }

    //         
    public Builder encoder(Encoder encoder) {
      this.encoder = encoder;
      return this;
    }

    //        
    public Builder decoder(Decoder decoder) {
      this.decoder = decoder;
      return this;
    }

    //    404        
    public Builder decode404() {
      this.decode404 = true;
      return this;
    }

    //        
    public Builder errorDecoder(ErrorDecoder errorDecoder) {
      this.errorDecoder = errorDecoder;
      return this;
    }

    //        
    public Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
      this.requestInterceptors.clear();
      for (RequestInterceptor requestInterceptor : requestInterceptors) {
        this.requestInterceptors.add(requestInterceptor);
      }
      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 T target(Class api Type,String url)에서 HardCodedTarget 대상 을 직접 만 든 것 을 볼 수 있 습 니 다.이 대상 도 위 debug 에서 보 이 는 대상 입 니 다.더 깊이 들 어가 면 feign.Feign 의 new Instance(Target target)방법 에 왔 습 니 다.추상 적 인 방법 입 니 다.사실은 현재 하위 클래스 Reflective Feign 에서 이 방법 은 인터페이스 에이전트 가 생 성 된 곳 입 니 다.다음은 소스 코드 를 통 해 논 리 를 실현 하 는 것 이 어떤 지 보 겠 습 니 다.

public class ReflectiveFeign extends Feign {

  ...  

  private final ParseHandlersByName targetToHandlersByName;
  private final InvocationHandlerFactory factory;
  private final QueryMapEncoder queryMapEncoder;

  ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
      QueryMapEncoder queryMapEncoder) {
    this.targetToHandlersByName = targetToHandlersByName;
    this.factory = factory;
    this.queryMapEncoder = queryMapEncoder;
  }

  @SuppressWarnings("unchecked")
  @Override
  public <T> T newInstance(Target<T> target) {
    // <  #    , MethodHandler>,key     feign.Feign.configKey(Class targetType, Method method)    
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    //   Map<String, MethodHandler>      Map<Method, MethodHandler>     
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    //        
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      //    Object         
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        //     (         )            
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        //          (e.g. GitHub.listContributors(String, String))  
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }

    //    Feign     InvocationHandler
    InvocationHandler handler = factory.create(target, methodToHandler);
    //    JDK             (e.g. Github   )
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);

    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

...

}
전체 절 차 는 방법 T new Instance(Target target)에서 FeignInvocation Handler 를 포함 하 는 대리 대상 을 생 성 하 는 것 입 니 다.FeignInvocation Handler 대상 은 맵map 를 가지 고 있 습 니 다.대리 대상 이 호출 될 때 FeignInvocation Handler\#invoke 방법 에 들 어가 호출 방법 에 따라 대응 하 는 MethodHandler 를 얻 습 니 다.그리고 MethodHandler 에서 방법 처리(HTTP 요청 처리 등)를 완료 합 니 다.
다음은 MethodHandler 에 깊이 들 어가 방법 HTTP 요청 처 리 를 어떻게 완성 하 는 지 살 펴 보 겠 습 니 다.MethodHandler 는 하나의 인터페이스 가 feign.Invocation Handler Factory 인터페이스 에 정의 되 어 있 습 니 다(P.S.기초 지식 점,인 터 페 이 스 는 내부 인터페이스 에서 정의 할 수 있 습 니 다).두 가지 실현 유형 은 Default MethodHandler 와 Synchronous MethodHandler 입 니 다.첫 번 째 Default MethodHandler 는 인 터 페 이 스 를 처리 하 는 기본 적 인 방법 이 고,두 번 째 는 정상 적 인 인터페이스 방법 을 처리 하 는 것 이 며,일반적인 상황 에 서 는 이러한 종류 로 처리 된다.

final class SynchronousMethodHandler implements MethodHandler {

  ...

  @Override
  public Object invoke(Object[] argv) throws Throwable {
    //    RequestTemplate               
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    //      
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        //             
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          //                
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    //       RequestTemplate          Request  
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
      //    client(Apache HttpComponnets、HttpURLConnection OkHttp  )   HTTP     ,    HttpURLConnection 
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    if (decoder != null)
      //            
      return decoder.decode(response, metadata.returnType());

    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);

    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");

      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  }

...

}
이로써 Feign 의 핵심 실현 프로 세 스 소개 가 완료 되 었 습 니 다.코드 를 보면 feign.synchronousMethodHandler 의 조작 은 상대 적 으로 간단 합 니 다.주로 client 를 통 해 요청 을 완성 하고 응답 에 대한 디 코딩 과 이상 처리 작업 을 합 니 다.전체 절 차 는 다음 과 같 습 니 다.

총화
Feign 은 우리 에 게 정 의 된 대상 인터페이스(예 를 들 어 GitHub)를 통 해 HardCodedTarget 형식의 프 록 시 대상 을 만 들 고 JDK 동적 프 록 시 로 이 루어 집 니 다.프 록 시 를 생 성 할 때 주석 에 따라 해당 하 는 맵를 생 성 합 니 다.이 맵 은 Invocation Handler 가 가지 고 있 고 인터페이스 방법 이 호출 될 때,Invocation Handler 에 들 어 가 는 invoke 방법.JDK 동적 에이전트 의 기초 지식).
그 다음 에 호출 방법 에 따라 맵에서 해당 하 는 MethodHandler 를 가 져 온 다음 에 MethodHandler 를 통 해 지정 한 client 에 따라 대응 처 리 를 완성 합 니 다.MethodHandler 의 실현 클래스 Default MethodHandler 는 기본 방법(인터페이스의 기본 방법)의 요청 처 리 를 처리 합 니 다.SynchronousMethodHandler 구현 클래스 는 다른 방법 을 완성 하 는 HTTP 요청 의 실현 입 니 다.이것 이 Feign 의 주요 핵심 프로 세 스 입 니 다.페 인 프레임 워 크 구현 의 핵심 프로 세 스 소개 입 니 다.
이상 은 Feign 의 실현 원리 에 대한 상세 한 내용 입 니 다.더 많은 Feign 원리 에 관 한 자 료 는 저희 의 다른 관련 글 을 주목 해 주 십시오!

좋은 웹페이지 즐겨찾기