SpringCloud中怎么声明式调用Feign
今天就跟大家聊聊有关SpringCloud中怎么声明式调用Feign,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
Feign声明式调用
一、Feign简介
使用Ribbon和RestTemplate消费服务的时候,有一个最麻烦的点在于,每次都要拼接URL,组织参数,所以有了Feign声明式调用,Feign的首要目标是将Java HTTP客户端的调用过程非常简单。它采用声明式API接口的风格,将Java Http客户端绑定到它的内部,以此来方便调用。
二、Feign实践
1、项目组织
从前面Ribbon中拿到项目整体,然后再整改成如下目录
帖子地址:https://my.oschina.net/devilsblog/blog/3115061
码云地址:https://gitee.com/devilscode/cloud-practice/tree/ribbon-test
修改项目名称为feign-test
修改原来的子Module ribbon-service为feign-service
2、核心pom
feign-test/pom.xml
4.0.0 com.calvin.feigb feign-test pom 1.0-SNAPSHOT common-service eureka-server feign-service org.springframework.boot spring-boot-starter-parent 1.5.3.RELEASE UTF-8 UTF-8 1.8 org.springframework.cloud spring-cloud-dependencies Dalston.SR4 pom import
common-service/pom.xml
com.calvin.feigb feign-test 1.0-SNAPSHOT 4.0.0 common-service org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-maven-plugin
eureka-service/pom.xml
com.calvin.feigb feign-test 1.0-SNAPSHOT 4.0.0 eureka-server 1.8 org.springframework.cloud spring-cloud-starter-eureka-server org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin
feign-service/pom.xml
com.calvin.feigb feign-test 1.0-SNAPSHOT 4.0.0 feign-service org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-feign org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin
3、feign-service项目改造
(1)修改服务名称
这样就可以从eureka注册中心中区别出我们的服务是feign-service
server: port: 8082spring: application: name: feign-serviceeureka: client: service-url: defaultZone: http://localhost:8080/eureka/
(2)增加配置注解EurekaFeignClient
服务启动的时候,此注解的作用会使得所有使用了注解FeignClient的类,被扫描解析,然后注册到IoC容器中。
/** ** 启动类 *
* * @author Calvin * @date 2019/10/09 * @since */@EnableEurekaClient@SpringBootApplication@EnableFeignClientspublic class FeignServerApplication { public static void main(String[] args) { SpringApplication.run(FeignServerApplication.class); }}
(3)新建FeignConfiguration.java
/** *FeignClient的配置类
* * @author Calvin * @date 2019/10/21 * @since */@Configurationpublic class FeignConfiguration { /** * 覆盖默认的重试 * 重试间隔100ms * 最大重试时间1s * 最大重试次数5次 */ @Bean public Retryer feignRetryer(){ return new Retryer.Default(100,SECONDS.toMillis(1), 5); }}
关于FeignClient的相关配置,如果我们不主动去配置,都有默认配置,配置类为FeignClientsConfiguration.class,详细内容后续分析
(4)增加CommonFeignClient.java
/** ** 远程调用公共服务消费端 *
* * @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();}
(5)改造SayHiController.java
/** ** 测试接口 *
* * @author Calvin * @date 2019/10/09 * @since */@RestControllerpublic 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、启动整体项目
step1. EurekaSeverApplicaton
step2. CommonServiceApplication
step3. CommonServiceApplication2
step4. FeignServerApplication
4、调用接口测试
Eureka管理界面 http://localhost:8080/
调用 http://localhost:8082/hi
刷新页面
三、工作原理探索
1. FeignClientsConfiguration
/** * @author Dave Syer * @author Venil Noronha */@Configurationpublic class FeignClientsConfiguration { /** * 配置消息转换器 */ @Autowired private ObjectFactorymessageConverters; /** * 注入参数解析,用于解析@RequestParam */ @Autowired(required = false) private List parameterProcessors = new ArrayList<>(); /** * */ @Autowired(required = false) private List 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)步骤
通过@EnableFeignClients开启注解扫描
扫描@FeignClient注解修饰的元注解信息,使用BeanDefinitionBuilder解析成BeanDefinition,交给IoC容器中
通过JDK代理,当发现FeignClient被调用的时候,拦截该方法
拦截到该方法后,在SynchronousMethodHandler类中,使用生成的RequestTemplate,重新生成Request对象
使用HttpClient调用请求,获取Response
(2)相关代码
扫描@EnableFeignClients
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware { /** ** 检查EnableFeignClients注解是否开启,如果开启,则开始注册默认配置 *
*/ private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { MapdefaultAttrs = 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); SetbasePackages; Map 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 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 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 attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); registerClientConfiguration(registry, name, attributes.get("configuration")); registerFeignClient(registry, annotationMetadata, attributes); } } }}
解析元注解内容,注入到IoC容器中
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Mapattributes) { 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的调用
public class ReflectiveFeign extends Feign {@Override publicT newInstance(Target target) { Map nameToHandler = targetToHandlersByName.apply(target); Map methodToHandler = new LinkedHashMap (); List defaultMethodHandlers = new LinkedList (); 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有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注行业资讯频道,感谢大家的支持。