本文约2千字,主要知识
- OpenFeign的父子容器
FeignClient
的注册
背景
在使用Spring Cloud时,经常使用OpenFeign 作为远程服务调用的类库;
Feign 是一种声明式服务调用组件,它在 RestTemplate 的基础上做了进一步的封装。通过 Feign,我们只需要声明一个接口并通过注解进行简单的配置(类似于 Dao 接口上面的 Mapper 注解一样)即可实现对 HTTP 接口的绑定。通过 Feign,我们可以像调用本地方法一样来调用远程服务,而完全感觉不到这是在进行远程调用。
Spring Boot3中,也引入了一套全新的声明式HTTP调用,这也可说明声明式的调用方式收到足够的青睐。
环境
Spring Boot : 2.7.6
spring-cloud:2021.0.5
OpenFeign-core : 11.10
jdk:17
示例工程
https://github.com/Fudeveloper/openfeign-demo.git
以下为示例工程的主要类
@FeignClient(name = "api-proxy"
,url = "http://127.0.0.1:8080"
,configuration = MyApiConfiguration.class)
public interface MyApi {
@GetMapping("/test/my")
String testForGet();
}
public class MyApiConfiguration {
@Bean
public Encoder myEncoder() {
return new Encoder() {
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
System.out.println("trigger myEncoder#encode");
}
};
}
}
参考资料
OpenFeign 的使用文档
FeignClient BeanDefinition的注册
按照国际惯例,要启用SpringBoot
的某个组件,需要先标注@EnableXXX
注解;OpenFeign 也不例外,其注解类@EnableFeignClients
主要内容如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 核心:导入FeignClientsRegistrar
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] basePackages() default {};
Class<?>[] clients() default {};
// ...
}
其中,最核心的动作是@Import(FeignClientsRegistrar.class)
FeignClientsRegistrar
实现了ImportBeanDefinitionRegistrar
接口,实现了一套自定义的BeanDefinition
注册流程。在Spring applicationContext
容器 refresh
的过程中(具体为invokeBeanFactoryPostProcessors->postProcessBeanDefinitionRegistry->processConfigBeanDefinitions
)时,将调用FeignClientsRegistrar#registerBeanDefinitions
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册 默认配置
registerDefaultConfiguration(metadata, registry);
// 注册 FeignClient
registerFeignClients(metadata, registry);
}
}
registerFeignClients
注册所有@FeignClient
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 保存clients BeanDefinition 的数组
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 如果未手动指定 clients,则扫描后添加
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
// 已手动指定,直接添加
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// ①遍历所有client,并注册
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// ...
// 获取@FeignClient 标注类的属性
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// ② 注册 ${name}.FeignClientSpecification
registerClientConfiguration(registry, name, attributes.get("configuration"));
// ③ 注册client的bean
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
在①处断点后可以发现,candidateComponents
已装载我们自定义的FeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
// 强转
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
// ③.1 构建factoryBean
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
// 是否可刷新,从feign.client.refresh-enabled配置解析
factoryBean.setRefreshableClient(isClientRefreshEnabled());
// 将@FeignClinet标注类的配置信息构建为BeanDefinition
// clazz即为@FeignClinet标注类的class
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
// ③.2 注意此处是从factoryBean 获取对象,具体为FeignClientFactoryBean#getObject
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
// 委托给DefaultListableBeanFactory#registerBeanDefinition
// ④ 将@FeignClinet标注类注册入Spring IOC父容器
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
// 支持Request.Options的refresh 操作,非本章重点
registerOptionsBeanDefinition(registry, contextId);
}
registerDefaultConfiguration
、registerFeignClients
,最终都将调用registerClientConfiguration
方法。
② 和③ 方法则将实际操作委托给DefaultListableBeanFactory#registerBeanDefinition
;这涉及到Spring IOC的核心流程,此处不再赘述。在本文中只需记住如下现象。若要了解更多相关知识,可参考: 《Spring 揭秘》第二部分:Spring IOC容器
![](https://img-blog.csdnimg.cn/img_convert/183575716fde7fefbf7cffe4f2f9da87.png#averageHue=#f0f0f0&crop=0&crop=0&crop=1&crop=1&from=url&height=296&id=BnAcr&margin=[object Object]&originHeight=323&originWidth=775&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=711)
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
// 不存在则加入集合
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
}
在③结束后,查看DefaultListableBeanFactory#beanDefinitionMap
,可发现我们自定义的FeignClient已注册到IOC 容器中
FeignContext 容器的实例化
回看③.1 、③.2的操作,即创建FeignClientFactoryBean
并从中获取对象;在Spring中,FactoryBean
是个举足轻重的类,可参考:What’s a FactoryBean?
注意③.2的后续操作:FeignClientFactoryBean#getObject
public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
@Override
public Object getObject() {
return getTarget();
}
<T> T getTarget() {
// ③.3 以注入的FeignContext,构建Feign
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
// ③.5 构建feign,并创建子容器
Feign.Builder builder = feign(context);
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
// ...
builder.client(client);
}
// 应用自定义的配置项
applyBuildCustomizers(context, builder);
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
protected Feign.Builder feign(FeignContext context) {
// ③.5.1 首次从子容器获取bean,将触发子容器创建
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
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));
// ③.5.2 根据FeignClientProperties、FeignClientConfiguration、父容器配置Feign
configureFeign(context, builder);
return builder;
}
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for " + contextId);
}
return instance;
}
}
根据Spring Boot的SPI
机制,可以发现在IOC 容器中引入了FeignAutoConfiguration
。FeignAutoConfiguration
主要工作是注入一个FeignContext
,与上方③.3 相呼应
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class, FeignEncoderProperties.class })
public class FeignAutoConfiguration {
// 注入第一节中的所有配置
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
// ③.4 构建FeignContext
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
}
在③.4 步骤时,断点查看configurations,可看到 步骤② 注册到Spring IOC容器中的FeignClientSpecification
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
// 注意FeignClientsConfiguration 类,其中存放了默认Bean配置
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
FeignContext
继承于NamedContextFactory
:NamedContextFactory 可以创建一个子容器(或者说子上下文)。OpenFeign
则是依赖它来实现子容器机制
的。
FeignContext 子容器的创建
在③.5中,多处调用了FeignClientFactoryBean#get
方法,此方法将调用NamedContextFactory#createContext
方法创建子容器。
// 子容器 map集合
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
try {
// 从容器中获取Bean
return context.getBean(type);
}
catch (NoSuchBeanDefinitionException e) {
// ignore
}
return null;
}
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);
}
// 创建子容器的方法
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context;
if (this.parent != null) {
// 处理父类信息
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
if (parent instanceof ConfigurableApplicationContext) {
beanFactory.setBeanClassLoader(
((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader());
}
else {
beanFactory.setBeanClassLoader(parent.getClassLoader());
}
context = new AnnotationConfigApplicationContext(beanFactory);
context.setClassLoader(this.parent.getClassLoader());
}
else {
// 当前已为顶级
context = new AnnotationConfigApplicationContext();
}
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
// 处理@FeignClient 中定义的configuration,对应背景中的`MyApiConfiguration`
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
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) {
// 设置父容器,目的是让所有bean也使用父容器的上下文信息
context.setParent(this.parent);
}
context.setDisplayName(generateDisplayName(name));
// 和Spring IOC容器相同,走refresh流程
context.refresh();
return context;
}
同样的,在子容器的refresh 过程中(具体为finishBeanFactoryInitialization->beanFactory.preInstantiateSingletons
),将初始化子容器所需要的Bean,如背景中定义的MyEncoder
,以及FeignClientsConfiguration
类中定义的名为feignXX
的各个bean。
之后情况则与IOC 容器初始化过程类似,此处不再赘述。
子容器初始化完成后,执行到③.5.2 处的代码,其根据FeignClientProperties、FeignClientConfiguration、父容器配置Feign;其中,FeignClientConfiguration
主要是注入一些默认Bean,如下所示
@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
//... 省略其他默认Encoder、Capability 等
}
之后,执行至④,将@FeignClinet标注类,以bean的形式注册入Spring IOC父容器。至此,FeignClient的注册已完成。