美文网首页
open feign源码解析

open feign源码解析

作者: 拥抱孤独_to | 来源:发表于2020-08-07 18:04 被阅读0次

    feign的基本使用

    引入openfeign依赖

           <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
    

    创建服务调用接口

    @FeignClient(value = "user-service",path = "/user")
    public interface IUserService {
    
        @PostMapping("/name")
        String getName(@RequestParam(name = "uid",defaultValue = "0") Integer uid);
    }
    

    启动类上开启feign功能,并指定调用接口所在的包
    @EnableFeignClients(basePackages = "spring.cloud.user.feign.api")
    就可以直接注入使用

         @Autowired
        private IUserService iUserService;
    
        @GetMapping("/userinfo/{uid}")
        public String getUserInfo(@PathVariable("uid") Integer uid){
            return iUserService.getName(uid);
        }
    

    feign相关配置

    日志配置

    # 调用日志级别
    @Configuration
    public class FeignConfiguration {
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    }
    

    日志等级有 4 种

    • NONE:不输出日志。
    • BASIC:只输出请求方法的 URL 和响应的状态码以及接口执行的时间。
    • HEADERS:将 BASIC 信息和请求头信息输出。
    • FULL:输出完整的请求信息。
      还需要在application.properties中额外指定
      logging.level.spring.cloud.user.feign.api=debug
      这样在调用的时候就能展示详细的信息

    拦截器配置

    public class FeignRequestInteceptor implements RequestInterceptor, ApplicationContextAware {
        private ApplicationContext applicationContext;
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
             requestTemplate.header("platform","app");
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            if (this.applicationContext == null) {
                this.applicationContext = applicationContext;
            }
        }
    }
    
    
    @Configuration
    public class FeignConfiguration {
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    
        @Bean
        RequestInterceptor requestInterceptor() {
            return new FeignRequestInteceptor();
        }
    }
    

    在调用前我们可以提前设置服务端需要的参数

    连接和请求超时时间配置

     @Bean
        public Request.Options options() {
            return new Request.Options(6000, 60000);
        }
    

    http通信框架配置

    添加依赖

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-okhttp</artifactId>
    </dependency>
    

    application.properties中添加配置

    feign.httpclient.enabled=false
    feign.okhttp.enabled=true
    

    这样就会使用okhttp通信

    压缩配置 当没有配置okhttp的时候才会生效

    # 数据传输的时候做压缩,可以提高请求效率
    feign.compression.request.enabled=true
    feign.compression.response.enabled=true
    
    # 可以配置压缩类型和最小压缩标准
    feign.compression.request.mime-types=text/xml,application/xml,application/json
    feign.compression.request.min-request-size=2048
    

    上面的配置依然可以用配置文件的方式配置

    # 链接超时时间
    feign.client.config.user-service.connectTimeout=5000
    # 读取超时时间
    feign.client.config.user-service.readTimeout=5000
    # 日志等级
    feign.client.config.user-service.loggerLevel=full
    # 重试
    feign.client.config.user-service.retryer=
    feign.client.config.user-service.requestInterceptors[0]=
    feign.client.config.user-service.requestInterceptors[1]=
    

    feign源码

    feign源码一切起源于@EnableFeignClients
    该注解装配了@Import(FeignClientsRegistrar.class)
    所以我们要看FeignClientsRegistrar这个类

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            #注册默认的配置
            registerDefaultConfiguration(metadata, registry);
            #扫描并注入带有FeignClient注解的类
            registerFeignClients(metadata, registry);
        }
    
        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);
        }
    

    我们发现@FeignClient注解的接口都会被装载成FeignClientFactoryBean对象,而该对象又是一个FactoryBean对象,所以最终ioc中Bean就是该对象的getObeject方法的返回值

        @Override
        public Object getObject() throws Exception {
            return getTarget();
        }
    
        <T> T getTarget() {
            FeignContext context = applicationContext.getBean(FeignContext.class);
            Feign.Builder builder = feign(context);
    
            if (!StringUtils.hasText(this.url)) {
                String url;
                if (!this.name.startsWith("http")) {
                    url = "http://" + this.name;
                }
                else {
                    url = this.name;
                }
                url += cleanPath();
                return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                        this.name, url));
            }
    

    openfeign自动装配了FeignRibbonClientAutoConfiguration FeignAutoConfiguration两个

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
    org.springframework.cloud.openfeign.FeignAutoConfiguration,\
    

    FeignAutoConfiguration 类中,又帮我们转载了一些默认类

    @Configuration
    @ConditionalOnClass(Feign.class)
    @EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
    public class FeignAutoConfiguration {
    
        @Autowired(required = false)
        private List<FeignClientSpecification> configurations = new ArrayList<>();
    
    
        @Bean
        public FeignContext feignContext() {
            FeignContext context = new FeignContext();
            context.setConfigurations(this.configurations);
            return context;
        }
    
        @Configuration
        @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
        protected static class DefaultFeignTargeterConfiguration {
            @Bean
            @ConditionalOnMissingBean
            public Targeter feignTargeter() {
                return new DefaultTargeter();
            }
        }
    }
    

    因此,我们回过头来看

       <T> T getTarget() {
            FeignContext context = applicationContext.getBean(FeignContext.class);
            Feign.Builder builder = feign(context);
    
            if (!StringUtils.hasText(this.url)) {
                String url;
                if (!this.name.startsWith("http")) {
                    url = "http://" + this.name;
                }
                else {
                    url = this.name;
                }
                url += cleanPath();
                return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                        this.name, url));
            }
        }
        protected Feign.Builder feign(FeignContext context) {
            FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
            Logger logger = loggerFactory.create(this.type);
    
            // @formatter:off
            Feign.Builder builder = get(context, Feign.Builder.class)
                    // required values
                    .logger(logger)
                    .encoder(get(context, Encoder.class))
                    .decoder(get(context, Decoder.class))
                    .contract(get(context, Contract.class));
            // @formatter:on
    
            configureFeign(context, builder);
    
            return builder;
        }
    
        protected void configureFeign(FeignContext context, Feign.Builder builder) {
            FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
            if (properties != null) {
                if (properties.isDefaultToProperties()) {
                    configureUsingConfiguration(context, builder);
                    configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
                    configureUsingProperties(properties.getConfig().get(this.name), builder);
                } else {
                    configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
                    configureUsingProperties(properties.getConfig().get(this.name), builder);
                    configureUsingConfiguration(context, builder);
                }
            } else {
                configureUsingConfiguration(context, builder);
            }
        }
    

    在这里我们看到了feignclient配置的装载,先装在@EnableFeignClients注解中配置的defaultConfiguration,然后在加载@FeignClient注解中配置的configuration,最后加载application.properties中配置的。

    同时还将url组装成了http://user-service/user

        protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                HardCodedTarget<T> target) {
            Client client = getOptional(context, Client.class);
            if (client != null) {
                builder.client(client);
                Targeter targeter = get(context, Targeter.class);
                return targeter.target(this, builder, context, target);
            }
    
            throw new IllegalStateException(
                    "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
        }
    

    这里的ClientLoadBalancerFeignClient,在自动装配中FeignRibbonClientAutoConfiguration类中import进来的,而Targeter则是DefaultTargeter

    @Configuration
    class DefaultFeignLoadBalancedConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                                  SpringClientFactory clientFactory) {
            return new LoadBalancerFeignClient(new Client.Default(null, null),
                    cachingFactory, clientFactory);
        }
    }
    
    
        public <T> T target(Target<T> target) {
          return build().newInstance(target);
        }
    
        public Feign build() {
          SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
              new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                                   logLevel, decode404, closeAfterDecode);
          ParseHandlersByName handlersByName =
              new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                                      errorDecoder, synchronousMethodHandlerFactory);
          return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
        }
    
      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;
      }
    
    

    最终我们看到这些@FeignClient注解的接口被定义成了MethodHandler并且保存到了Map<Method, MethodHandler>中,而该MethodHandler则是SynchronousMethodHandler
    最终又将该信息存入到代理类中,并且返回代理类FeignInvocationHandler

      @Override
        public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
          return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
        }
    

    到这里,我们已经知道了我们注入使用的接口实际上是FeignInvocationHandler,而我们的@FeignClient的定义接口实际上是为了通过该类获取我们一个http请求所需的信息,包括方法,调用接口路径,请求方式,返回值,参数等等这些信息,并且组装成了一个个的SynchronousMethodHandler,等我们调用接口方法的时候,就会获取对应的SynchronousMethodHandler处理请求。

    继续来到这个代理类

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          if ("equals".equals(method.getName())) {
            try {
              Object
                  otherHandler =
                  args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
              return equals(otherHandler);
            } catch (IllegalArgumentException e) {
              return false;
            }
          } else if ("hashCode".equals(method.getName())) {
            return hashCode();
          } else if ("toString".equals(method.getName())) {
            return toString();
          }
          return dispatch.get(method).invoke(args);
        }
    
      @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;
          }
        }
      }
    
     Object executeAndDecode(RequestTemplate template) throws Throwable {
        Request request = targetRequest(template);
    
        if (logLevel != Logger.Level.NONE) {
          logger.logRequest(metadata.configKey(), logLevel, request);
        }
    
        Response response;
        long start = System.nanoTime();
        try {
          response = client.execute(request, options);
          // ensure the request is set. TODO: remove in Feign 10
          response.toBuilder().request(request).build();
        } catch (IOException e) {
          if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
          }
          throw errorExecuting(request, e);
        }
    

    这里创建了一个RequestTemplate

      Request targetRequest(RequestTemplate template) {
        for (RequestInterceptor interceptor : requestInterceptors) {
          interceptor.apply(template);
        }
        return target.apply(new RequestTemplate(template));
      }
    

    可以看到,我们的拦截器实际上就是在这里被调用了。

        @Override
        public Response execute(Request request, Request.Options options) throws IOException {
            try {
                URI asUri = URI.create(request.url());
                String clientName = asUri.getHost();
                URI uriWithoutHost = cleanUrl(request.url(), clientName);
                FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                        this.delegate, request, uriWithoutHost);
    
                IClientConfig requestConfig = getClientConfig(options, clientName);
                return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
                        requestConfig).toResponse();
            }
            catch (ClientException e) {
                IOException io = findIOException(e);
                if (io != null) {
                    throw io;
                }
                throw new RuntimeException(e);
            }
        }
    

    最终由RequestTemplate创建了Request请求对象来到LoadBalancerFeignClient.execute方法

    
        private FeignLoadBalancer lbClient(String clientName) {
            return this.lbClientFactory.create(clientName);
        }
    
        public FeignLoadBalancer create(String clientName) {
            FeignLoadBalancer client = this.cache.get(clientName);
            if(client != null) {
                return client;
            }
            IClientConfig config = this.factory.getClientConfig(clientName);
            ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
            ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
            client = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
                loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
            this.cache.put(clientName, client);
            return client;
        }
    

    这里我们可以看到,由于feign包中默认引入了ribbon自动装配包,所以ILoadBalancer就是ZoneAwareLoadBalancer,并且被封装了成FeignLoadBalancer返回。

      public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
            LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
    
            try {
                return command.submit(
                    new ServerOperation<T>() {
                        @Override
                        public Observable<T> call(Server server) {
                            URI finalUri = reconstructURIWithServer(server, request.getUri());
                            S requestForServer = (S) request.replaceUri(finalUri);
                            try {
                                return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                            } 
                            catch (Exception e) {
                                return Observable.error(e);
                            }
                        }
                    })
                    .toBlocking()
                    .single();
            } catch (Exception e) {
                Throwable t = e.getCause();
                if (t instanceof ClientException) {
                    throw (ClientException) t;
                } else {
                    throw new ClientException(e);
                }
            }
            
        }
    
    Observable<T> o = 
                    (server == null ? selectServer() : Observable.just(server))
                    .concatMap(new Func1<Server, Observable<T>>() {
    
       private Observable<Server> selectServer() {
            return Observable.create(new OnSubscribe<Server>() {
                @Override
                public void call(Subscriber<? super Server> next) {
                    try {
                        Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);   
                        next.onNext(server);
                        next.onCompleted();
                    } catch (Exception e) {
                        next.onError(e);
                    }
                }
            });
        }
    
           ILoadBalancer lb = getLoadBalancer();
            if (host == null) {
                // Partial URI or no URI Case
                // well we have to just get the right instances from lb - or we fall back
                if (lb != null){
                    Server svc = lb.chooseServer(loadBalancerKey);
    

    可以看到Server就是用ribbon负载均衡器获取的,所以到这里又进入了ribbon的处理逻辑了。

    总结

    feign 实际上就是通过接口定义的形式,帮我们生成了一个代理类,该代理类中包含每个方法对应的http请求信息,然后通过调用方法获取对应的信息并且生成一个http://user-service/name 对应的url,然后集成ribbon将服务名解析成具体的host地址,最后执行http请求

    相关文章

      网友评论

          本文标题:open feign源码解析

          本文链接:https://www.haomeiwen.com/subject/mfmfdktx.html