今天就跟大家聊聊有关SpringCloud中怎么声明式调用Feign,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
使用Ribbon和RestTemplate消费服务的时候,有一个最麻烦的点在于,每次都要拼接URL,组织参数,所以有了Feign声明式调用,Feign的首要目标是将Java HTTP客户端的调用过程非常简单。它采用声明式API接口的风格,将Java Http客户端绑定到它的内部,以此来方便调用。
从前面Ribbon中拿到项目整体,然后再整改成如下目录
帖子地址:https://my.oschina.net/devilsblog/blog/3115061
码云地址:https://gitee.com/devilscode/cloud-practice/tree/ribbon-test
修改项目名称为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注册中心中区别出我们的服务是feign-service
server: port: 8082 spring: application: name: feign-service eureka: client: service-url: defaultZone: http://localhost:8080/eureka/
服务启动的时候,此注解的作用会使得所有使用了注解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 { /** * 覆盖默认的重试 * 重试间隔100ms * 最大重试时间1s * 最大重试次数5次 */ @Bean public Retryer feignRetryer(){ return new Retryer.Default(100,SECONDS.toMillis(1), 5); } }
关于FeignClient的相关配置,如果我们不主动去配置,都有默认配置,配置类为FeignClientsConfiguration.class,详细内容后续分析
/** * <p> * 远程调用公共服务消费端 * </p> * * @author Calvin * @date 2019/10/21 * @since */ @FeignClient(value = "common-service", configuration = FeignConfiguration.class) public interface CommonFeignClient { /** * 调用 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/
调用 http://localhost:8082/hi
刷新页面
/** * @author Dave Syer * @author Venil Noronha */ @Configuration public class FeignClientsConfiguration { /** * 配置消息转换器 */ @Autowired private ObjectFactory<HttpMessageConverters> messageConverters; /** * 注入参数解析,用于解析@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代理,当发现FeignClient被调用的时候,拦截该方法
拦截到该方法后,在SynchronousMethodHandler类中,使用生成的RequestTemplate,重新生成Request对象
使用HttpClient调用请求,获取Response
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { /** * <p> * 检查EnableFeignClients注解是否开启,如果开启,则开始注册默认配置 * </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; } }
拦截进行处理,先解析RequestTemplate,然后使用HttpClient调用网络请求
final class SynchronousMethodHandler implements MethodHandler { /** * 将传递过来的参数解析成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转换成一个Request,然后使用HttpClient调用请求,拿到返回结果 */ 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) { // 处理异常 } //省略返回的代码 }
看完上述内容,你们对SpringCloud中怎么声明式调用Feign有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。