@EnableFeignClients 原理

工程版本

<spring-boot.version>2.2.6.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
<spring-cloud.version>Hoxton.SR4</spring-cloud.version>

该文章的一些基础原理在Feign源码分析分析过了,因此这里就不详细说明了。

EnableFeignClients

@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这个类

FeignClientsRegistrar

FeignClientsRegistrar 继承了 ImportBeanDefinitionRegistrarImportBeanDefinitionRegistrar 类的功能就是将所需要初始化的 BeanDefinition 注入到容器中。

Spring 容器过启动程中根据 BeanDefinition 的内容完成对类的初始化,并注入到容器中。

所以 FeignClientsRegistrar 的最终目的就是将生成的代理类注入到 Spring 容器中。

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
    @Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        // 1. 注册 EnableFeignClients 的配置类
        // 注册一个feign客户端配置bean,可以用于注册针对所有feign客户端的缺省配置的注册
		registerDefaultConfiguration(metadata, registry);
        // 2. 注册具有 FeignClient 注解的类
        // 针对每个feign客户端的专有配置的注册
		registerFeignClients(metadata, registry);
	}
}

注册 EnableFeignClients 的配置类

// 注册 EnableFeignClients 的配置类
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();
        }
        // defaultAttrs.get("defaultConfiguration")是获取配置类
        // 这里是将配置类注入到容器中
        registerClientConfiguration(registry, name,
                                    defaultAttrs.get("defaultConfiguration"));
    }
}

注册具有 FeignClient 注解的类

// 注册具有 FeignClient 注解的类
public void registerFeignClients(AnnotationMetadata metadata,
                                 BeanDefinitionRegistry registry) {

    // 这里的 scanner 是用来收集指定类路径下的Bena信息的;
    ClassPathScanningCandidateComponentProvider scanner = getScanner();

    scanner.setResourceLoader(this.resourceLoader);

    // 扫描具有 @FeignClient 注解的路径
    Set<String> basePackages;

    // 获取 EnableFeignClients 注解的属性信息
    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");

    // clients 属性是用来设置被 @FeignClient 注解标注的类
    if (clients == null || clients.length == 0) {
        // 如果 clients 为空

        // 往 scanner 中新增一个AnnotationTypeFilter
        // 相当于scanner扫描的时候只扫描具有 FeignClient 注解的类
        scanner.addIncludeFilter(annotationTypeFilter);
        // 根据 EnableFeignClients 注解信息获取basePackages
        // 如果 EnableFeignClients 注解中没有设置扫描路径,默认把启动类所在包路径作为扫描路径
        basePackages = getBasePackages(metadata);
    }
    else {
        // 如果 clients 不为空则直接根据 clients 类路径设置 basePackages
        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);
            }
        };
        // 设置哪些Bean是需要被扫描的
        scanner.addIncludeFilter(
            new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    }
    
    // 遍历 basePackages
    // 使用 scanner 扫描每一个 basePackage, 获取 feign 客户端定义, 
	// 也就是 @FeignClient 定义的那些接口
    for (String basePackage : basePackages) {
        // 根据  basePackage 获取满足扫描条件 BeanDefinition ;
        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");

                // 获取 FeignClient 注解中的属性信息
                Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(
                    FeignClient.class.getCanonicalName());

                String name = getClientName(attributes);

                // 注册 FeignClient 的配置信息到容器中
                registerClientConfiguration(registry, name,
                                            attributes.get("configuration"));

                // 根据 BeanDefinition 生成 BeanDefinitionBuilder,注册到容器中
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    // 获取接口名
    String className = annotationMetadata.getClassName();
    //从 @FeignClient 获取信息并设置到 BeanDefinitionBuilder 中;
    // BeanDefinitionBuilder 注册的类是 FeignClientFactoryBean ,Spring 会根据 FeignClientFactoryBean 生成对象注入到容器中
    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();

    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);
}

注册到Spring容器中的是FeignClientFactoryBeanFeignClientFactoryBean实现了FactoryBean接口。FactoryBean是一个工厂Bean,可以生成某一个类型Bean实例,它最大的一个作用是:可以让我们自定义Bean的创建过程。通过FeignClientFactoryBean#getObject()来获取实例对象。FeignClientFactoryBean用于生成feign客户端代理对象。

FeignClientFactoryBean

根据上面的分析可以知道,我们定义的feign客户端和相关配置会以bean定义的形式注册到Spring容器中,这样当使用@Autowired注入一个feign客户端时,容器会使用工厂类FeignClientFactoryBean为其生成一个实例。

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

	private Class<?> type;
	private String name;
	private String url;
	private String contextId;
	private String path;
	private boolean decode404;
	private ApplicationContext applicationContext;
	private Class<?> fallback = void.class;
	private Class<?> fallbackFactory = void.class;
    
    // 这里是获取Bean实例
    @Override
	public Object getObject() throws Exception {
		return getTarget();
	}
    
    <T> T getTarget() {
        // 从应用上下文中获取创建 feign 客户端的上下文对象 FeignContext
        // FeignContext 针对每个feign客户端定义会生成一个不同的 AnnotationConfigApplicationContext,
        // 这些应用上下文的parent都设置为当前应用的主应用上下文
        // Ribbon的处理方式也是这样的,这样可以达到针对每个客户端都能设置不同的配置
        // 参考Feign的自动配置类 : FeignAutoConfiguration
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
        
        // 为目标feign客户端对象构建一个 builder,该builder最终生成的feign客户端是一个
        // 动态代理对象,使用 InvocationHandler : ReflectiveFeign$FeignInvocationHandler
		Feign.Builder builder = feign(context);
        // 1. 处理没有URL的情况
		if (!StringUtils.hasText(this.url)) {
            // 根据属性 name 拼接 url,
            // 这种通常是需要在多个服务节点之间进行负载均衡的情况
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
            
           // 函数 loadBalance 做如下动作 :
           // 1. 设置Feign.Builder.client 为 LoadBalancerFeignClient
           // LoadBalancerFeignClient 是 feign.Client的子类
           // feign.Client是Feign用来发送请求的接口
           // 2. 使用一个 HystrixTargeter 将 builder 和一个 HardCodedTarget 实例关联起来
           // 这里 HardCodedTarget 表示对应 url 为 http://app-service
           // 3. 生成最终的feign client 实例 : ReflectiveFeign$FeignInvocationHandler 的动态代理对象
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
        // 2. 处理含有URL的情况
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
            // 因为指定了明确的服务节点url,所以这里不需要负载均衡,
			// 尽管这里client是LoadBalancerFeignClient,
			// 但是 ((LoadBalancerFeignClient) client).getDelegate(); 这里返回的还是Feign默认的Client,这就相当于去掉了负载均衡的功能
			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<>(this.type, this.name, url));
	} 
}

