溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringCloud中怎么聲明式調(diào)用Feign

發(fā)布時(shí)間:2021-06-24 15:49:33 來源:億速云 閱讀:198 作者:Leah 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)SpringCloud中怎么聲明式調(diào)用Feign,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

Feign聲明式調(diào)用

一、Feign簡介

使用Ribbon和RestTemplate消費(fèi)服務(wù)的時(shí)候,有一個(gè)最麻煩的點(diǎn)在于,每次都要拼接URL,組織參數(shù),所以有了Feign聲明式調(diào)用,F(xiàn)eign的首要目標(biāo)是將Java HTTP客戶端的調(diào)用過程非常簡單。它采用聲明式API接口的風(fēng)格,將Java Http客戶端綁定到它的內(nèi)部,以此來方便調(diào)用。

二、Feign實(shí)踐

1、項(xiàng)目組織

從前面Ribbon中拿到項(xiàng)目整體,然后再整改成如下目錄
帖子地址:https://my.oschina.net/devilsblog/blog/3115061
碼云地址:https://gitee.com/devilscode/cloud-practice/tree/ribbon-test

  1. 修改項(xiàng)目名稱為feign-test

  2. 修改原來的子Module ribbon-service為feign-service
    SpringCloud中怎么聲明式調(diào)用Feign

2、核心pom


feign-test/pom.xml
<modelVersion>4.0.0</modelVersion>

<groupId>com.calvin.feigb</groupId>
<artifactId>feign-test</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>

<modules>
    <module>common-service</module>
    <module>eureka-server</module>
    <module>feign-service</module>
</modules>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.3.RELEASE</version>
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Dalston.SR4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

common-service/pom.xml
<parent>
    <groupId>com.calvin.feigb</groupId>
    <artifactId>feign-test</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>common-service</artifactId>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

eureka-service/pom.xml
<parent>
    <groupId>com.calvin.feigb</groupId>
    <artifactId>feign-test</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

feign-service/pom.xml
<parent>
    <groupId>com.calvin.feigb</groupId>
    <artifactId>feign-test</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>feign-service</artifactId>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

3、feign-service項(xiàng)目改造

(1)修改服務(wù)名稱

這樣就可以從eureka注冊中心中區(qū)別出我們的服務(wù)是feign-service

server:
  port: 8082
spring:
  application:
    name: feign-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka/
(2)增加配置注解EurekaFeignClient

服務(wù)啟動的時(shí)候,此注解的作用會使得所有使用了注解FeignClient的類,被掃描解析,然后注冊到IoC容器中。

/**
 * <p> 
 *     啟動類 
 * </p>
 *
 * @author Calvin
 * @date 2019/10/09
 * @since
 */
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients
public class FeignServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignServerApplication.class);
    }
}
(3)新建FeignConfiguration.java
/**
 * <p> FeignClient的配置類 </p>
 *
 * @author Calvin
 * @date 2019/10/21
 * @since
 */
@Configuration
public class FeignConfiguration {

    /**
     * 覆蓋默認(rèn)的重試
     * 重試間隔100ms
     * 最大重試時(shí)間1s
     * 最大重試次數(shù)5次
     */
    @Bean
    public Retryer feignRetryer(){
        return new Retryer.Default(100,SECONDS.toMillis(1), 5);
    }
}

關(guān)于FeignClient的相關(guān)配置,如果我們不主動去配置,都有默認(rèn)配置,配置類為FeignClientsConfiguration.class,詳細(xì)內(nèi)容后續(xù)分析

(4)增加CommonFeignClient.java
/**
 * <p> 
 *     遠(yuǎn)程調(diào)用公共服務(wù)消費(fèi)端
 * </p>
 *
 * @author Calvin
 * @date 2019/10/21
 * @since
 */
@FeignClient(value = "common-service", configuration = FeignConfiguration.class)
public interface CommonFeignClient {

    /**
     * 調(diào)用 common-service/hello接口
     * @return
     */
    @GetMapping(value = "/hello")
    String sayHi();

}
(5)改造SayHiController.java
/**
 * <p>
 *     測試接口
 * </p>
 *
 * @author Calvin
 * @date 2019/10/09
 * @since
 */
@RestController
public class SayHiController {

//    @Autowired
//    private RemoteCommonService remoteCommonService;

    @Autowired
    private CommonFeignClient commonFeignClient;

    @GetMapping("/hi")
    public String sayHi(){
        return commonFeignClient.sayHi() + ", this is feign service";
    }

}
(6)停用RemoteCommonService

刪除RemoteCommonService.java即可

3、啟動整體項(xiàng)目

  • step1. EurekaSeverApplicaton

  • step2. CommonServiceApplication

  • step3. CommonServiceApplication2

  • step4. FeignServerApplication

4、調(diào)用接口測試

