美文网首页
Spring Cloud Feign源码解析

Spring Cloud Feign源码解析

作者: __TiAmo | 来源:发表于2021-01-17 13:31 被阅读0次

    介绍

    Feign是一个声明式Web Service客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

    使用

    首先在启动类上加上注解@EnableFeignClients,这个注解是加载feign的关键

    @SpringBootApplication
    @EnableFeignClients
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
    }
    

    然后需要定义interface,并指定调用的服务,在发起调用时feign就会真正调用到指定的服务接口,非常方便简洁。

    @FeignClient(name = "feign-provider")
    public interface HelloClient {
    
        @GetMapping(value = "/test/feign/hello")
        BizResponse test();
    }
    

    feign解决了什么问题

    封装了Http调用流程,更适合面向接口化的变成习惯

    feign注册源码

    从@EnableFeignClients开始,会发现引入了FeignClientsRegistrar,通过名字可以直观的发现FeignClientsRegistrar会完成FeignClientde注册,那我们探究一下是如何注册的。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(FeignClientsRegistrar.class)
    public @interface EnableFeignClients {
    
        /**
         * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
         * declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
         * {@code @ComponentScan(basePackages="org.my.pkg")}.
         * @return the array of 'basePackages'.
         */
        String[] value() default {};
    
        /**
         * Base packages to scan for annotated components.
         * <p>
         * {@link #value()} is an alias for (and mutually exclusive with) this attribute.
         * <p>
         * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
         * package names.
         * @return the array of 'basePackages'.
         */
        String[] basePackages() default {};
    
        /**
         * Type-safe alternative to {@link #basePackages()} for specifying the packages to
         * scan for annotated components. The package of each class specified will be scanned.
         * <p>
         * Consider creating a special no-op marker class or interface in each package that
         * serves no purpose other than being referenced by this attribute.
         * @return the array of 'basePackageClasses'.
         */
        Class<?>[] basePackageClasses() default {};
    
        /**
         * A custom <code>@Configuration</code> for all feign clients. Can contain override
         * <code>@Bean</code> definition for the pieces that make up the client, for instance
         * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
         *
         * @see FeignClientsConfiguration for the defaults
         * @return list of default configurations
         */
        Class<?>[] defaultConfiguration() default {};
    
        /**
         * List of classes annotated with @FeignClient. If not empty, disables classpath
         * scanning.
         * @return list of FeignClient classes
         */
        Class<?>[] clients() default {};
    
    }
    

    进入FeignClientsRegistrar,会发现该类实现了ImportBeanDefinitionRegistrar接口,该接口的功能是可以动态的注册额外的bean到spring容器中,一般用于启动类或者配置类,通常和ResourceLoaderAware, EnvironmentAware搭配使用,下面我们具体看FeignClientsRegistrar是如何注册的

    class FeignClientsRegistrar
            implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
        // patterned after Spring Integration IntegrationComponentScanRegistrar
        // and RibbonClientsConfigurationRegistgrar
    
        private ResourceLoader resourceLoader;
    
        private Environment environment;
    
        FeignClientsRegistrar() {
        }
        
        // 省略非必要代码
        
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            // 注册默认配置
            registerDefaultConfiguration(metadata, registry);
            // 注册feign cliients
            registerFeignClients(metadata, registry);
        }
    }
    

    我们先看一个registerDefaultConfiguration(metadata, registry)的代码registry是DefaultListableBeanFactory,熟悉spring源码的同学知道这是beanFactory的默认实现类,有一系列获取bean的方法

    private void registerDefaultConfiguration(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
        // 获取@EnableFeignClients注解的配置信息
        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"));
        }
    }
    
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
                Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(
                name + "." + FeignClientSpecification.class.getSimpleName(),
                builder.getBeanDefinition());
    }
    

    回过头来咱们看registerFeignClients(AnnotationMetadata metadata,
    BeanDefinitionRegistry registry)方法,也就是注册FeignClients的具体步骤

    public void registerFeignClients(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
    
        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        // 获取注解@EnableFeignClients的信息
        Map<String, Object> attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        // A simple {@link TypeFilter} which matches classes with a given annotation,
    checking inherited annotations as well.
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        // @EnableFeignClients的属性clients为空
        if (clients == null || clients.length == 0) {
           // 获取扫描classpath下component组件的扫描器
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            // 扫描器增加要扫描的过滤器(扫描被@FeignClient注解修饰的类)
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            // 获取配置的扫描包的路径,如果没配置,默认为启动类的包路径
            Set<String> basePackages = getBasePackages(metadata);
            for (String basePackage : basePackages) {
                // 将扫描出来的@FeignClient注解修饰的类放入candidateComponents中
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }
        else {
            for (Class<?> clazz : clients) {
            // 如果clients不为空,直接将定义好的FeignClient放入candidateComponents
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        }
    
        // 遍历candidateComponents,完成FeignClient的注册
        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"));
            // 注册FeignClient
                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);
        String contextId = getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        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 = contextId + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
    
        // has a default, won't be null
        boolean primary = (Boolean) attributes.get("primary");
    
        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);
    }
    

    这个方法逻辑很清晰,设置BeanDefinition的属性,最终调用registry.registerBeanDefinition的方法完成register

    feign实例化源码

    FeignClientFactoryBean实现了FactoryBean接口来完成对象创建,
    在spring容器启动时会调用FeignClientFactoryBean的getObject()方法(只有在其他bean注入feign client时才会调用),看下FeignClientFactoryBean的getObject()方法做了哪些处理。

    @Override
    public Object getObject() throws Exception {
        return getTarget();
    }
    
    /**
     * @param <T> the target type of the Feign client
     * @return a {@link Feign} client created with the specified data and the context
     * information
     */
    <T> T getTarget() {
        // 这个FeignContext在FeignAutoConfiguration配置中已经声明了,所以可以直接用applicationContext获取bean
        FeignContext context = applicationContext.getBean(FeignContext.class);
        //配置feign 的decoder、encoder、retryer、contract、RequestInterceptor等
        //这些有默认配置,在FeignAutoConfiguration及FeignClientsConfiguration中有默认配置
        Feign.Builder builder = feign(context);
    
        // 如果feign client 没有指定url
        if (!StringUtils.hasText(url)) {
            if (!name.startsWith("http")) {
                url = "http://" + name;
            }
            else {
                url = name;
            }
            url += cleanPath();
            // 使用负载均衡处理feign 请求
            return (T) loadBalance(builder, context,
                    new HardCodedTarget<>(type, name, url));
        }
        // 如果制定了url则会直接调用url
        if (StringUtils.hasText(url) && !url.startsWith("http")) {
            url = "http://" + url;
        }
        String url = this.url + cleanPath();
        Client client = getOptional(context, Client.class);
        if (client != null) {
            if (client instanceof LoadBalancerFeignClient) {
                // not load balancing because we have a url,
                // but ribbon is on the classpath, so unwrap
                client = ((LoadBalancerFeignClient) client).getDelegate();
            }
            if (client instanceof FeignBlockingLoadBalancerClient) {
                // not load balancing because we have a url,
                // but Spring Cloud LoadBalancer is on the classpath, so unwrap
                client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
            }
            builder.client(client);
        }
        Targeter targeter = get(context, Targeter.class);
        return (T) targeter.target(this, builder, context,
                new HardCodedTarget<>(type, name, url));
    }
    

    继续进入targeter.target(this, builder, context,
    new HardCodedTarget<>(type, name, url))方法

    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
                FeignContext context, Target.HardCodedTarget<T> target) {
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        }
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
                : factory.getContextId();
        SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
        if (setterFactory != null) {
            builder.setterFactory(setterFactory);
        }
        Class<?> fallback = factory.getFallback();
        if (fallback != void.class) {
            return targetWithFallback(name, context, target, builder, fallback);
        }
        Class<?> fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {
            return targetWithFallbackFactory(name, context, target, builder,
                    fallbackFactory);
        }
    
        return feign.target(target);
    }
    

    在这个方法中会判断是不是HystrixFeign.Builder,如果是的话会添加一个降级的逻辑,感兴趣的朋友可以了解一下Hystrix。Feign是默认关闭Hystrix的,如需要打开Hystrix,需要添加配置feign.hystrix.enabled=true。之后我们看一下 feign.target(target)

    public <T> T target(Target<T> target) {
        return build().newInstance(target);
    }
    
    public <T> T newInstance(Target<T> target) {
        // 这个apply方法就是ReflectiveFeign中的apply方法,返回了每个方法的调用包装类SynchronousMethodHandler
        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)));
            }
        }
        // 返回了ReflectiveFeign.FeignInvocationHandler对象,这个对象的invoke方法其实就是调用了SynchronousMethodHandler.invoke方法
        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;
    }
    

    在这里会为每个feign client里的方法生成一个SynchronousMethodHandler对象,然后通过动态代理实现对请求拦截,并发起调用

    feign调用

    那feign client多个方法我们怎么知道调用哪个SynchronousMethodHandler呢。这里很巧妙的使用了一个Map结构,feign client的Method作为key,对应的SynchronousMethodHandler作为value,当调用Method时,就可以方便的找到它的代理方法执行了

    static class FeignInvocationHandler implements InvocationHandler {
    
        private final Target target;
        private final Map<Method, MethodHandler> dispatch;
    
        FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
            this.target = checkNotNull(target, "target");
            this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
        }
    
        @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);
        }
    }
    

    默认情况下,Feign采用的是JDK的HttpURLConnection,所以整体性能并不高,可以替换为Apache 的HttpClient或者OkHttp提高性能

    总结

    以上就介绍完了Feign的主要核心源码,代码量不多,但设计非常精妙。

    相关文章

      网友评论

          本文标题:Spring Cloud Feign源码解析

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