loadBalance

这个方法使得Feign客户端具有负载均衡的功能,默认情况下SpringCloud Feign与Ribbon结合实现负载均衡功能。


protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
    // 这里获取到 LoadBalancerFeignClient
    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?");
}

LoadBalancerFeignClient

public class LoadBalancerFeignClient implements Client {
    
    private CachingSpringLoadBalancerFactory lbClientFactory;
    
    // 执行请求的地方
    @Override
	public Response execute(Request request, Request.Options options) throws IOException {
		try {
            // 这里是构建请求地址
            // asUri: http://app-service/app/2020
			URI asUri = URI.create(request.url());
            // clientName = app-service
			String clientName = asUri.getHost();
            // http:///app/2020
			URI uriWithoutHost = cleanUrl(request.url(), clientName);
            
            // 构建 RibbonRequest 对象
			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
					this.delegate, request, uriWithoutHost);
            
            // 设置超时时间connectTimeout、readTimeout
			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);
		}
	}
    
    // 这里是获取 FeignLoadBalancer
    // FeignLoadBalancer 里面有 ILoadBalancer
    private FeignLoadBalancer lbClient(String clientName) {
        // create方法比较简单
        // 1. 从缓存中获取 clientName 对应的 FeignLoadBalancer,如果存在则直接返回
        // 2. 缓存中不存在
        // 通过 SpringClientFactory 获取 clientName 对应的 ILoadBalancer 和 IClientConfig
        // 然后将 ILoadBalancer 和 IClientConfig封装成 FeignLoadBalancer 
        // 存入缓存并返回
		return this.lbClientFactory.create(clientName);
	}
}

看到这里可以发现,FeignLoadBalancer#executeWithLoadBalancer内部实现负载均衡。

FeignLoadBalancer

public class FeignLoadBalancer extends
		AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
    
}

FeignLoadBalancer继承了AbstractLoadBalancerAwareClient,executeWithLoadBalancer方法在抽象类AbstractLoadBalancerAwareClient

public abstract class AbstractLoadBalancerAwareClient<S extends ClientRequest, T extends IResponse> extends LoadBalancerContext implements IClient<S, T>, IClientConfigAware {
    public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        // 构建一个 LoadBalancerCommand 对象
        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);
            }
        }
    }
}

该方法构造了一个LoadBalancerCommand对象, 往LoadBalancerCommand的submit方法传了一个ServerOperation对象。

public class LoadBalancerCommand<T> {
   
     public Observable<T> submit(final ServerOperation<T> operation) {
        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);
                }
            }
        });
    }
                           
}

继续往下看loadBalancerContext.getServerFromLoadBalancer方法

public class LoadBalancerContext implements IClientConfigAware {
    
    public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
        // original: http:///app/2020
        // 这里的 original 是没有host
        String host = null;
        int port = -1;
        if (original != null) {
            host = original.getHost();
        }
        if (original != null) {
            Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);        
            port = schemeAndPort.second();
        }

        ILoadBalancer lb = getLoadBalancer();
        
        if (host == null) {
            if (lb != null){
                // 通过 ILoadBalancer 来获取服务地址
                Server svc = lb.chooseServer(loadBalancerKey);
                if (svc == null){
                    throw new ClientException(ClientException.ErrorType.GENERAL,
                            "Load balancer does not have available server for client: "
                                    + clientName);
                }
                host = svc.getHost();
                if (host == null){
                    throw new ClientException(ClientException.ErrorType.GENERAL,
                            "Invalid Server for :" + svc);
                }
                logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
                // 返回服务信息
                return svc;
            } else {
                // 省略代码......
            }
        }
    }
    
}

看到这里Feign与Ribbon的结合分析也就结束了,返回请求url信息,然后得到response结果:

自动配置类

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
		matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
		OkHttpFeignLoadBalancedConfiguration.class,
		DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {

	@Bean
	@Primary
	@ConditionalOnMissingBean
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	public CachingSpringLoadBalancerFactory cachingLBClientFactory(
			SpringClientFactory factory) {
        // SpringClientFactory 是用来获取 Ribbon 客户端的
		return new CachingSpringLoadBalancerFactory(factory);
	}

	@Bean
	@Primary
	@ConditionalOnMissingBean
	@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
	public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
			SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
		return new CachingSpringLoadBalancerFactory(factory, retryFactory);
	}

	@Bean
	@ConditionalOnMissingBean
	public Request.Options feignRequestOptions() {
		return LoadBalancerFeignClient.DEFAULT_OPTIONS;
	}
}

这个自动配置类注入了DefaultFeignLoadBalancedConfiguration,代码如下所示。

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
			SpringClientFactory clientFactory) {
		return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
				clientFactory);
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值