前言
由上文我们知道针对某一个Feign接口,我们可以给他设置特定的配置类。那如果现在有一个服务,我们只想对A服务配置一个拦截器拦截请求而不影响其他服务,那应该怎么做呢?
一、feign接口配置
由前面的文章我们知道了feign的代理过程以及调用过程。现在我们看一下feign都有哪些配置?
- @FeignClient可配置属性列表
属性 | 值类型 | 含义 |
---|---|---|
value | string | 服务名,无论是否有URL,服务名称都不能为空,它可以设置为属性值${server_name}这种形式 |
name | string | 同value |
qualifier | string | 指定feign接口在容器中的bean实例名称(我们知道每一个feign接口最终都会向容器注入一个实例),等同于@Qualifier |
url | string | 绝对URL或可解析主机名(协议是可选的) |
decode404 | boolean | 是否应该解码404而不是抛出假异常 |
fallback | class | 指定一个会退类(比如发生未处理的异常后)。回退类必须实现feign接口,并注册到容器。 |
fallbackFactory | class | 为指定的外部客户机接口定义回退工厂。回退工厂必须实现FallbackFactory接口,此工厂接口返回一个feign接口对应的回滚类。回退工厂必须注册到容器。 |
path | string | 指定相对路径 |
primary | boolean,默认true | 当容器中存在同名的bean的实现,以当前代理对象的bean为主。 |
configuration | class | 当前feign接口的配置类,他会将配置类注册到当前feign对象的容器中(非应用程序的上下文环境) |
- 配置是何时被加载的
这里直接上核心代码(FeignClientsRegistrar.registerClientConfiguration)
// name @FeignClient的name属性值
// configuration @FeignClient的class对象
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());
}
由上面的代码我们知道,他是将@FeignClient对象的configuration属性对应的配置类作为成员FeignClientSpecification的属性注入到应用上下文。那看到这里我们就会产生一个疑问:configuration对应的配置类到底什么时候被初始化呢?最终发现在为@FeignClient生成代理对象的时候,他初始化了FeignClient对应的配置。如下(NamedContextFactory.createContext):
// 在获取一个@FeignClient接口对应的代理对象前,先为其创建Context对象
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注册环境变量
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;
}
显然,在为@FeignClient接口的代理对象创建了Context后,将配置初始化到了该Context。这里可以明确的是每一个@FeignClient只要name属性不一致(2.1版本以后可以手动指定contextId),那么他们将拥有不同的上下文。
二、feign调用的沙箱设计思路
在现实中的sandbox,是一种儿童玩具,类如KFC中一个装满小球的容器,儿童可以在随意玩耍,起到保护儿童的作用。(也可以理解为一种安全环境)。同样在网络技术中也是一种按照安全策略限制程序行为的执行环境。在feign的调用过程中,针对不同的服务为其创建一个上下文,我们可以将这个上下文理解为sandbox。执行调用所需要的资源是从各自的沙箱环境中取。整体的职责分工如下图所示。
从中我们可以看到,我们最终调用的执行器来源是来自沙箱,而沙箱的配置可以各不相同,创建不同的Client,这给了我们决定不同的服务怎样去调用高度的自主权。也解决了文章开篇提出的问题。
三、小结
至此,feign调用暂时告一段落。由于后续要花时间做一个开源框架,文章的书写便只能告一段落了。