Eureka管理界面 http://localhost:8080/
SpringCloud中怎么聲明式調(diào)用Feign

調(diào)用 http://localhost:8082/hi

SpringCloud中怎么聲明式調(diào)用Feign

刷新頁面

SpringCloud中怎么聲明式調(diào)用Feign

三、工作原理探索

1. FeignClientsConfiguration

/**
 * @author Dave Syer
 * @author Venil Noronha
 */
@Configuration
public class FeignClientsConfiguration {

	/**
	 * 配置消息轉(zhuǎn)換器
	 */
	@Autowired
	private ObjectFactory<HttpMessageConverters> messageConverters;

	/**
	 * 注入?yún)?shù)解析,用于解析@RequestParam
	 */
	@Autowired(required = false)
	private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
	
	/**
	 * 
	 */
	@Autowired(required = false)
	private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

	@Autowired(required = false)
	private Logger logger;

	
	/**
	 * 返回值解析
	 */
	@Bean
	@ConditionalOnMissingBean
	public Decoder feignDecoder() {
		return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
	}

	/**
	 * url編碼
	 */
	@Bean
	@ConditionalOnMissingBean
	public Encoder feignEncoder() {
		return new SpringEncoder(this.messageConverters);
	}

	@Bean
	@ConditionalOnMissingBean
	public Contract feignContract(ConversionService feignConversionService) {
		return new SpringMvcContract(this.parameterProcessors, feignConversionService);
	}

	@Bean
	public FormattingConversionService feignConversionService() {
		FormattingConversionService conversionService = new DefaultFormattingConversionService();
		for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
			feignFormatterRegistrar.registerFormatters(conversionService);
		}
		return conversionService;
	}

	@Configuration
	@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
	protected static class HystrixFeignConfiguration {
		@Bean
		@Scope("prototype")
		@ConditionalOnMissingBean
		@ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
		public Feign.Builder feignHystrixBuilder() {
			return HystrixFeign.builder();
		}
	}

	/**
	 * 配置重試,NEVER_RETRY代表從不重試
	 */
	@Bean
	@ConditionalOnMissingBean
	public Retryer feignRetryer() {
		return Retryer.NEVER_RETRY;
	}

	/**
	 * 給Feign綁定重試器
	 */
	@Bean
	@Scope("prototype")
	@ConditionalOnMissingBean
	public Feign.Builder feignBuilder(Retryer retryer) {
		return Feign.builder().retryer(retryer);
	}

	/**
	 * 日志工廠
	 */
	@Bean
	@ConditionalOnMissingBean(FeignLoggerFactory.class)
	public FeignLoggerFactory feignLoggerFactory() {
		return new DefaultFeignLoggerFactory(logger);
	}

}

2. Feign工作原理

(1)步驟
  1. 通過@EnableFeignClients開啟注解掃描

  2. 掃描@FeignClient注解修飾的元注解信息,使用BeanDefinitionBuilder解析成BeanDefinition,交給IoC容器中

  3. 通過JDK代理,當(dāng)發(fā)現(xiàn)FeignClient被調(diào)用的時(shí)候,攔截該方法

  4. 攔截到該方法后,在SynchronousMethodHandler類中,使用生成的RequestTemplate,重新生成Request對象

  5. 使用HttpClient調(diào)用請求,獲取Response

(2)相關(guān)代碼
掃描@EnableFeignClients
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
		ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
	/**
	 * <p>
	 *    檢查EnableFeignClients注解是否開啟,如果開啟,則開始注冊默認(rèn)配置
	 * </p>
	 */
	private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				name = "default." + metadata.getClassName();
			}
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}
}
掃描@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());
	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)));
	}
	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);
			}
		}
	}
}
解析元注解內(nèi)容,注入到IoC容器中
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);
	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 = 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);
}
JDK代理,攔截FeignClient的調(diào)用
public class ReflectiveFeign extends 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;
  }
}

攔截進(jìn)行處理,先解析RequestTemplate,然后使用HttpClient調(diào)用網(wǎng)絡(luò)請求

final class SynchronousMethodHandler implements MethodHandler {

  /**
   * 將傳遞過來的參數(shù)解析成RequestTemplate
   */
  @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;
      }
    }
  }
  /**
   * 將RequestTemplate轉(zhuǎn)換成一個(gè)Request,然后使用HttpClient調(diào)用請求,拿到返回結(jié)果
   */
  Object executeAndDecode(RequestTemplate template) throws Throwable {
  Request request = targetRequest(template);

  Response response;
  long start = System.nanoTime();
  try {
  response = client.execute(request, options);
  response.toBuilder().request(request).build();
  } catch (IOException e) {
  // 處理異常
  }
  //省略返回的代碼
}

看完上述內(nèi)容,你們對SpringCloud中怎么聲明式調(diào)用Feign有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI