【Spring Cloud】OpenFeign之@EnableFeignClients(1)注册默认配置

四、RESTful客户端 - OpenFeign

OpenFeignSpring Cloud 在原有 NetflixFeign 的基础之上改造成自己的远程调用组件。

OpenFeign 是个声明式 RESTful 网络请求客户端,使得编写 Web 服务客户端更加方便和快捷,只需要使用 OpenFeign 提供的注解修饰定义网络请求的接口类,就可以使用该接口的实例发送 RESTful 风格的网络请求 。OpenFeign 还可以集成 RibbonHystrix 来提供负载均衡和网络断路器的功能。

RESTful 网络请求是指 RESTful 风格的网络请求,其中 RESTResource Representational State Transfer 的缩写,直接翻译即“资源表现层状态转移”。Resource 代表互联网资源。所谓“资源”是网络上的一个实体,或者说网上的一个具体信息。 它可以是一段文本、一首歌曲、一种服务,可以使用一个 URI 指向它,每种“资源”对应一个 URI。Representational 是“表现层”意思。 “资源”是一种消息实体,它可以有多种外在的表现形式,我们把“资源”具体呈现出来的形式叫作它的“表现层”。 比如说文本可以用 TXT 格式进行表现,也可以使用 XML 格式、 JSON 格式和二进制格式;视频可以用 MP4 格式表 现,也可以用 AVI 格式表现。 URI 只代表资源的实体,不代表它的形式 。它的具体表现形 式,应该由 HTTP 请求的头信息 AcceptContent-Type 字段指定,这两个字段是对“表现层”的描述。 State Transfer 是指“状态转移”。 客户端访问服务的过程中必然涉及数据和状态的转化。 如果客户端想要操作服务端资源,必须通过某种手段,让服务器端资源发生“状态转移”。 而这种转化是建立在表现层之上的,所以被称为 “表现层状态转移”。 客户端通过使 HTTP 协议中的四个动词来实现上述操作,它们分别是:获取资源的 GET、 新建或更新资源的 POST 、更新资源的 PUT 和删除资源的 DELETE

4.1、OpenFeign简介

首先先贴上 OpenFeignGitHub地址。

OpenFeign 是一个声明式、模板化RESTful 网络请求客户端。 OpenFeign 会根据带有注解的函数信息构建出网络请求的模板,在发送网络请求之前,OpenFeign 会将函数的参数值设置到这些请求模板中。

Spring Cloud 中,使用 OpenFeign 非常简单——创建一个接口,并在接口上添加一些注解即可。除了使用 SpringMVC 的注解之外,OpenFeign 也支持多种自带的注解。

使用 OpenFeignSpring 应用架构一般分为三个部分,分别为服务注中心、服务提供者和服务消费者。服务提供者向服务注册中心注册自己,然后服务消费者通过 OpenFeign 发送请求时, OpenFeign 会向服务注册中心获取关于服务提供者的信息,然后再向服务提供者发送网络请求。

OpenFeign调用架构图


4.2、核心

在阅读源码的时候,可以考虑以下两点:

  • @FeignClient 注解修饰的接口类如何创建,也就是其Bean实例如何被创建到容器中。
  • 在调用 @FeignClient 对象的网络请求相关函数时,OpenFeign 是如何发送网络请求的。

OpenFeign源码流程图


4.3、源码分析

4.3.1、注解:@EnableFeignClients

@EnableFeignClients 类似 @EnableEurekaServer 注解,其作用就像是 OpenFeign 的开关一样,一切 OpenFeign 的相关操作都是从它开始的。@EnableFeignClients 有几个作用,一个是引入了 FeignClientsRegistrar;二是指定扫描了 FeignClient 的包信息,就是指定了 FeignClient 接口类所在的包名;三是指定了 FeignClient 接口类的自定义配置类。

@EnableFeignClients 注解的定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

    //下面三个函数都是为了指定需要扫描的包
	String[] value() default {};
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};

    //指定自定义feignclient的自定义配置,可以配置Decoder、Encoder、Contract等组件,FeignClientsConfiguration是默认的配置
	Class<?>[] defaultConfiguration() default {};

	Class<?>[] clients() default {};

}

在上述注解定义中,引入了 FeignClientsRegistrar 组件,而该类是 ImportBeanDefinitionRegistrar 的子类,用于动态注册 BeanDefinition。所以 OpenFeign 通过 FeignClientsRegistrar 来处理 @FeignClient 注解修饰的 FeignClient 接口类,将这些接口类的 BeanDefinition 注册到 Spring 容器中,这样就可以使用 @Autowired 等方式来自动装载这些 FeignClient 接口类的 Bean 实例。

FeignClientsRegistrar 的部分代码如下:

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    //other...
    
    @Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //从@EnableFeignClients注解的属性值来构建Feign的自定义Configuration进行注册
		registerDefaultConfiguration(metadata, registry);
        //扫描package,注册被@FeignClient注解修饰的接口类的bean信息
		registerFeignClients(metadata, registry);
	}
}

