您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)SpringCloud中怎么聲明式調(diào)用Feign,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
使用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)用。
從前面Ribbon中拿到項(xiàng)目整體,然后再整改成如下目錄
帖子地址:https://my.oschina.net/devilsblog/blog/3115061
碼云地址:https://gitee.com/devilscode/cloud-practice/tree/ribbon-test
修改項(xiàng)目名稱為feign-test
修改原來的子Module ribbon-service為feign-service
<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>
<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>
<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>
<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>
這樣就可以從eureka注冊中心中區(qū)別出我們的服務(wù)是feign-service
server: port: 8082 spring: application: name: feign-service eureka: client: service-url: defaultZone: http://localhost:8080/eureka/
服務(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); } }
/** * <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ù)分析
/** * <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(); }
/** * <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"; } }
刪除RemoteCommonService.java即可
step1. EurekaSeverApplicaton
step2. CommonServiceApplication
step3. CommonServiceApplication2
step4. FeignServerApplication
Eureka管理界面 http://localhost:8080/
調(diào)用 http://localhost:8082/hi
刷新頁面
/** * @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); } }
通過@EnableFeignClients開啟注解掃描
掃描@FeignClient注解修飾的元注解信息,使用BeanDefinitionBuilder解析成BeanDefinition,交給IoC容器中
通過JDK代理,當(dāng)發(fā)現(xiàn)FeignClient被調(diào)用的時(shí)候,攔截該方法
攔截到該方法后,在SynchronousMethodHandler類中,使用生成的RequestTemplate,重新生成Request對象
使用HttpClient調(diào)用請求,獲取Response
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")); } } }
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); } } } }
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); }
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è)資訊頻道,感謝大家的支持。
免責(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)容。