文章目录
- 前言
- springcloud中的父子容器
- 基础知识了解
- springcloud提供父子容器的支持
- springcloud_openfeign对父子容器的应用
- 总结
前言
在前面springcloud_openfeign的文章中了解到FeignClientsRegistrar
在扫描到满足条件的feign接口后会生成BeanDefinition
并注入到spring上下文容器, 并且在FeignClientFactoryBean
中构建Feign.Builder
时会从父子容器中获取相关的组件设置给它, 那么注入到容器中时怎么选择容器, 以及使用时是怎么从容器中获取的呢??, 本节我们就来了解一下springcloud中的父子容器, 以及springcloud_openfeign是怎么使用父子容器的。
springcloud中的父子容器
基础知识了解
在spring框架中有一个非常重要的类org.springframework.context.ApplicationContext
, 它有个方法getParent
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
/**
* 返回父上下文,如果没有父上下文,则返回null,并且这是上下文层次结构的根。
*/
@Nullable
ApplicationContext getParent();
}
一个上下文可以有父上下文, 如果没有, 那么它是根上下文。有了这个我们可以定义父子上下文的关系。同时它也继承了ListableBeanFactory
, 它自己本身也是一个BeanFactory
其中parent属性定义在AbstractApplicationContext
, 它是所有子容器的一个父抽象类
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
private ApplicationContext parent;
}
同时, AbstractApplicationContext有一个一级子类GenericApplicationContext
, 其它具体实现都直接继承与它, 它有个beanFactory属性
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
}
到这里就可以看出ApplicationContext
继承ListableBeanFactory
是用来包装它内部的beanFactory属性用的(包装模式哈);
所以ApplicationContext和beanFactory的关系其实就是ApplicationContext
是beanFactory
的包装类, beanFactory
作为ApplicationContext
的属性存在。
ApplicationContent依赖关系如图
springcloud提供父子容器的支持
在springcloud中有一个比较重要的类org.springframework.cloud.context.named.NamedContextFactory
, 它提供一种机制,根据名称(比如服务名称)动态创建 ApplicationContext,每个命名上下文都拥有独立的配置和 Bean 定义
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
/**
* 给指定容器做初始化扩展用的接口, 就是容器创建之后可以用它进行扩展; {容器名, 可扩展的对象}
*/
private final Map<String, ApplicationContextInitializer<GenericApplicationContext>> applicationContextInitializers;
/**
* 子容器们; {容器名, 子容器对象}
*/
private final Map<String, GenericApplicationContext> contexts = new ConcurrentHashMap<>();
/**
* 需要注册到子容器中的对象, 其中default.开头的会被注册到所有子容器中;
* {容器名, NamedContextFactory.Specification}
*/
private final Map<String, C> configurations = new ConcurrentHashMap<>();
/**
* 父容器对象
*/
private ApplicationContext parent;
/**
* 默认需要注入到所有子容器中的对象
*/
private final Class<?> defaultConfigType;
}
以上就是NamedContextFactory
中比较核心的属性们, 包含子容器, 子容器初始化的扩展对象和需要注入到子容器中的对象等, 下面我们分析几个核心的方法
创建并初始化子容器
/**
* 根据名称获取容器对象
*/
protected GenericApplicationContext getContext(String name) {
// 不存在指定容器名的容器对象
if (!this.contexts.containsKey(name)) {
// 锁一下, double check了解一下
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
// 创建容器并添加到注册表中
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
/**
* 根据名称创建容器对象
*/
public GenericApplicationContext createContext(String name) {
// 创建一个容器
GenericApplicationContext context = buildContext(name);
// 存在对应容器的ApplicationContextInitializer对象
if (applicationContextInitializers.get(name) != null) {
// 使用ApplicationContextInitializer对容器进行初始化
applicationContextInitializers.get(name).initialize(context);
// 刷新容器
context.refresh();
return context;
}
// 注册一些bean到容器中
registerBeans(name, context);
// 刷新容器
context.refresh();
return context;
}
/**
* 世家创建容器的方法
* @param name 容器名称
*/
public GenericApplicationContext buildContext(String name) {
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
// https://github.com/spring-cloud/spring-cloud-openfeign/issues/475
ClassLoader classLoader = getClass().getClassLoader();
GenericApplicationContext context;
// 存在父容器
if (this.parent != null) {
// 创建一个子类的beanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 父容器默认是ConfigurableApplicationContext类型
if (parent instanceof ConfigurableApplicationContext) {
// 使用和父容器一样的bean类加载器
beanFactory.setBeanClassLoader(
((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader());
}
else {
beanFactory.setBeanClassLoader(classLoader);
}
// 默认AotDetector.useGeneratedArtifacts()为false, 所以这里默认是AnnotationConfigApplicationContext
context = AotDetector.useGeneratedArtifacts() ? new GenericApplicationContext(beanFactory)
: new AnnotationConfigApplicationContext(beanFactory);
}
else {
// 父容器不存在的话,当前就是根容器,所以不需要设置beanFactory
context = AotDetector.useGeneratedArtifacts() ? new GenericApplicationContext()
: new AnnotationConfigApplicationContext();
}
context.setClassLoader(classLoader);
// 给子容器环境上下文添加对象
context.getEnvironment().getPropertySources().addFirst(
new MapPropertySource(this.propertySourceName, Collections.singletonMap(this.propertyName, name)));
// 这里就给当前子容器添加父容器对象
if (this.parent != null) {
context.setParent(this.parent);
}
// 设置子容器的名称
context.setDisplayName(generateDisplayName(name));
return context;
}
/**
* 给创建的子容器注册bean
*/
public void registerBeans(String name, GenericApplicationContext context) {
Assert.isInstanceOf(AnnotationConfigRegistry.class, context);
AnnotationConfigRegistry registry = (AnnotationConfigRegistry) context;
// 给子容器配置了注册的bean对象
if (this.configurations.containsKey(name)) {
// 将子容器配置的对象都注入进去
for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
registry.register(configuration);
}
}
// 将defalut.开头的配置注册到所有子容器中
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
registry.register(configuration);
}
}
}
// 给所有子容器注册默认配置
registry.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
}
方法小结
- 如果存在父容器对象, 那么先创建一个beanFactory, 然后默认创建
AnnotationConfigApplicationContext
作为子容器, 将这个生成的beanFactory设置给创建的子容器中 - 给子容器上下文的属性对象添加传入的默认属性对象
- 给当前子容器添加父容器对象
默认生成的子容器是AnnotationConfigApplicationContext
对象, 它与是GenericApplicationContext
的子类, 提供了注解扫描和解析的功能, 所以我们可以在子容器的ApplicationContextInitializer
中使用它去扫描指定的包啥的。
- 如果存在子容器对应的
ApplicationContextInitializer
, 则对创建出来的子容器进行初始化 - 如果不存在子容器对应的
ApplicationContextInitializer
- 则将子容器配置的所有对象注册
configurations
到子容器中 - 将
default.
开头的配置注册到所有子容器中 - 将默认配置注入到所有子容器中
获取子容器中的对象
/**
* 从指定容器中指定类型的bean
* @param name 容器名称
* @param type bean的类型
*/
public <T> T getInstance(String name, Class<T> type) {
// 获取或创建子容器
GenericApplicationContext context = getContext(name);
try {
// 从子容器中获取指定类型的bean
return context.getBean(type);
}
catch (NoSuchBeanDefinitionException e) {
// ignore
}
return null;
}
/**
* 从指定容器中指定类型和泛型的bean
* @param name 容器名称
* @param clazz bean的类型
* @param generics bean类型的泛型参数
*/
public <T> T getInstance(String name, Class<?> clazz, Class<?>... generics) {
// 生成一个包含泛型信息的 ResolvableType 对象
// 例如,如果 clazz 是 List,generics 是 String.class,则 ResolvableType 描述的是 List<String>。
ResolvableType type = ResolvableType.forClassWithGenerics(clazz, generics);
return getInstance(name, type);
}
public <T> T getInstance(String name, ResolvableType type) {
// 获取或者创建子容器
GenericApplicationContext context = getContext(name);
// 获取当前上下文及其祖先上下文中,符合指定类型的所有 Bean 名称
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type);
for (String beanName : beanNames) {
// 检查 Bean 的类型是否与 ResolvableType 匹配
if (context.isTypeMatch(beanName, type)) {
// 从当前容器或者父子容器中获取bean实例
return (T) context.getBean(beanName);
}
}
return null;
}
public <T> Map<String, T> getInstances(String name, Class<T> type) {
// 获取指定名称的容器
GenericApplicationContext context = getContext(name);
// 从父子容器中获取指定类型的所有 Bean 实例,并返回一个 Map,其中包含 Bean 的名称和实例
return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
}
springcloud_openfeign对父子容器的应用
之前聊到@EnableFeignClients
注解使用@Import
引入了FeignClientsRegistrar
对象, 然后在ImportBeanDefinitionRegistrar
的回调方法中注入了一些对象, 如下
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册FeignClientSpecification的BeanDefinition
registerDefaultConfiguration(metadata, registry);
// 扫描FeignClient注解, 将feign的接口的接口注册成一个BeanDefinition到spring中
registerFeignClients(metadata, registry);
}
registerDefaultConfiguration
/**
* 将defaultConfiguration对应的类封装成FeignClientSpecification对象, 并且注册成一个BeanDefinition
*/
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 获取EnableFeignClients注解的属性
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// 存在defaultConfiguration属性值
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
// 判断当前类是否是内部类
if (metadata.hasEnclosingClass()) {
// default. + EnableFeignClients注解标识的类的外部类的名称
name = "default." + metadata.getEnclosingClassName();
}
else {
// default. + EnableFeignClients注解标识的类的名称
name = "default." + metadata.getClassName();
}
// 注解feign的全局配置类, 注册全局配置FeignClientSpecification类, 构造器的三个参数为{name}, default, defaultAttrs.get("defaultConfiguration")
registerClientConfiguration(registry, name, "default", defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(className);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
这里就将@EnableFeignClients
注解中defaultConfiguration
属性对应的值, 构建成了一个FeignClientSpecification
注册到了容器中, 其中指定的name(容器名称)是以default.
开头的
FeignClientSpecification
public class FeignClientSpecification implements NamedContextFactory.Specification {
/**
* 对应容器的名称
*/
private String name;
/**
* 注入到子容器的对象的名称
*/
private String className;
/**
* 注入到子容器中的对象
*/
private Class<?>[] configuration;
}
这个类中name
和configuration
比较重要,记录了要注入的目标容器名称name
以及注入的实际对象configuration
registerFeignClients
// ... 省略部分代码
for (BeanDefinition candidateComponent : candidateComponents) {
// 注解定义的BeanDefinition
if (candidateComponent instanceof AnnotatedBeanDefinition beanDefinition) {
// verify annotated class is an interface
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());
// 子容器名称, 获取当前feign接口对应要生成的FeignClientSpecification的名称的前缀, 它必须唯一
// 顺序: contextId>value>name>serviceId
String name = getClientName(attributes);
// feign接口的名称, 同时也是feign接口注册在spring中的名称
String className = annotationMetadata.getClassName();
// 注册当前feign接口的配置类(FeignClientSpecification的BeanDefinition)到spring中
registerClientConfiguration(registry, name, className, attributes.get("configuration"));
// 注册feign接口对应的FeignClientFactoryBean的BeanDefinition到spring中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(className);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
其中registerClientConfiguration(registry, name, className, attributes.get("configuration"));
将@FeignClient注解的configuration
属性对应的值构建成FeignClientSpecification
注册到spring容器中。
其中名称name
的取值顺序为contextId>value>name>serviceId
, 所以@FeignClient
注解中contextId
实际就是子容器的名称
到此, 默认的对于所有子容器应该注入的对象(@EnableFeignClients
注解中defaultConfiguration
属性对应的值), 以及当个子容器应该注入的对象(@FeignClient
注解的configuration
属性对应的值)已经全部注入到spring上下文了
子容器对象注入到子容器中
@Configuration(proxyBeanMethods = false)
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignClientFactory feignContext() {
FeignClientFactory context = new FeignClientFactory();
context.setConfigurations(this.configurations);
return context;
}
}
在FeignAutoConfiguration
类中, 定义了一个方法类型的工厂bean(feignContext方法), 创建了一个FeignClientFactory
对象, 并将容器中的FeignClientSpecification
对象添加到了FeignClientFactory
中; 从这里也可以看出, 我们是可以自定义FeignClientSpecification
对象的, 它会在这里直接添加到FeignClientFactory
中。
其中关于setConfigurations的作用, 回看一下上面介绍的NamedContextFactory
FeignClientFactory
public class FeignClientFactory extends NamedContextFactory<FeignClientSpecification> {
/**
* 从子容器中获取指定类型的单个bean, 多 Bean 时返回null
*/
@Nullable
public <T> T getInstanceWithoutAncestors(String name, Class<T> type) {
try {
// getContext方法会把EnableFeignClients#defaultConfiguration指定的接口和默认配置添加到feign接口的上下文
return BeanFactoryUtils.beanOfType(getContext(name), type);
}
catch (BeansException ex) {
// 多 Bean 时抛异常
return null;
}
}
/**
* 从子容器中获取指定类型的多个bean
*/
@Nullable
public <T> Map<String, T> getInstancesWithoutAncestors(String name, Class<T> type) {
return getContext(name).getBeansOfType(type);
}
/**
* 从父子容器中获取指定名字和类型的bean
*/
public <T> T getInstance(String contextName, String beanName, Class<T> type) {
return getContext(contextName).getBean(beanName, type);
}
}
FeignClientFactory继承了NamedContextFactory
抽象类
-
它有两个构造器, 在super中给
NamedContextFactory
设置了defaultConfigType
字段, 它将会注入到所有子容器中; 容器名称也会注入到子容器中, 其中key为spring.cloud.openfeign.client.name
关于这个
FeignClientsConfiguration
类, 我们这里先不做解释, 先有个印象即可
super(FeignClientsConfiguration.class, "spring.cloud.openfeign", "spring.cloud.openfeign.client.name",
applicationContextInitializers);
- 并新增了三个方法
- getInstanceWithoutAncestors: 从子容器中获取指定类型的单个bean
- getInstancesWithoutAncestors: 从子容器中获取指定类型的多个bean
- getInstance: 从父子容器中获取指定名字和类型的bean
现在我们再回过头来看一下FeignClientFactoryBean
中对父子容器的使用
FeignClientFactoryBean
public class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
/**
* 使用容器中配置的组件和属性配置填充Feign.Builder
*/
<T> T getTarget() {
// feign上下文的工具类
FeignClientFactory feignClientFactory = beanFactory != null ? beanFactory.getBean(FeignClientFactory.class)
: applicationContext.getBean(FeignClientFactory.class);
// ... 省略代码
}
protected Feign.Builder feign(FeignClientFactory context) {
// 从contextId对应的上下文中获取FeignLoggerFactory对象
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
// 创建logger
Logger logger = loggerFactory.create(type);
// @formatter:off
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;
}
/**
* 从容器中获取一个bean, 没有找到就反射创建一个
*/
private <T> T getOrInstantiate(Class<T> tClass) {
try {
// 容器中获取
return beanFactory != null ? beanFactory.getBean(tClass) : applicationContext.getBean(tClass);
}
catch (NoSuchBeanDefinitionException e) {
// 反射创建一个
return BeanUtils.instantiateClass(tClass);
}
}
/**
* 从父子容器中获取一个bean, 没有找到会抛异常
*/
protected <T> T get(FeignClientFactory context, Class<T> type) {
// 从contextId对应的上下文中或者spring上下文获取type对象(从父子容器中获取对象)
T instance = context.getInstance(contextId, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for " + contextId);
}
return instance;
}
/**
* 从父子容器中获取一个bean, 没有找到不会抛异常; 1.先当前上下文中获取 2.没有再从父容器中获取
*/
protected <T> T getOptional(FeignClientFactory context, Class<T> type) {
return context.getInstance(contextId, type);
}
/**
* 从父子容器或者当前容器中获取一个bean, 没有找到不会抛异常; 1.先当前上下文中获取 2.没有再从父容器中获取
*/
protected <T> T getInheritedAwareOptional(FeignClientFactory context, Class<T> type) {
if (inheritParentContext) {
// 从当前及父容器中获取一个满足条件的bean; 1.先当前上下文中获取 2.没有再从父容器中获取
return getOptional(context, type);
}
else {
// 从当前容器中获取一个满足条件的bean
return context.getInstanceWithoutAncestors(contextId, type);
}
}
protected <T> Map<String, T> getInheritedAwareInstances(FeignClientFactory context, Class<T> type) {
if (inheritParentContext) {
// 从当前以及父容器中获取所有type类型的bean; 先从当前容器中拿, 再从父容器中拿, 以此类推, 直到拿到所有的bean
return context.getInstances(contextId, type);
}
else {
// 从当前容器中拿的满足条件的bean们
return context.getInstancesWithoutAncestors(contextId, type);
}
}
}
FeignClientFactoryBean代表了一个feign接口客户端对象, 它在构建Feign.Builder
的时候, 调用了get
,getOptional
,getInheritedAwareOptional
,getInheritedAwareInstances
等方法。
这些方法都有个共同点, 就是使用FeignClientFactory
对象, 调用它的相关api从父子容器中获取指定类型的对象。
总结
- spring的容器基对象
ApplicationContext
默认支持父子容器结构, 通过getParent方法获取父容器对象。
- 在它的一级抽象实现类
AbstractApplicationContext
中定义了父容器ApplicationContext parent
对象。 - 在它的一级非抽象子类
GenericApplicationContext
中, 定义了DefaultListableBeanFactory beanFactory
对象, beanFactory是applicationContent的一个属性, 并且它俩是包装模式的关系而存在
- springcloud提供的
NamedContextFactory
类, 定义了对容器的操作方法
- 创建-createContext), buildContext
- 获取-getContext)
- 初始化子容器(registerBeans或applicationContextInitializers.get(name).initialize(context))
- 获取容器中的对象-getInstance、getInstances等
@EnableFeignClients
注解使用@Import
引入了FeignClientsRegistrar
- 根据
@EnableFeignClients.defaultConfiguration
构建名称为default.开头的FeignClientSpecification
注入到spring容器中, 它会注册到所有feign的子容器中 - 根据
@FeignClient.configuration
构建名称为contextId(子容器名称)的FeignClientSpecification
注入到spring容器中
- FeignAutoConfiguration类中会创建
FeignClientFactory
对象, 它是springcloud_feign对NamedContextFactory
的一个实现, 它将2,3两步构建的FeignClientSpecification
注入到FeignClientFactory
中; 同时我们也可以自定义FeignClientSpecification
对象的bean放进去使用 - 在
FeignClientFactoryBean
中, 通过@FeignClient.contextId
字段从FeignClientFactory
获取指定容器, 获取配置的目标对象(FeignClientSpecification配置的)构建Feign.Builder
- 每个子容器共享一个父容器中的对象, 子容器中各个独有组件互相隔离