如上述代码所示,FeignClientsRegistrarregisterBeanDefinitions 方法主要做了两个事情, 一是注册 @EnableFeignClients 提供的自定义配置类 中的相关Bean实例, 二是根据 @EnableFeignClients 提供的包信息扫描 @FeignClient 注解修饰的 FeignClient 接口类,然后进行 Bean 实例注册。

4.3.1.1、注册默认配置
//FeignClientsRegistrar#registerDefaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    //获取@EnableFeignClients注解的属性键值对
    Map<String, Object> defaultAttrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

    //如果配置了@EnableFeignClients的defaultConfiguration属性,则进行注册该属性对应的Configuration
    //后期使用default {}
    if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
        String name;
        //判断是否是内部类
        if (metadata.hasEnclosingClass()) {
            name = "default." + metadata.getEnclosingClassName();
        }
        else {
            //name="default" + "." + @EnableFeignClients修饰的类路径
            name = "default." + metadata.getClassName();
        }
        //注册defaultConfiguration指定的Configuration配置类
        registerClientConfiguration(registry, name,
                                    defaultAttrs.get("defaultConfiguration"));
    }
}

如上述代码所示, registerDefaultConfiguration 方法会判断 @EnableFeignClients 注解是否设置了 defaultConfiguration, 如果有,则将调用 registerClientConfiguration 方法,进行 BeanDefinitionRegistry 的注册。

//FeignClientsRegistrar#registerClientConfiguration
private void registerClientConfiguration(BeanDefinitionRegistry registry, 
                                         Object name, Object configuration) {
    //使用BeanDefinitionBuilder来生成BeanDefinition,并注册到registry中
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientSpecification.class);
    //添加构造函数的参数
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(configuration);
    registry.registerBeanDefinition(
        name + "." + FeignClientSpecification.class.getSimpleName(),
        builder.getBeanDefinition());
}

BeanDefinitionRegistrySpring 框架中用于动态注册 BeanDefinition 信息的接口,调用其registerBeanDefinition 方法可以将 BeanDefinition 注册到 Spring 容器中,其中 name属性就是注册 BeanDefinition 的名称,类似:default.xxx.FeignClientSpecification,这里的xxx一般为使用 @EnableFeignClients 修饰的类的全路径。

重点FeignClientSpecification 类实现了 NamedContextFactory.Specification 接口,Spring Cloud 框架使用 NamedContextFactory 来创建一系列的运行上下文(ApplicationContext),来让对应的 Specification 在这些上下文中创建实例对象,这样使得各个子上下文中的实例对象相互独立,互不影响,可以方便地通过子上下文管理一系列不同的实例对象。

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {

	private final String propertySourceName;

	private final String propertyName;

    //每个name对应一个ApplicationContext
	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

    //每个name对应一个Specification
	private Map<String, C> configurations = new ConcurrentHashMap<>();

    //父ApplicationContext
	private ApplicationContext parent;

	private Class<?> defaultConfigType;

	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

	@Override
	public void setApplicationContext(ApplicationContext parent) throws BeansException {
		this.parent = parent;
	}

    //在FeignAutoConfiguration#feignContext方法会调用
	public void setConfigurations(List<C> configurations) {
		for (C client : configurations) {
			this.configurations.put(client.getName(), client);
		}
	}

	public Set<String> getContextNames() {
		return new HashSet<>(this.contexts.keySet());
	}

    //实现了DisposableBean接口,在对象消亡时会调用destroy方法来销毁bean实例
	@Override
	public void destroy() {
		Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
		for (AnnotationConfigApplicationContext context : values) {
			// This can fail, but it never throws an exception (you see stack traces
			// logged as WARN).
			context.close();
		}
		this.contexts.clear();
	}

    //根据name获取ApplicationContext,如果不存在,则先创建一个AnnotationConfigApplicationContext上下文对象,并存储保存,否则直接返回name对应的AnnotationConfigApplicationContext上下文对象
	protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}

    //创建与name对应的AnnotationConfigApplicationContext子上下文中,并在其注册对应的配置等
	protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
                //在子上下文中注册与name关联的Configuration(独立)
				context.register(configuration);
			}
		}
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
                //在子上下文中注册默认的Configuration(共用)
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
        //注册PropertyPlaceholderAutoConfiguration和FeignClientsConfiguration配置类
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
            //所有的context的parent相同,这样一些相同的bean可以通过parent的context来获取
			context.setParent(this.parent);
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			context.setClassLoader(this.parent.getClassLoader());
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}

	protected String generateDisplayName(String name) {
		return this.getClass().getSimpleName() + "-" + name;
	}

    //从name对应的AnnotationConfigApplicationContext获取单个Bean实例信息
	public <T> T getInstance(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
			return context.getBean(type);
		}
		return null;
	}

	public <T> ObjectProvider<T> getLazyProvider(String name, Class<T> type) {
		return new ClientFactoryObjectProvider<>(this, name, type);
	}

	public <T> ObjectProvider<T> getProvider(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		return context.getBeanProvider(type);
	}

    //
	public <T> T getInstance(String name, Class<?> clazz, Class<?>... generics) {
		ResolvableType type = ResolvableType.forClassWithGenerics(clazz, generics);
		return getInstance(name, type);
	}

	@SuppressWarnings("unchecked")
	public <T> T getInstance(String name, ResolvableType type) {
		AnnotationConfigApplicationContext context = getContext(name);
		String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type);
		if (beanNames.length > 0) {
			for (String beanName : beanNames) {
				if (context.isTypeMatch(beanName, type)) {
					return (T) context.getBean(beanName);
				}
			}
		}
		return null;
	}

    //从name对应的AnnotationConfigApplicationContext中获取T类型的Bean实例,由BeanName与Bean组成的K-V对象
	public <T> Map<String, T> getInstances(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
			return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
		}
		return null;
	}

	/**
	 * Specification with name and configuration.
	 */
	public interface Specification {

		String getName();

		Class<?>[] getConfiguration();

	}

}

