Spring Cloud Feign源码解析

介绍

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.
     * 

* {@link #value()} is an alias for (and mutually exclusive with) this attribute. *

* 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. *

* 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 @Configuration for all feign clients. Can contain override * @Bean 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 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 candidateComponents = new LinkedHashSet<>();
    // 获取注解@EnableFeignClients的信息
    Map 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 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 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 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  the target type of the Feign client
 * @return a {@link Feign} client created with the specified data and the context
 * information
 */
 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 target(FeignClientFactoryBean factory, Feign.Builder feign,
            FeignContext context, Target.HardCodedTarget 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 target(Target target) {
    return build().newInstance(target);
}

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

    FeignInvocationHandler(Target target, Map 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源码解析)