前言

由上文我们知道针对某一个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调用暂时告一段落。由于后续要花时间做一个开源框架,文章的书写便只能告一段落了。

posted on 2020-11-18 18:25  泥粑  阅读(1398)  评论(0编辑  收藏  举报