文章目录
工程版本
<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 继承了 ImportBeanDefinitionRegistrar,ImportBeanDefinitionRegistrar 类的功能就是将所需要初始化的 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容器中的是FeignClientFactoryBean,FeignClientFactoryBean实现了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);
}
}

被折叠的 条评论
为什么被折叠?