NamedContextFactory 有几个功能:

  • 创建 AnnotationConfigApplicationContext 子上下文(#createContext方法);
  • 在子上下文中创建并获取 Bean 实例(#createContext方);
  • 当子上下文消亡时清除其中的 Bean 实例(#destroy方法):由于 NameContextFactory 实现了DisposableBean 接口 ,当 NamedContextFactory 实例消亡时, Spring 框架会调用其 destroy 方法,清除掉自己创建的所有子上下文和自身包含的所有组件实例。

OpenFeign 中, FeignContext 继承了 NamedContextFactory ,用于存储各类 OpenFeign 的组件实例。

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {

	public FeignContext() {
		super(FeignClientsConfiguration.class, "feign", "feign.client.name");
	}

}

FeignContext 的构造方法调用父类的构造方法,传入的默认配置类defaultConfigTypeFeignClientsConfigurationpropertySourceNameFeignpropertyNamefeign.client.name

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {

	@Autowired
	private ObjectFactory<HttpMessageConverters> messageConverters;

	@Autowired(required = false)
	private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();

	@Autowired(required = false)
	private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

	@Autowired(required = false)
	private Logger logger;

	@Autowired(required = false)
	private SpringDataWebProperties springDataWebProperties;

	@Bean
	@ConditionalOnMissingBean
	public Decoder feignDecoder() {
		return new OptionalDecoder(
				new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
	public Encoder feignEncoder() {
		return new SpringEncoder(this.messageConverters);
	}

	@Bean
	@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
	@ConditionalOnMissingBean
	public Encoder feignEncoderPageable() {
		PageableSpringEncoder encoder = new PageableSpringEncoder(
				new SpringEncoder(this.messageConverters));
		if (springDataWebProperties != null) {
			encoder.setPageParameter(
					springDataWebProperties.getPageable().getPageParameter());
			encoder.setSizeParameter(
					springDataWebProperties.getPageable().getSizeParameter());
			encoder.setSortParameter(
					springDataWebProperties.getSort().getSortParameter());
		}
		return encoder;
	}

	@Bean
	@ConditionalOnMissingBean
	public Contract feignContract(ConversionService feignConversionService) {
		return new SpringMvcContract(this.parameterProcessors, feignConversionService);
	}

	@Bean
	public FormattingConversionService feignConversionService() {
		FormattingConversionService conversionService = new DefaultFormattingConversionService();
		for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
			feignFormatterRegistrar.registerFormatters(conversionService);
		}
		return conversionService;
	}

	@Bean
	@ConditionalOnMissingBean
	public Retryer feignRetryer() {
		return Retryer.NEVER_RETRY;
	}

	@Bean
	@Scope("prototype")
	@ConditionalOnMissingBean
	public Feign.Builder feignBuilder(Retryer retryer) {
		return Feign.builder().retryer(retryer);
	}

	@Bean
	@ConditionalOnMissingBean(FeignLoggerFactory.class)
	public FeignLoggerFactory feignLoggerFactory() {
		return new DefaultFeignLoggerFactory(this.logger);
	}

	@Bean
	@ConditionalOnClass(name = "org.springframework.data.domain.Page")
	public Module pageJacksonModule() {
		return new PageJacksonModule();
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
	protected static class HystrixFeignConfiguration {

		@Bean
		@Scope("prototype")
		@ConditionalOnMissingBean
		@ConditionalOnProperty(name = "feign.hystrix.enabled")
		public Feign.Builder feignHystrixBuilder() {
			return HystrixFeign.builder();
		}

	}

}

在讲解 @EnableFeignClients 注解的时候介绍到, FeignClientsConfigurationOpenFeign 默认的配置类,它会创建一些默认的 Bean 对象,比如 EncoderDecoderContractRetryer 等。后面再介绍,在此先过。

总结:NamedContextFactory 会创建出 AnnotationConfigApplicationContext 实例,并以 name 作为标识,然后每个 AnnotationConfigApplicationContext 实例都会注册部分配置类, 从而可以给出一系列基于配置类生成的组件实例,这样就可以基于 name 来管理一系列的组件实例,为不同的 FeignClient 准备不同配置组件实例, 比如说 DecoderEncoder等。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值