千家信息网

SpringCloud中怎么声明式调用Feign

发表于:2025-02-02 作者:千家信息网编辑
千家信息网最后更新 2025年02月02日,今天就跟大家聊聊有关SpringCloud中怎么声明式调用Feign,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Feign声明式调用一、Fe
千家信息网最后更新 2025年02月02日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

  1. 修改项目名称为feign-test

  2. 修改原来的子Module ribbon-service为feign-service

2、核心pom


feign-test/pom.xml
4.0.0com.calvin.feigbfeign-testpom1.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-SNAPSHOT4.0.0common-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-SNAPSHOT4.0.0eureka-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-SNAPSHOT4.0.0feign-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 ObjectFactory messageConverters;        /**         * 注入参数解析,用于解析@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)步骤
  1. 通过@EnableFeignClients开启注解扫描

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

  3. 通过JDK代理,当发现FeignClient被调用的时候,拦截该方法

  4. 拦截到该方法后,在SynchronousMethodHandler类中,使用生成的RequestTemplate,重新生成Request对象

  5. 使用HttpClient调用请求,获取Response

(2)相关代码
扫描@EnableFeignClients
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,                ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {        /**         * 

* 检查EnableFeignClients注解是否开启,如果开启,则开始注册默认配置 *

*/ private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { Map 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 basePackages;        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, Map 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的调用
public class ReflectiveFeign extends Feign {@Override  public  T 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有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注行业资讯频道,感谢大家的支持。

0