Spring Cloud Fegin 상세 설명 (1)

104221 단어 SpringCloud
1). Fegin 의 기본 기능
FeginClient 주해 @ Target (Element Type. TYPE) 수식 은 FeginClient 주해 의 역할 목표 가 인터페이스 에 있 음 을 나타 낸다.FeginClient 주석 에 대응 하 는 속성:
  • name: FeginClient 의 이름 을 지정 합 니 다. 프로젝트 가 Ribbon 을 사용 하면 name 속성 은 마이크로 서비스의 이름 으로 서비스 발견 에 사 용 됩 니 다.
  • url: url 은 일반적으로 디 버 깅 에 사 용 됩 니 다. @ FeginClient 호출 주소
  • 를 수 동 으로 지정 할 수 있 습 니 다.
  • decode 404: 404 오류 가 발생 했 을 때 decoder 디 코딩 을 호출 하지 않 으 면 FeginException
  • 을 던 집 니 다.
  • configuration: Fegin 설정 클래스, Fegin 의 인 코더, Decoder, LogLevel, Contract
  • 를 사용자 정의 할 수 있 습 니 다.
  • fallback: 잘못 사용 한 처리 클래스 를 정의 합 니 다. 원 격 인터페이스 호출 에 실패 하거나 시간 이 초과 되면 해당 인터페이스 의 잘못 사용 논 리 를 되 돌려 사용 합 니 다. fallback 이 지정 한 클래스 는 @ FeginClient 표지 의 인 터 페 이 스 를 실현 해 야 합 니 다.
  • fallback Factory: 공장 류 는 fallback 인 스 턴 스 를 생 성 하 는 데 사 용 됩 니 다. 이 속성 을 통 해 우 리 는 모든 인터페이스 에서 통용 되 는 잘못 사용 논 리 를 실현 하고 코드 의 번 거 로 움 을 줄 일 수 있 습 니 다
  • path: 현재 FeginClient 의 통 일 된 접 두 사 를 정의 합 니 다.
  • if (!StringUtils.hasText(this.url)) {
       String url;
       if (!this.name.startsWith("http")) {
          url = "http://" + this.name;
       }
       else {
          url = this.name;
       }
       url += cleanPath();
       return loadBalance(builder, context, new HardCodedTarget<>(this.type,
             this.name, url));
    }
    

    Spring Cloud Fegin 은 요청 과 응답 을 GZIP 압축 하여 통신 효율 을 향상 시 키 는 것 을 지원 합 니 다.압축 설정:
    fegin:
      compression:
        request:
          enabled: true
          mime-types: text/xml,application/xml,application/json
          min-request-size: 2048
        response:
          enabled: true
    

    주의해 야 할 것 은 GZIP 압축 을 시작 한 후 Fegin 간 호출 은 바 이 너 리 프로 토 콜 을 통 해 전송 되 기 때문에 반환 값 은 ResponseEntity 로 수정 해 야 정상적으로 표시 할 수 있 습 니 다.
    다른 설정 과 기본 기능 은 여기 서 소개 하지 않 겠 습 니 다.
    2). Spring Cloud Fegin 작업 원리
    우리 의 접점 은 Spring 시작 클래스 입 니 다. 그리고 Feign 을 사용 할 때 시작 클래스 에 @ Enable Feign Client 주 해 를 추가 합 니 다.
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(FeignClientsRegistrar.class)
    public @interface EnableFeignClients {
    	//        
    	String[] basePackages() default {};
        //       
        Class<?>[] defaultConfiguration() default {};
        //  @FeignClient       
        Class<?>[] clients() default {};
    
    }
    

    SpringBoot 가 시 작 될 때 applicationContext \ # refresh 방법 에서 invokeBean Factory PostProcessors 방법 을 호출 합 니 다. invokeBean Definition Registry PostProcessors () 는 모든 configClass (설정 클래스) @ Import 가 도입 한 ImportBean Definition Registrar 하위 클래스 를 불 러 오고 registerBean Definitions 방법 을 실행 합 니 다.(구체 적 인 과정 에서 독 자 는 FeignClient Registrar \ # registerBeanDefinitions 중단 점 에서 호출 스 택 을 볼 수 있 습 니 다).
    구체 적 으로 FeignClient Registrar 를 보십시오.
    class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
    		ResourceLoaderAware, EnvironmentAware {
            
                ......
                    //    FeignClient
                    public void registerFeignClients(AnnotationMetadata metadata,
    			BeanDefinitionRegistry registry) {
    		ClassPathScanningCandidateComponentProvider scanner = getScanner();
    		scanner.setResourceLoader(this.resourceLoader);
    
    		Set<String> basePackages;
    
    		Map<String, Object> attrs = metadata
    				.getAnnotationAttributes(EnableFeignClients.class.getName());
               //  FeignClient     
    		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
    				FeignClient.class);
              //   @EnableFeignClients       clients     ,    
    		final Class<?>[] clients = attrs == null ? null
    				: (Class<?>[]) attrs.get("clients");
               //  clients     ,   FeignClient      
    		if (clients == null || clients.length == 0) {
    			scanner.addIncludeFilter(annotationTypeFilter);
    			basePackages = getBasePackages(metadata);
    		}
    		else {
                //           clients        
    			final Set<String> clientClasses = new HashSet<>();
    			basePackages = new HashSet<>();
    			for (Class<?> clazz : clients) {
    				basePackages.add(ClassUtils.getPackageName(clazz));
    				clientClasses.add(clazz.getCanonicalName());
    			}
    			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
    				@Override
    				protected boolean match(ClassMetadata metadata) {
    					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
    					return clientClasses.contains(cleaned);
    				}
    			};
    			scanner.addIncludeFilter(
    					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    		}
    			//     
    		for (String basePackage : basePackages) {
    			Set<BeanDefinition> candidateComponents = scanner
    					.findCandidateComponents(basePackage);
    			for (BeanDefinition candidateComponent : candidateComponents) {
    				if (candidateComponent instanceof AnnotatedBeanDefinition) {
    					//           
    					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
    					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
    					Assert.isTrue(annotationMetadata.isInterface(),
    							"@FeignClient can only be specified on an interface");
    
    					Map<String, Object> attributes = annotationMetadata
    							.getAnnotationAttributes(
    									FeignClient.class.getCanonicalName());
    
    					String name = getClientName(attributes);
                        //    FeignClientSpecification 
    					registerClientConfiguration(registry, name,
    							attributes.get("configuration"));
    					//     FeignClient  
    					registerFeignClient(registry, annotationMetadata, attributes);
    				}
    			}
    		}
    	}
        //     FeignClient  
    	private void registerFeignClient(BeanDefinitionRegistry registry,
    			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    		String className = annotationMetadata.getClassName();
             //   FeignClientFactoryBean  bean    
    		BeanDefinitionBuilder definition = BeanDefinitionBuilder
    				.genericBeanDefinition(FeignClientFactoryBean.class);
    		validate(attributes);
            //  FeignClient        bean    
    		definition.addPropertyValue("url", getUrl(attributes));
    		definition.addPropertyValue("path", getPath(attributes));
    		String name = getName(attributes);
    		definition.addPropertyValue("name", name);
    		definition.addPropertyValue("type", className);
    		definition.addPropertyValue("decode404", attributes.get("decode404"));
    		definition.addPropertyValue("fallback", attributes.get("fallback"));
    		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
            //    Type         
    		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    		//      
    		String alias = name + "FeignClient";
    		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    
    		boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
    
    		beanDefinition.setPrimary(primary);
    
    		String qualifier = getQualifier(attributes);
    		if (StringUtils.hasText(qualifier)) {
    			alias = qualifier;
    		}
    
    		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
    				new String[] { alias });
            //    
    		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    	}
         //    FeignClientSpecification   FeignClientSpecification     name   @FeignClient  name  configuration   @FeignClient     configuration 
          private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
    			Object configuration) {
    		BeanDefinitionBuilder builder = BeanDefinitionBuilder
    				.genericBeanDefinition(FeignClientSpecification.class);
    		builder.addConstructorArgValue(name);
    		builder.addConstructorArgValue(configuration);
    		registry.registerBeanDefinition(
    				name + "." + FeignClientSpecification.class.getSimpleName(),
    				builder.getBeanDefinition());
          }   
                .....
            
    }
    

    위의 코드 를 통 해 알 수 있 듯 이 Spring Cloud 는 @ EnableFeignClient 에서 지정 한 가방 의 모든 @ FeignClient 에 표 시 된 클래스 에 FeignClient Factory Bean 과 FeignClient Specification (각 @ FeignClient 에 표 시 된 클래스 에 대한 지정 설정) 을 만 들 었 습 니 다.
    FeignClient Factory Bean 은 Factory Bean (여기 서 Factory Bean 을 소개 하지 않 음) 입 니 다. Spring 을 주입 할 때 Factory Bean 의 getObject 방법 을 호출 합 니 다. 그러면 관건 은 FeignClient Factory Bean 의 getObject 방법 입 니 다.
    @Override
    	public Object getObject() throws Exception {
    		FeignContext context = applicationContext.getBean(FeignContext.class);
            //     Feign.Builder (  ,   ,    )
    		Feign.Builder builder = feign(context);
    		//   url    
    		if (!StringUtils.hasText(this.url)) {
    			String url;
                //     url 
    			if (!this.name.startsWith("http")) {
    				url = "http://" + this.name;
    			}
    			else {
    				url = this.name;
    			}
    			url += cleanPath();
                //    Object 
    			return loadBalance(builder, context, new HardCodedTarget<>(this.type,
    					this.name, url));
    		}
    		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
    			this.url = "http://" + this.url;
    		}
    		String url = this.url + cleanPath();
            //    Client
    		Client client = getOptional(context, Client.class);
    		if (client != null) {
    			if (client instanceof LoadBalancerFeignClient) {
    				// not load balancing because we have a url,
    				// but ribbon is on the classpath, so unwrap
    				client = ((LoadBalancerFeignClient)client).getDelegate();
    			}
    			builder.client(client);
    		}
    		Targeter targeter = get(context, Targeter.class);
    		return targeter.target(this, builder, context, new HardCodedTarget<>(
    				this.type, this.name, url));
    	}
    
    
    
    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
    			HardCodedTarget<T> target) {
         //    Client   ribbon   LoadBalancerFeignClient 
    		Client client = getOptional(context, Client.class);
    		if (client != null) {
    			builder.client(client);
                //     Targeter     Hystrix     HystrixTargeter 
    			Targeter targeter = get(context, Targeter.class);
    			return targeter.target(this, builder, context, target);
    		}
    
    		throw new IllegalStateException(
    				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
    	}
    

    위 에 설정 을 준비 한 다음 에 Targeter 의 target 방법 을 보고 HystrixTargeter 를 직접 봅 니 다.
    @Override
    	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
    						Target.HardCodedTarget<T> target) {
    		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
    			return feign.target(target);
    		}
    		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
    		SetterFactory setterFactory = getOptional(factory.getName(), context,
    			SetterFactory.class);
    		if (setterFactory != null) {
    			builder.setterFactory(setterFactory);
    		}
    		Class<?> fallback = factory.getFallback();
    		if (fallback != void.class) {
    			return targetWithFallback(factory.getName(), context, target, builder, fallback);
    		}
    		Class<?> fallbackFactory = factory.getFallbackFactory();
    		if (fallbackFactory != void.class) {
    			return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
    		}
    
    		return feign.target(target);
        }
    

    feign 을 feign. hystrix. Hystrix Feign. Builder 로 바 꾸 고 fallback 또는 fallback Factory 에서 feign. hystrix. Hystrix Feign. Builder 를 호출 하 는 target 방법 을 설정 합 니 다.
      public <T> T target(Target<T> target, T fallback) {
          return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null)
              .newInstance(target);
        }
    
    
    Feign build(final FallbackFactory<?> nullableFallbackFactory) {
          super.invocationHandlerFactory(new InvocationHandlerFactory() {
            @Override public InvocationHandler create(Target target,
                Map<Method, MethodHandler> dispatch) {
              return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
            }
          });
          super.contract(new HystrixDelegatingContract(contract));
          return super.build();
        }
    

    Hystrix Invocation Handler 와 Hystrix Delegating Contract 를 설정 합 니 다.Feign \ # newInstance 방법 을 호출 합 니 다. 사실 지금 Reflective Feign 입 니 다.
    @Override
      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;
      }
    

    JDK 의 동적 대 리 를 사용 하여 프 록 시 클래스 를 생 성 했 습 니 다. 구체 적 인 실행 논 리 는 Invocation Handler 인 터 페 이 스 를 보고 factory. create (target, methodToHandler) 를 보 며 feign. hystrix. Hystrix Feign. Builder 의 target 방법 은 Invocation Handler Factory factory 를 Hystrix Invocation Handler 로 설정 하 였 습 니 다.
    new InvocationHandlerFactory() {
      @Override public InvocationHandler create(Target target,
          Map<Method, MethodHandler> dispatch) {
        return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
      }
    

    Hystrix Invocation Handler 에 들 어가 invoke 방법 을 보 는 것 이 구체 적 인 방법의 집행 논리 입 니 다.
     @Override
      public Object invoke(final Object proxy, final Method method, final Object[] args)
          throws Throwable {
        // early exit if the invoked method is from java.lang.Object
        // code is the same as ReflectiveFeign.FeignInvocationHandler
        if ("equals".equals(method.getName())) {
          try {
            Object otherHandler =
                args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
            return equals(otherHandler);
          } catch (IllegalArgumentException e) {
            return false;
          }
        } else if ("hashCode".equals(method.getName())) {
          return hashCode();
        } else if ("toString".equals(method.getName())) {
          return toString();
        }
    
        HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
          @Override
          protected Object run() throws Exception {
            try {
              return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
            } catch (Exception e) {
              throw e;
            } catch (Throwable t) {
              throw (Error) t;
            }
          }
    
          @Override
          protected Object getFallback() {
            if (fallbackFactory == null) {
              return super.getFallback();
            }
            try {
              Object fallback = fallbackFactory.create(getExecutionException());
              Object result = fallbackMethodMap.get(method).invoke(fallback, args);
              if (isReturnsHystrixCommand(method)) {
                return ((HystrixCommand) result).execute();
              } else if (isReturnsObservable(method)) {
                // Create a cold Observable
                return ((Observable) result).toBlocking().first();
              } else if (isReturnsSingle(method)) {
                // Create a cold Observable as a Single
                return ((Single) result).toObservable().toBlocking().first();
              } else if (isReturnsCompletable(method)) {
                ((Completable) result).await();
                return null;
              } else {
                return result;
              }
            } catch (IllegalAccessException e) {
              // shouldn't happen as method is public due to being an interface
              throw new AssertionError(e);
            } catch (InvocationTargetException e) {
              // Exceptions on fallback are tossed by Hystrix
              throw new AssertionError(e.getCause());
            }
          }
        };
    
        if (isReturnsHystrixCommand(method)) {
          return hystrixCommand;
        } else if (isReturnsObservable(method)) {
          // Create a cold Observable
          return hystrixCommand.toObservable();
        } else if (isReturnsSingle(method)) {
          // Create a cold Observable as a Single
          return hystrixCommand.toObservable().toSingle();
        } else if (isReturnsCompletable(method)) {
          return hystrixCommand.toObservable().toCompletable();
        }
        return hystrixCommand.execute();
      }
    

    Hystrix Command 를 만들어 Hystrix 특성 을 가지 게 하 는 것 이 주요 논리 임 을 알 수 있다.구체 적 인 업 무 는 HystrixInvocation Handler. this. dispatch. get (method). invoke (args) 에 있 습 니 다.바로 MethodHandler 의 invoke 방법 입 니 다.
      @Override
      public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = buildTemplateFromArgs.create(argv);
        Retryer retryer = this.retryer.clone();
        while (true) {
          try {
            return executeAndDecode(template);
          } catch (RetryableException e) {
            retryer.continueOrPropagate(e);
            if (logLevel != Logger.Level.NONE) {
              logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
          }
        }
      }
    
      Object executeAndDecode(RequestTemplate template) throws Throwable {
        Request request = targetRequest(template);
    
        if (logLevel != Logger.Level.NONE) {
          logger.logRequest(metadata.configKey(), logLevel, request);
        }
    
        Response response;
        long start = System.nanoTime();
        try {
          response = client.execute(request, options);
          // ensure the request is set. TODO: remove in Feign 10
          response.toBuilder().request(request).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);
    
        boolean shouldClose = true;
        try {
          if (logLevel != Logger.Level.NONE) {
            response =
                logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
            // ensure the request is set. TODO: remove in Feign 10
            response.toBuilder().request(request).build();
          }
          if (Response.class == metadata.returnType()) {
            if (response.body() == null) {
              return response;
            }
            if (response.body().length() == null ||
                    response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
              shouldClose = false;
              return response;
            }
            // Ensure the response body is disconnected
            byte[] bodyData = Util.toByteArray(response.body().asInputStream());
            return response.toBuilder().body(bodyData).build();
          }
          if (response.status() >= 200 && response.status() < 300) {
            if (void.class == metadata.returnType()) {
              return null;
            } else {
              return decode(response);
            }
          } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
            return decode(response);
          } else {
            throw errorDecoder.decode(metadata.configKey(), response);
          }
        } catch (IOException e) {
          if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
          }
          throw errorReading(request, response, e);
        } finally {
          if (shouldClose) {
            ensureClosed(response.body());
          }
        }
      }
    

    위 코드 에서 보 듯 이 RequestTemplate 를 통 해 HTTP 요청 을 하류 서비스 에 보 냅 니 다. 이때 RequestTemplate 는 부하 균형 특성 을 가지 고 있 습 니 다 (위 에서 언급 한 Client 는 LoadBalancer FeignClient 입 니 다).
    요약: @ EnableFeignClient 주석 이 지정 한 패 키 지 를 검색 합 니 다. @ FeignClient 에 표 시 된 인터페이스 에 JDK 동적 프 록 시 를 통 해 프 록 시 클래스 를 생 성하 고 호출 할 때 RequestTemplate 로 요청 을 보 냅 니 다.부하 균형 과 서 비 스 를 끊 을 지 설정 할 수 있 습 니 다.다음 절 에 서 는 Feign 과 Ribbon, Hystrix 통합 을 소개 합 니 다.

    좋은 웹페이지 즐겨찾기