在我们日常项目中,使用FeignClient实现各系统接口调用变得更加简单。 在各个系统集成过程中,难免会遇到某些系统的Client需要特殊的配置、返回读取等需求。Feign使用NamedContextFactory来为每个Client模块构造单独的上下文(ApplicationContext).
文中代码来源于spring-cloud的2.2.9.release版本。
1、NamedContextFactory
看一下NamedContextFactory的主要代码。NamedContextFactory中有个contexts属性,contexts是Map<String, AnnotationConfigApplicationContext>,这个map以name为key 以子上线文为value。
getContext方法会从contexts查找name为key的子上下文,如果没有的话会调用createContext创建一个子上下文。
createContext方法是NamedContextFactory的重要方法之一。createContext首选创建一个AnnotationConfigApplicationContext作为子上下文;然后查询configurations中有无以此name为key的Specification(这个类稍后介绍),如果有相关的Specification的话就会注册这个
Specification存储的class到子上下文;然后注册configurations中包含的以"defalut."开头的key的Specification中存储的class到子上下文;紧接着在子上下文注册了PropertyPlaceholderAutoConfiguration 和 defaultConfigType(构造方法中初始的变量);紧接着注册了一个名字为propertySourceName的MapPropertySource,MapPropertySource的内容包括一个key为propertyName值为name的变量;后边进行设置parent然后refresh子上下文。
可以看到NamedContextFactory可以通过getContext(name)并为每个模块构造不同的上下文,并且加载相关的配置加载到子上下文。使用时可以通过Specification就是存储了模块名称和每个模块的配置类,进行配置,也可以通过NamedContextFactory指定一个配置类。
后边的getInstance等方法,都是通过查询子上下文中的来加载bean。
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
private final String propertySourceName;
private final String propertyName;
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
private Map<String, C> configurations = new ConcurrentHashMap<>();
private Class<?> defaultConfigType;
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
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 = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
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) {
// Uses Environment from parent as well as beans
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;
}
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
try {
return context.getBean(type);
}
catch (NoSuchBeanDefinitionException e) {
// ignore
}
return null;
}
public void setConfigurations(List<C> configurations) {
for (C client : configurations) {
this.configurations.put(client.getName(), client);
}
}
}
public interface Specification {
String getName();
Class<?>[] getConfiguration();
}
2、NamedContextFactory在feign中使用
feign中提供了Specification的实现
class FeignClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
}
EnableFeignClients中import了FeignClientsRegistrar,下面代码不是全部的EnableFeignClients代码,做了简化处理。
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients中import了 {
Class<?>[] defaultConfiguration() default {};
}
具体client的配置注册是在FeignClientsRegistrar 实现的。registerBeanDefinitions方法实现了具体注册。registerDefaultConfiguration 将EnableFeignClients中的defaultConfiguration的配置,以"defalut."开头的名称注入到了容器。registerFeignClients扫描所有的@FeignClient类,得到name(具体逻辑可参考源码),得到FeignClient的configration属性,再调用registerClientConfiguration 就行配置(注册了name + "." + FeignClientSpecification.class.getSimpleName()的bean)。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
}
在FeignAutoConfiguration中引入了所有的 FeignClientSpecification,并且初始化了FeiginContext,调用了setConfigurations(参考NamedContextFactory方法,此操作将以name为key,FeignClientSpecification对象为value添加到NamedContextFactory的configurations属性中),后续在调用到NamedContextFactory的createContext方法时,会将configurations属性中的配置进行加载。
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。