springcloud openfeign原理-FeignClient初始化以及代理对象的创建

必备知识:ImportBeanDefinitionRegistrar方式实现Bean的动态装载

1.FeignClient初始化

在ImportBeanDefinitionRegistrar方式实现Bean的动态装载 一文中,@EnableFeignClients 这个注解的作用是开启feignClient的扫描,注解定义如下:

package org.springframework.cloud.openfeign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@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.
	 * @return the array of 'basePackages'.
	 */
	String[] basePackages() default {};

	/**
	 * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. 
	 * @return the array of 'basePackageClasses'.
	 */
	Class<?>[] basePackageClasses() default {};

	/**
	 * A custom <code>@Configuration</code> for all feign clients
	 * @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 {};
}

其中**@Import(FeignClientsRegistrar.class)** 导入了一个配置类FeignClientsRegistrar,这个类实现了ImportBeanDefinitionRegistrar接口,那么必然要重写接口中的registerBeanDefinitions 方法:

//AnnotationMetadata  metadata:注解中的元数据
@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);    // 注册默认的配置信息,
		registerFeignClients(metadata, registry);            // 注册不同包下所有的FeignClient
	}

这个方法里面又做了两件事:

  • registerDefaultConfiguration(metadata, registry)
  • 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);
				}
			}
		}
	}

上面代码中的for循环里遍历了扫描到的每个包,找到加了@FeignClient注解的接口,然后迪用下面的方法去注册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);
	}

BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class) 这行代码,使用到了FeignClientFactoryBean, 这是一个工厂Bean (class FeignClientFactoryBean
implements FactoryBean
, InitializingBean, ApplicationContextAware )
FactoryBean有个特点,就是如果需要获得这个bean的真正的实例,只有一个方法,就是通过 getObject方法来实现


public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

因此,FeignClientFactoryBean中的getObject()方法一定会被调用到,它的实现如下,最终创建了一个复杂的bean进行返回:

    @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中自动装配了一个FeignContext的bean,因此这里可以直接用
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
			
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.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) {
			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));
	}

2.创建代理对象

在这里插入图片描述
FeignContext继承了NamedContextFactory, 会根据serviceId对不同的FeignContext进行隔离:
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
源码中是在FeignAutoConfiguration中自动装配了一个FeignContext的bean:
在这里插入图片描述
而参数中的configurations是一个FeignClientSpecification 的list,实现了不同context对应不同的configuration的隔离

  • Feign.Builder builder = feign(context) 的实现:
protected Feign.Builder feign(FeignContext context) {
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(this.type);

		// @formatter:off     Feign.Builder这个静态内部类中做了一些参数的初始化
		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;
	}

  • loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url))
	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?");
	}

Client client = getOptional(context, Client.class) 这个client是怎么来的,具体实现是哪个呢?
在FeignRibbonClientAutoConfiguration 这个自动装配类中,导入了一个配置类DefaultFeignLoadBalancedConfiguration(默认的feign的负载均衡配置类), 这个配置类中自动装配返回的是LoadBalancerFeignClient 这个对象的实例,因此getOptional 返回的就是 LoadBalancerFeignClient
在这里插入图片描述

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {

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

targeter.getTarget:

class DefaultTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}
}

feign.target(target)实现如下:
在这里插入图片描述
build()返回的是ReflectiveFeign,因此newInstance 是在ReflectiveFeign中实现的,因此查看newInstance时,选择ReflectiveFeign
在这里插入图片描述
实现如下:

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

可以看到,这段代码里面,创建了代理对象,被代理对象就是参数中的handler

 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler);

因此org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject 这个方法,最终返回的就是一个代理对象,在代码中debug如下:
在这里插入图片描述
既然是在ReflectiveFeign中生成的代理对象,那么必然会有一个invoke方法来实现代理对象方法的调用:
在这里插入图片描述
这里调用了dispatch的方法,上面debug截图中可以看到dispatch是SynchronousMethodHandler, 之所以 要使用dispatch.get(method).invoke(args) ,是因为,feign接口中不止一个方法,dispatch是一个map, map的key是feign接口中的方法名,value是每个接口对应的具体的处理器,而dispatch的初始化在 ReflectiveFeign#newInstance这几行代码中:

Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
InvocationHandler handler = factory.create(target, methodToHandler);

重点关注Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); 这个apply方法:
在这里插入图片描述
拿到了feign接口中的方法的元注解的值,包括方法名,请求方法,接口名,但是还是没有看到请求地址是在哪里拼装的,回到feign.SynchronousMethodHandler#invoke 方法中,这个方法又调用了feign.SynchronousMethodHandler#executeAndDecode 方法:
可以看到request对象中的url还是服务名,http://order-service/orders ,而下一个断点中的client是LoadBalancerFeignClient在这里插入图片描述
进入LoadBalancerFeignClient 的execute方法,进入到了ribbon负载均衡的创建逻辑:
在这里插入图片描述
进入executeWithLoadBalancer方法,可以看到真正的请求url拼装是在reconstructURIWithServer方法中去完成的:
在这里插入图片描述

本文示例代码:https://download.csdn.net/download/weixin_41300437/13068346

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页