SpringCloud.Honxton 버전 OpenFeign 원리 하편

77199 단어 openfeignspringcloud
SpringCloud.Honxton 버전 OpenFeign 원리
  • 사전 설명
  • 사용 방법
  • springcloud와openfeign 통합원리
  • 요약
  • 선행 설명
    전편에서 Openfeign의 사용과 원리를 소개했으니 이편은springcloud와 통합된 부분을 소개할 것이다.전편의 기초가 있으면 원리를 분석할 때 훨씬 간단해지고 스프링 클라우드의 통합 부분에 더 관심을 기울일 것이다.
    사용 의존
      <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
    

    사용 방법
    관례에 따라feign을 어떻게 사용하는지 먼저 소개합니다
    @FeignClient("client2") // service-id
    public interface HelloOpenfeignSpringcloud {
    
       
        @RequestMapping(name=" /feign",method = RequestMethod.GET, produces="application/x-www-form-urlencoded")
        String feign(@RequestParam("feign") String feign);
    
        @RequestMapping(name="/feignPost",method = RequestMethod.POST, produces="application/x-www-form-urlencoded")
        String feignPost(@RequestParam("feign") String feign);
    
        @RequestMapping(name = "/feignJson",method = RequestMethod.POST , produces="application/json")
        String feignJson(@RequestBody String feign);
    
    }
    
    

    그리고 @EnableFeignClients 주석을 붙여서 부팅 클래스에 올려야 합니다.
    통합된 용법의 장점은 다음과 같은 몇 가지가 있음을 알 수 있다
  • 수동 호출 에이전트의 생성이 필요하지 않음
  • 저희가 익숙한springmvc 주석
  • 을 사용할 수 있습니다.
  • 등록센터와 결합한 후에 우리가 URL을 지정할 필요가 없고 부하 균형을 지원할 수 있다. 이외에springcloud는 퓨즈와feign의 통합을 제공했다. 사용법도 간단하다. 바로 인터페이스에 실현 클래스를 추가하여 강등 처리의 논리로 한다.물론 단점도 많다. 파일을 전달할 때 문제가 있을 수 있고, 전달 대상은 @RequestBody를 사용해야 하지만, 공인된 클라이언트 원격 호출 프레임워크라고 할 수 있다.

  • springcloud와openfeign 통합 원리
    기본적인 사용을 알고 나면 스프링클라우드 통합 피그 베일을 벗길 때입니다.
    다음은 모든feign의 자동 설정 클래스입니다
    org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration, org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration, org.springframework.cloud.openfeign.FeignAutoConfiguration, org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration, org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration, org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
    그 중에서 가장 중요한 것은FeignAutoConfigurationFeignRibbonClientAutoConfiguration 또는FeignLoadBalancerAutoConfiguration그러면 저희가 하나씩 분석해 보도록 하겠습니다.
    
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Feign.class) //     
    @EnableConfigurationProperties({ FeignClientProperties.class,
    		FeignHttpClientProperties.class })
    @Import(DefaultGzipDecoderConfiguration.class)
    public class FeignAutoConfiguration {
    
    	@Autowired(required = false)  //    feignclient  
    	private List<FeignClientSpecification> configurations = new ArrayList<>();
    
    	@Bean  // feignclient   spring    ,        serviceId     applicationContext          ,         client       
    	public FeignContext feignContext() {
    		FeignContext context = new FeignContext();
    		context.setConfigurations(this.configurations);
    		return context;
    	}
    
    	//       feign hystrix   
    	@Configuration(proxyBeanMethods = false)
    	@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
    	protected static class HystrixFeignTargeterConfiguration {
    		//    Targeter   , springcloud      ,       DefaultTargeter ,   Feign.Builder target  ,          
    		@Bean
    		@ConditionalOnMissingBean
    		public Targeter feignTargeter() {
    			return new HystrixTargeter();
    		}
    
    	}
    	//     hystrix    
    	@Configuration(proxyBeanMethods = false)
    	@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
    	protected static class DefaultFeignTargeterConfiguration {
    
    		@Bean
    		@ConditionalOnMissingBean
    		public Targeter feignTargeter() {
    			return new DefaultTargeter();
    		}
    
    	}
    
    
    	// feign         ,        httpclient   bean   
    	@Configuration(proxyBeanMethods = false)
    	@ConditionalOnClass(ApacheHttpClient.class) //                  
    	@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    	@ConditionalOnMissingBean(CloseableHttpClient.class)
    	@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    	protected static class HttpClientFeignConfiguration {
    
    		private final Timer connectionManagerTimer = new Timer(
    				"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
    
    		@Autowired(required = false)
    		private RegistryBuilder registryBuilder;
    
    		private CloseableHttpClient httpClient;
    
    		@Bean
    		@ConditionalOnMissingBean(HttpClientConnectionManager.class)
    		public HttpClientConnectionManager connectionManager(
    				ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
    				FeignHttpClientProperties httpClientProperties) {
    			final HttpClientConnectionManager connectionManager = connectionManagerFactory
    					.newConnectionManager(httpClientProperties.isDisableSslValidation(),
    							httpClientProperties.getMaxConnections(),
    							httpClientProperties.getMaxConnectionsPerRoute(),
    							httpClientProperties.getTimeToLive(),
    							httpClientProperties.getTimeToLiveUnit(),
    							this.registryBuilder);
    			this.connectionManagerTimer.schedule(new TimerTask() {
    				@Override
    				public void run() {
    					connectionManager.closeExpiredConnections();
    				}
    			}, 30000, httpClientProperties.getConnectionTimerRepeat());
    			return connectionManager;
    		}
    
    		@Bean
    		public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
    				HttpClientConnectionManager httpClientConnectionManager,
    				FeignHttpClientProperties httpClientProperties) {
    			RequestConfig defaultRequestConfig = RequestConfig.custom()
    					.setConnectTimeout(httpClientProperties.getConnectionTimeout())
    					.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
    					.build();
    			this.httpClient = httpClientFactory.createBuilder()
    					.setConnectionManager(httpClientConnectionManager)
    					.setDefaultRequestConfig(defaultRequestConfig).build();
    			return this.httpClient;
    		}
    		//   client                    ,    httpclient    ,              bean      
    		@Bean
    		@ConditionalOnMissingBean(Client.class)
    		public Client feignClient(HttpClient httpClient) {
    			return new ApacheHttpClient(httpClient);
    		}
    
    		@PreDestroy
    		public void destroy() throws Exception {
    			this.connectionManagerTimer.cancel();
    			if (this.httpClient != null) {
    				this.httpClient.close();
    			}
    		}
    
    	}
    ...
    
    }
    
    

    다음은feign의 밑바닥 부하 균형의 실현을 소개한다. 여기는 두 가지가 있는데 하나는 리본의 밑바닥 통합이고 다른 하나는 내가 전에 소개한 스프링이 스스로 개발한loadbalancer이다.
    
    @ConditionalOnClass({ ILoadBalancer.class, Feign.class })
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
    		matchIfMissing = true)
    @Configuration(proxyBeanMethods = false)
    @AutoConfigureBefore(FeignAutoConfiguration.class) //            
    @EnableConfigurationProperties({ FeignHttpClientProperties.class })
    @Import({ HttpClientFeignLoadBalancedConfiguration.class,
    		OkHttpFeignLoadBalancedConfiguration.class,
    		DefaultFeignLoadBalancedConfiguration.class }) //          client     client bean  
    public class FeignRibbonClientAutoConfiguration {
    	
    	@Bean
    	@Primary
    	@ConditionalOnMissingBean
    	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    	public CachingSpringLoadBalancerFactory cachingLBClientFactory(
    			SpringClientFactory factory) {
    		return new CachingSpringLoadBalancerFactory(factory);
    	}
    	//             Client   execute     
    	@Bean
    	@Primary
    	@ConditionalOnMissingBean
    	@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    	public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
    			SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
    		return new CachingSpringLoadBalancerFactory(factory, retryFactory);
    	}
    
    	@Bean
    	@ConditionalOnMissingBean
    	public Request.Options feignRequestOptions() {
    		return LoadBalancerFeignClient.DEFAULT_OPTIONS;
    	}
    
    }
    
    //           client  ,         feign   DefaultClient
    @Configuration(proxyBeanMethods = false)
    class DefaultFeignLoadBalancedConfiguration {
    	
    	@Bean
    	@ConditionalOnMissingBean
    	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
    			SpringClientFactory clientFactory) {
    		return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
    				clientFactory);
    	}
    
    }
    
    
    

    또 다른 부하 균형기와feign의 통합을 보도록 하겠습니다.
    //        
    @ConditionalOnClass(Feign.class)
    @ConditionalOnBean(BlockingLoadBalancerClient.class)
    @AutoConfigureBefore(FeignAutoConfiguration.class)
    @AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)
    @EnableConfigurationProperties(FeignHttpClientProperties.class)
    @Configuration(proxyBeanMethods = false)
    @Import({ HttpClientFeignLoadBalancerConfiguration.class,
    		OkHttpFeignLoadBalancerConfiguration.class,
    		DefaultFeignLoadBalancerConfiguration.class }) //          ,    ribbon   
    class FeignLoadBalancerAutoConfiguration {
    
    }
    
    //               Client   
    @Configuration(proxyBeanMethods = false)
    class DefaultFeignLoadBalancedConfiguration {
    
    	@Bean
    	@ConditionalOnMissingBean
    	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
    			SpringClientFactory clientFactory) {
    		return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
    				clientFactory);
    	}
    
    }
    
    

    위의 모든 설정 클래스를 보고 다들 어지러우실 거라고 믿습니다. 사실 걱정하지 마세요. 아래에 Feign Client 스캔으로 Feign Client Factory Bean을 만들 때 그 때 위의 설정 bean을 모두 주입해서 사용할 수 있기 때문입니다.
    우선 @EnableFeignClients 주석은 스캔을 켜는 기능입니다.
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(FeignClientsRegistrar.class) //     
    public @interface EnableFeignClients {
    	String[] value() default {};
    	String[] basePackages() default {};//      
    ... 
    
    
    }
    //@FeignClient   
    class FeignClientsRegistrar
    		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
      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());
    		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
    				FeignClient.class);
    		final Class<?>[] clients = attrs == null ? null
    				: (Class<?>[]) attrs.get("clients");
    		if (clients == null || clients.length == 0) {
    			scanner.addIncludeFilter(annotationTypeFilter);
    			basePackages = getBasePackages(metadata);
    		}
    		else {
    			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)));
    		}
    		//        beandefinition
    		for (String basePackage : basePackages) {
    			Set<BeanDefinition> candidateComponents = scanner
    					.findCandidateComponents(basePackage);
    			for (BeanDefinition candidateComponent : candidateComponents) {
    				if (candidateComponent instanceof AnnotatedBeanDefinition) {
    					// verify annotated class is an interface
    					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);
    					registerClientConfiguration(registry, name,
    							attributes.get("configuration"));
    						//    
    					registerFeignClient(registry, annotationMetadata, attributes);
    				}
    			}
    		}
    	}
    
    	private void registerFeignClient(BeanDefinitionRegistry registry,
    			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    		String className = annotationMetadata.getClassName();
    		BeanDefinitionBuilder definition = BeanDefinitionBuilder
    				.genericBeanDefinition(FeignClientFactoryBean.class);
    		validate(attributes);
    		definition.addPropertyValue("url", getUrl(attributes));
    		definition.addPropertyValue("path", getPath(attributes));
    		String name = getName(attributes);
    		definition.addPropertyValue("name", name);
    		String contextId = getContextId(attributes);
    		definition.addPropertyValue("contextId", contextId);
    		definition.addPropertyValue("type", className);
    		definition.addPropertyValue("decode404", attributes.get("decode404"));
    		definition.addPropertyValue("fallback", attributes.get("fallback"));
    		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
    		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    
    		String alias = contextId + "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 });
    				//    beanfactotry  
    		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    	}
    }
    

    이상은 스캔과 관련된 실현 원리에 관한 것입니다. 주석을 달고 있는 모든 인터페이스가 결국FeignClientFactoryBean 형식으로 bean으로 등록되었습니다. 다음은 간단합니다. FeignClientFactoryBeangetobject 방법만 쳐다보면 마지막 수수께끼를 풀 수 있습니다.
    class FeignClientFactoryBean
    		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
    
    
    <T> T getTarget() {
    		FeignContext context = this.applicationContext.getBean(FeignContext.class);
    		//   Builder,               
    		Feign.Builder builder = feign(context);
    
    		if (!StringUtils.hasText(this.url)) {
    			if (!this.name.startsWith("http")) {
    				this.url = "http://" + this.name;
    			}
    			else {
    				this.url = this.name;
    			}
    			this.url += cleanPath();
    			return (T) loadBalance(builder, context,
    					new HardCodedTarget<>(this.type, this.name, this.url));
    		}
    		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
    			this.url = "http://" + this.url;
    		}
    		String url = this.url + cleanPath();
    		Client client = getOptional(context, Client.class); //          
    		if (client != null) {
    			if (client instanceof LoadBalancerFeignClient) {
    				client = ((LoadBalancerFeignClient) client).getDelegate();
    			}
    			builder.client(client);
    		}
    		//     Targeter      ,       DefaultTargeter
    		Targeter targeter = get(context, Targeter.class);
    		return (T) targeter.target(this, builder, context, //                        
    				new HardCodedTarget<>(this.type, this.name, url));
    	}
    
    protected Feign.Builder feign(FeignContext context) {
    		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); 
    		Logger logger = loggerFactory.create(this.type);
    
    		// @formatter:off               ,   bean          applicationContext   ,          
    		Feign.Builder builder = get(context, Feign.Builder.class)
    				// required values
    				.logger(logger)
    				.encoder(get(context, Encoder.class))
    				.decoder(get(context, Decoder.class))
    				.contract(get(context, Contract.class));
    		// @formatter:on
    
    		configureFeign(context, builder);
    
    		return builder;
    	}
    
    
    
    }
    

    Feign.Builder.class 이 bean은 어떻게 얻습니까
    //             bean  ,             FeignClientsConfiguration
    public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
    
    	public FeignContext() {
    		super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    	}
    
    }
    
    @Configuration(proxyBeanMethods = false)
    public class FeignClientsConfiguration {
    ...
    	@Bean
    	@Scope("prototype")
    	@ConditionalOnMissingBean
    	public Feign.Builder feignBuilder(Retryer retryer) {
    		return Feign.builder().retryer(retryer);
    	}
    
    ...
    
    }
    
    

    총결산
    마지막으로 요약하자면springcloud는feign을 통합시키기 위해 많은 작업을 했고 부하 균형기와 Hystrix를 결합시키기 위해 많은 확장 실현을 했다.이것 또한 배치류가 급격히 팽창하여 우리로 하여금 눈이 어지럽게 한다. 이 글을 보고 너로 하여금 구름과 안개를 헤치고 푸른 하늘을 보게 할지 모르겠지만, 나도 가능한 한 내가 이해한 것을 말하게 했다. 만약 내가 잘못 말한 것이 있다면 아래에서 평론과 교류를 할 수 있다.

    좋은 웹페이지 즐겨찾기