SpringCloud整合Feign有多个配置类,因为其中SpringCloud和整合Feign的同时还需要把Ribbon整合进来,包括Feign的一些自身组件的配置更换等,下面我们先来把这些配置类梳理一下

关于springcloud整合feign相关的配置类如上图所示,这些类都是为springcloud整合feign的时候所准备的,我们去springcloud-feign包下面的spring.factories文件看一下

其中前面两个配置类是我们常用的,首先先来看一下FeignAutoConfiguration

FeignAutoConfiguration

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,FeignHttpClientProperties.class })
public class FeignAutoConfiguration {// 注入配置隔离对象@Autowired(required = false)private List<FeignClientSpecification> configurations = new ArrayList<>();@Beanpublic HasFeatures feignFeature() {return HasFeatures.namedFeature("Feign", Feign.class);}// 注入多个容器配置工厂对象@Beanpublic FeignContext feignContext() {FeignContext context = new FeignContext();context.setConfigurations(this.configurations);return context;}// 注入带有Hystrix熔断降级的Targeter// 默认当我们加上依赖SpringCloud-Feign依赖的时候就有HystrixFeign这个类// 与下面的DefaultTargeter互斥@Configuration@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")protected static class HystrixFeignTargeterConfiguration {@Bean@ConditionalOnMissingBeanpublic Targeter feignTargeter() {return new HystrixTargeter();}}// 注入默认的Targeter// 与上面的HystrixTargeter互斥@Configuration@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")protected static class DefaultFeignTargeterConfiguration {@Bean@ConditionalOnMissingBeanpublic Targeter feignTargeter() {return new DefaultTargeter();}}// 当类路径上不存在ribbon的LoadBalancer的才会注入到IOC容器// HttpClientFeignConfiguration与下面的OkHttpFeignConfiguration这两个配置// 作用就是用来配置Feign的Client组件的,而springcloud在整合feign的时候提供了// 对Client组件的两种替换实现,分别是HttpClient,Okhttp作为请求客户端,默认feign是用jdk自带的HttpURLConnection// 基本上HttpClientFeignConfiguration和下面的OkHttpFeignConfiguration都不会被注入到IOC容器// 因为只要我们把springcloud-feign包依赖进来,这个包里面也依赖了springcloud-ribbon包// 所有类路径上面肯定会有ribbon的LoadBalancer// 那么是在哪里替换client组件的呢?答案就是在FeignRibbonClientAutoConfiguration@Configuration@ConditionalOnClass(ApacheHttpClient.class)@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")@ConditionalOnMissingBean(CloseableHttpClient.class)@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)protected static class HttpClientFeignConfiguration {private final Timer connectionManagerTimer = new Timer("FeignApacheHttpClientConfiguration.connectionManagerTimer", true);@Autowired(required = false)private RegistryBuilder registryBuilder;private CloseableHttpClient httpClient;@Bean@ConditionalOnMissingBean(HttpClientConnectionManager.class)public HttpClientConnectionManager connectionManager(ApacheHttpClientConnectionManagerFactory connectionManagerFactory,FeignHttpClientProperties httpClientProperties) {final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(httpClientProperties.isDisableSslValidation(),httpClientProperties.getMaxConnections(),httpClientProperties.getMaxConnectionsPerRoute(),httpClientProperties.getTimeToLive(),httpClientProperties.getTimeToLiveUnit(),this.registryBuilder);this.connectionManagerTimer.schedule(new TimerTask() {@Overridepublic void run() {connectionManager.closeExpiredConnections();}}, 30000, httpClientProperties.getConnectionTimerRepeat());return connectionManager;}@Beanpublic CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,HttpClientConnectionManager httpClientConnectionManager,FeignHttpClientProperties httpClientProperties) {RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectTimeout(httpClientProperties.getConnectionTimeout()).setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();this.httpClient = httpClientFactory.createBuilder().setConnectionManager(httpClientConnectionManager).setDefaultRequestConfig(defaultRequestConfig).build();return this.httpClient;}@Bean@ConditionalOnMissingBean(Client.class)public Client feignClient(HttpClient httpClient) {return new ApacheHttpClient(httpClient);}@PreDestroypublic void destroy() throws Exception {this.connectionManagerTimer.cancel();if (this.httpClient != null) {this.httpClient.close();}}}@Configuration@ConditionalOnClass(OkHttpClient.class)@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)@ConditionalOnProperty("feign.okhttp.enabled")protected static class OkHttpFeignConfiguration {private okhttp3.OkHttpClient okHttpClient;@Bean@ConditionalOnMissingBean(ConnectionPool.class)public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,OkHttpClientConnectionPoolFactory connectionPoolFactory) {Integer maxTotalConnections = httpClientProperties.getMaxConnections();Long timeToLive = httpClientProperties.getTimeToLive();TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);}@Beanpublic okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,ConnectionPool connectionPool,FeignHttpClientProperties httpClientProperties) {Boolean followRedirects = httpClientProperties.isFollowRedirects();Integer connectTimeout = httpClientProperties.getConnectionTimeout();Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation).connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects).connectionPool(connectionPool).build();return this.okHttpClient;}@PreDestroypublic void destroy() {if (this.okHttpClient != null) {this.okHttpClient.dispatcher().executorService().shutdown();this.okHttpClient.connectionPool().evictAll();}}@Bean@ConditionalOnMissingBean(Client.class)public Client feignClient(okhttp3.OkHttpClient client) {return new OkHttpClient(client);}}}

这个配置类看上去往容器中注入了很多bean,但是其实做的事情并不多,主要是因为后面注入的那两个client组件并不满足条件,所以这两个client并不会注入到容器中,还有一个比较重要的就是注入了一个FeignContext,这个组件说来话长,简单来说主要就是用来做对于每一个服务的容器配置隔离的,还有注入了一个HystrixTargeter,这个targeter并不是feign原生的组件,HystrixTargeter中主要就是加上了Hystrix的熔断降级功能,经过这个类feign生成的接口代理对象的InvocationHandler就不再是feign自带的FeignInvocation了,而是HystrixInvocationHandler,这个InvocationHandler里面的invoke方法加上了Hystrix的api熔断降级的支持

FeignRibbonAutoConfiguration

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancedConfiguration.class,OkHttpFeignLoadBalancedConfiguration.class,DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {@Bean@Primary@ConditionalOnMissingBean@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")public CachingSpringLoadBalancerFactory cachingLBClientFactory(SpringClientFactory factory) {return new CachingSpringLoadBalancerFactory(factory);}@Bean@Primary@ConditionalOnMissingBean@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {return new CachingSpringLoadBalancerFactory(factory, retryFactory);}@Bean@ConditionalOnMissingBeanpublic Request.Options feignRequestOptions() {return LoadBalancerFeignClient.DEFAULT_OPTIONS;}}

重点看

@Import({ HttpClientFeignLoadBalancedConfiguration.class,OkHttpFeignLoadBalancedConfiguration.class,DefaultFeignLoadBalancedConfiguration.class })

这里会往容器中导入三个配置类,从名字上来看这三个配置类所做的事情应该都是一样的,都和负载均衡有关,所以应该有对应的condition符合才能注入到容器中

这三个配置类分别就是往容器中注入HttpClient请求客户端,Okhttp请求客户端,jdk自带的HttpURLConnection请求客户端(默认使用),并且HttpClient和Okhttp需要加上对应的依赖包才能够符合condition条件往容器中注入

还有一点就是可以发现这三个配置类都是注入的一个LoadBalancerFeignClient,这个client有什么用?

public class LoadBalancerFeignClient implements Client {static final Request.Options DEFAULT_OPTIONS = new Request.Options();// 真正的请求客户端private final Client delegate;private CachingSpringLoadBalancerFactory lbClientFactory;private SpringClientFactory clientFactory;public LoadBalancerFeignClient(Client delegate,CachingSpringLoadBalancerFactory lbClientFactory,SpringClientFactory clientFactory) {this.delegate = delegate;this.lbClientFactory = lbClientFactory;this.clientFactory = clientFactory;}static URI cleanUrl(String originalUrl, String host) {String newUrl = originalUrl;if (originalUrl.startsWith("https://")) {newUrl = originalUrl.substring(0, 8)+ originalUrl.substring(8 + host.length());}else if (originalUrl.startsWith("http")) {newUrl = originalUrl.substring(0, 7)+ originalUrl.substring(7 + host.length());}StringBuffer buffer = new StringBuffer(newUrl);if ((newUrl.startsWith("https://") && newUrl.length() == 8)|| (newUrl.startsWith("http://") && newUrl.length() == 7)) {buffer.append("/");}return URI.create(buffer.toString());}@Overridepublic Response execute(Request request, Request.Options options) throws IOException {try {URI asUri = URI.create(request.url());String clientName = asUri.getHost();URI uriWithoutHost = cleanUrl(request.url(), clientName);FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);IClientConfig requestConfig = getClientConfig(options, clientName);return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();}catch (ClientException e) {IOException io = findIOException(e);if (io != null) {throw io;}throw new RuntimeException(e);}}IClientConfig getClientConfig(Request.Options options, String clientName) {IClientConfig requestConfig;if (options == DEFAULT_OPTIONS) {requestConfig = this.clientFactory.getClientConfig(clientName);}else {requestConfig = new FeignOptionsClientConfig(options);}return requestConfig;}protected IOException findIOException(Throwable t) {if (t == null) {return null;}if (t instanceof IOException) {return (IOException) t;}return findIOException(t.getCause());}public Client getDelegate() {return this.delegate;}private FeignLoadBalancer lbClient(String clientName) {return this.lbClientFactory.create(clientName);}static class FeignOptionsClientConfig extends DefaultClientConfigImpl {FeignOptionsClientConfig(Request.Options options) {setProperty(CommonClientConfigKey.ConnectTimeout,options.connectTimeoutMillis());setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());}@Overridepublic void loadProperties(String clientName) {}@Overridepublic void loadDefaultValues() {}}}

可以看到LoadBalancerFeignClient也实现了feign的client接口,所以说他自身也是一个client组件,并且LoadBalancerFeignClient中的delegate属性也是一个client对象,而在LoadBalancerFeignClient执行execute方法发起请求的时候,execute方法会再去调用ribbon的api去进行负载均衡得到目标服务实例,根据这个目标服务实例去重写url,然后使用delegate这个client对象真正地发起请求,所以说LoadBalancerFeignCleint只是一个包装类,包装了真正发起请求的请求客户端,在其上面包装了ribbon的负载均衡功能,这个类是springcloud-feign包里面的,所以说经过springcloud整合的feign才会有ribbon负载均衡的功能

FeignClientsConfiguration

@Configuration
public class FeignClientsConfiguration {@Autowiredprivate ObjectFactory<HttpMessageConverters> messageConverters;@Autowired(required = false)private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();@Autowired(required = false)private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();@Autowired(required = false)private Logger logger;@Bean@ConditionalOnMissingBeanpublic Decoder feignDecoder() {return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));}@Bean@ConditionalOnMissingBean@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")public Encoder feignEncoder() {return new SpringEncoder(this.messageConverters);}@Bean@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")@ConditionalOnMissingBeanpublic Encoder feignEncoderPageable() {return new PageableSpringEncoder(new SpringEncoder(this.messageConverters));}@Bean@ConditionalOnMissingBeanpublic Contract feignContract(ConversionService feignConversionService) {return new SpringMvcContract(this.parameterProcessors, feignConversionService);}@Beanpublic FormattingConversionService feignConversionService() {FormattingConversionService conversionService = new DefaultFormattingConversionService();for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {feignFormatterRegistrar.registerFormatters(conversionService);}return conversionService;}@Bean@ConditionalOnMissingBeanpublic Retryer feignRetryer() {return Retryer.NEVER_RETRY;}@Bean@Scope("prototype")@ConditionalOnMissingBeanpublic Feign.Builder feignBuilder(Retryer retryer) {return Feign.builder().retryer(retryer);}@Bean@ConditionalOnMissingBean(FeignLoggerFactory.class)public FeignLoggerFactory feignLoggerFactory() {return new DefaultFeignLoggerFactory(this.logger);}@Bean@ConditionalOnClass(name = "org.springframework.data.domain.Page")public Module pageJacksonModule() {return new PageJacksonModule();}@Configuration@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })protected static class HystrixFeignConfiguration {@Bean@Scope("prototype")@ConditionalOnMissingBean@ConditionalOnProperty(name = "feign.hystrix.enabled")public Feign.Builder feignHystrixBuilder() {return HystrixFeign.builder();}}}

可以看到这个配置类里面有很多都是feign的原生组件,所以这个配置类应该是专门用来配置feign的组件属性的,但是这个配置类并没有在spring.factories,那么这个配置是怎么放到容器里面的的呢?这里就要涉及到springcloud的一个规范包的东西了。springcloud-context规范包中提供了一个扩展类,使用者能够通过该扩展类去根据某个规则去创建不同的子容器,其中这个扩展类就是NamedContextFactory,它是一个抽象类,专门用来做配置隔离的。

SpringCloud整合Feign实现Feign的配置隔离

先来看下NamedContextFactory这个抽象类,它里面只有一个带三个参数的构造方法

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>implements DisposableBean, ApplicationContextAware {
private final String propertySourceName;private final String propertyName;// key是自定义的任意字符串,value是spring容器对象
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();private Map<String, C> configurations = new ConcurrentHashMap<>();private ApplicationContext parent;private Class<?> defaultConfigType;public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,String propertyName) {this.defaultConfigType = defaultConfigType;this.propertySourceName = propertySourceName;this.propertyName = propertyName;
}// 省略
......
}

实现这个抽象类的有两个子类,分别是FeignContext,SpringClientFactory,其中FeignContext对应是为Feign服务的,SpringClientFactory是为Ribbon服务的,我们这里看FeignContext(SpringClientFactory与其大同小异)

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {public FeignContext() {super(FeignClientsConfiguration.class, "feign", "feign.client.name");}}

可以看到FeignContext只是在它的构造方法中调用了父类的构造方法,并且还传了三个参数,其中第一个参数有点熟悉,这不就是我们feign的组件配置类FeignClientsConfiguration吗?那这里把这个类传进去有什么用呢?接着继续看它的getContext和createContext方法

protected AnnotationConfigApplicationContext getContext(String name) {// 判断contexts里面是否有该name的容器if (!this.contexts.containsKey(name)) {synchronized (this.contexts) {if (!this.contexts.containsKey(name)) {// 创建该name对应的容器对象,并且放到contexts中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);}}}// 把FeignClientsConfiguration放到容器中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/3101context.setClassLoader(this.parent.getClassLoader());}context.setDisplayName(generateDisplayName(name));// 刷新新容器context.refresh();return context;
}

可以看到createContext方法会创建一个新的spring容器,并且会把FeignClientsConfiguration配置类注册进这个新容器中,当新容器refresh之后,新容器里面就会存在feign组件的这些bean了

在createContext方法一开始还有两个for循环,并且这两个for循坏都是遍历configurations这个list,那么这个list是什么?是从哪里来的?

public void setConfigurations(List<C> configurations) {for (C client : configurations) {this.configurations.put(client.getName(), client);}
}

可以发现这个list有setConfigurations这个入口能够进来,但是这个list里面的元素是一个泛型,是由子类FeignContext去指定这个泛型的,而FeignContext指定的泛型是FeignClientSpecification这个类,我们看下这个类

class FeignClientSpecification implements NamedContextFactory.Specification {private String name;private Class<?>[] configuration;FeignClientSpecification() {}FeignClientSpecification(String name, Class<?>[] configuration) {this.name = name;this.configuration = configuration;}public String getName() {return this.name;}public void setName(String name) {this.name = name;}public Class<?>[] getConfiguration() {return this.configuration;}public void setConfiguration(Class<?>[] configuration) {this.configuration = configuration;}@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (o == null || getClass() != o.getClass()) {return false;}FeignClientSpecification that = (FeignClientSpecification) o;return Objects.equals(this.name, that.name)&& Arrays.equals(this.configuration, that.configuration);}@Overridepublic int hashCode() {return Objects.hash(this.name, this.configuration);}@Overridepublic String toString() {return new StringBuilder("FeignClientSpecification{").append("name='").append(this.name).append("', ").append("configuration=").append(Arrays.toString(this.configuration)).append("}").toString();}}

这个类实现了NamedContextFactory里面的Specification接口,它有两个熟悉,一个是name,一个configuration的class数组,那么这个name应该就是我们上面说的可以是任意字符串,configuration又是什么?带着这个问题,我们需要去看它是怎么被创建并且注入进来的

我们上面知道有一个setConfiguration能够把一个FeignClientSpecification集合set进来,所以我们去到FeignContext创建的时候看看是否在创建的时候调用了这个方法,FeignContext的创建是在FeignAutoConfiguration中创建的,我们回到FeignAutoConfiguration中

@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();@Bean
public FeignContext feignContext() {FeignContext context = new FeignContext();context.setConfigurations(this.configurations);return context;
}

可以看到确实是在FeignContext的时候把FeignClientSpecification放进来的,但是这些FeignClientSpecification是通过@Autowired依赖注入进FeignAutoConfiguration的,也就是说这些FeignClientSpecification都是在当前的应用容器中的,那又是什么时候把这些FeignClientSpecification放到容器中的呢?

答案就是feign的接口代理对象放到spring容器的时候,这个时候每个feign接口会去根据@FeignClient注解去生成对应的FeignClientSpecification然后放到spring容器中,具体我们看feign接口往spring容器放入的过程

FeignClientsRegistrar

我们直接来到FeignClientRegistrar这个类,为什么来到这个类?因为这个类是@EnableFeignClients注解里面通过@Import注解导入到spring容器的类,而这个类又实现了spring的ImportBeanDefinitionRegistrar接口,所以我们可以在这个里面直接往容器中去放bd,重点看ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法

public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {registerDefaultConfiguration(metadata, registry);registerFeignClients(metadata, registry);
}

可以看到这个方法中调用了两个方法,这两个方法都是往spring容器中去放bd的,里面就有我们所关注的FeignClientSpecification对象的创建

private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {String name;if (metadata.hasEnclosingClass()) {name = "default." + metadata.getEnclosingClassName();}else {name = "default." + metadata.getClassName();}registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));}
}
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());
}

我们可以看到上面的代码中主要就是创建FeignClientSpecification的bd,当spring通过构造方法反射得到FeignClientSpecification对象时,FeignClientSpecification对象里面的name就是default +"." +@EnableFeignClients注解标注的类的全类名,configuration就是@EnableFeignClients注解的defaultConfiguration属性的class数组,也就是说如果我们在@EnableFeignClients注解上面声明了defaultConfiguration属性,那么就会往spring容器中注册一个FeignClientSpecification对象,并且这个对象的name就是default +"." +@EnableFeignClients注解标注的类的全类名,configuration就是@EnableFeignClients注解的defaultConfiguration属性的class数组

那就spring容器中就只有这一个FeignClientSpecification对象了吗?当然不是,除了这里有往容器中注册FeignClientSpecification对象之外,registerFeignClients方法也会往容器中注册FeignClientSpecification对象

public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);Set<String> basePackages;Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);final Class<?>[] clients = attrs == null ? null: (Class<?>[]) attrs.get("clients");if (clients == null || clients.length == 0) {scanner.addIncludeFilter(annotationTypeFilter);basePackages = getBasePackages(metadata);}else {final Set<String> clientClasses = new HashSet<>();basePackages = new HashSet<>();for (Class<?> clazz : clients) {basePackages.add(ClassUtils.getPackageName(clazz));clientClasses.add(clazz.getCanonicalName());}AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {@Overrideprotected boolean match(ClassMetadata metadata) {String cleaned = metadata.getClassName().replaceAll("\\$", ".");return clientClasses.contains(cleaned);}};scanner.addIncludeFilter(new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));}for (String basePackage : basePackages) {Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// verify annotated class is an interfaceAnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;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());// 获取name,这个name就是我们上面说的那个任意字符串String name = getClientName(attributes);registerClientConfiguration(registry, name,attributes.get("configuration"));// 往容器中注册该接口的工厂bdregisterFeignClient(registry, annotationMetadata, attributes);}}}
}

上面就是通过spring的扫描器去指定的包路径下扫描带有@FeignClient的接口类并把这些接口类包装为一个个的工厂bd让spring通过FactoryBean去创建出接口的代理对象并放到spring容器中,在这个过程中我们可以看到再次调用了registerClientConfiguration方法去给容器中注册FeignClientSpecification对象,但是这次是把@FeignClient注解中的configuration属性的值拿出来作为FeignClientSpecification的configuration,而name是什么?name是从getClientName方法中返回出来的

private String getClientName(Map<String, Object> client) {if (client == null) {return null;}String value = (String) client.get("contextId");if (!StringUtils.hasText(value)) {value = (String) client.get("value");}if (!StringUtils.hasText(value)) {value = (String) client.get("name");}if (!StringUtils.hasText(value)) {value = (String) client.get("serviceId");}if (StringUtils.hasText(value)) {return value;}throw new IllegalStateException("Either 'name' or 'value' must be provided in @"+ FeignClient.class.getSimpleName());
}

可以看到先从@FeignClient注解的contextId属性取值,如果取出来的值是空,那么就取value的值,再没有的取name的值,再没有就取serviceId的值,也就是contextId>value>name>servieId,而我们通常都是直接在@FeignClient注解的name这个属性声明该接口所属的服务的服务名,所以通常来说FeignClientSpecification的name就是该api接口所属的服务名(如果有两个api接口所声明的name是相同的,也就是说都属于同一个服务的接口,这种情况是很常见的,而这种情况在稍微新一点的版本中应用在启动的时候就会报错,原因就是往spring容器中注册了相同名称的FeignClientSpecification对象,这在spring中默认不允许注册相同名称的bd的,当然也可以设置,但是并不推荐,而是我们都会显式地在这个api接口的@FeignClient注解上面声明contextId属性为接口的全类名,这样启动就不会报错了,但这仅仅是为了解决应用能够正常启动,我们要知道contextId属性主要是给我们用来区分容器配置的)

为每一个feign接口类的@FeignClient注解声明contextId的效果?

根据上面我们跟着源码可以知道,如果为每一个feign接口类的@FeignClient注解声明contextId的话,此时容器中的FeignClientSpecification的个数就和feign接口的个数一样,并且它们的name都分别是对应feign接口的@FeignClient注解的contextId属性值,configuration为@FeignClient注解的configuration属性值,那么我们又回到NamedContextFactory的createContext方法的那两个for循坏

// 此时configurations这个map里面就会有容器中所有的FeignClientSpecification了
// 当feign创建其代理对象的时候,需要各种组件构造其Feign.Build
// 需要一个组件的时候就会传在@FeignClient注解声明的contextId过来
// 先会去判断下有没有该contextId所属的spring容器对象
// 如果没有的话就需要创建spring容器对象,那么就会来到createContext方法
// 所以这里的name就是contextId
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);}}
}

当feign创建其代理对象的时候,需要各种组件构造其Feign.Build,需要一个组件的时候就会传在@FeignClient注解声明的contextId过来,先会去判断下有没有该contextId所属的spring容器对象,如果没有的话就需要创建spring容器对象,那么就会来到createContext方法,所以这里的name就是contextId。可以看到在第一个for循环中会去根据contextId从configurations中找到对应的配置类数组然后进行遍历往新的spring容器中去注册,而第二个for循环我们可以看到它是取name以default.开头的配置类数组,我们上面也看到了name以defalut.开头的FeignClientSpecification是怎么来的了,就是在@EnableFeignClient注解的defaultConfiguration属性上声明了配置类,然后这个配置类就会往所有新创建的spring容器去注册了,也就是说在@EnableFeignClient注解的defaultConfiguration属性上声明的配置类会作为全局配置去使用,而在某一个feign接口的@FeignClient注解上声明contextId+configuration这个配置类只会在此feign接口上有效,而且我们可以发现是先注册feign接口自己的配置类,然后再注册全局的配置类,最后才是注册默认的配置类,所以说配置类的优先级是feign接口自己的配置类>全局的配置类>默认的配置类

SpringCloud整合Feign配置类之间的关系以及feign配置隔离的实现相关推荐

  1. Rose的 一些简单说明--类与类之间的关系、用例与用例之间的关系

    1.uml中图的放置位置 注释: 用例视图 用例视图中包括了系统中的所有参与者.用例和用例图,必要时还可以在用例视图中添加顺序图.活动图等 逻辑视图 逻辑系统关注系统是如何实现用例中所描述的功能的,主 ...

  2. C++【对象模型】 | 【05】类与类之间各种关系下对数据成员的存取、绑定、布局

    文章目录 索引 1.类继承造成的负担 2.data member 3.data member的绑定 4.data member的布局 5.data member的存取 静态数据成员 非静态数据成员 6 ...

  3. Day-16 面向对象03 类与类之间的关系

    一.类与类之间的依赖关系 我用着你,但是你不属于我,这种关系是最弱的,比如,公司和雇员之间,对于正式员工,肯定要签订劳动合同,还得小心伺候着,但是如果是兼职,那无所谓,需要了你就来,不需要你就可以拜拜 ...

  4. 40)类与类之间的关系(has use is)

    1)类与类之间的关系  一般就是三类: ①has--A ②use--A ③is-----A ①has--A  包含关系,用以描述一个类由多个"部件构成".实现has--A关系用类成 ...

  5. 02 面向对象之:类空间问题以及类之间的关系

    一. 类的空间问题 1.1 何处可以添加对象属性 class A:def __init__(self,name):self.name = namedef func(self,sex):self.sex ...

  6. python:类的空间问题 类与类之间的关系

    1.类的空间问题 1.1何处可以添加对象属性 class A:def __init__(self,name):self.name = namedef func(self,sex):self.sex = ...

  7. 详解:面向对象与面向过程的比较 类之间的关系:泛化、实现、依赖、关联、聚合、组合

    文章目录 1.面向对象程序设计概述 1.1 面向对象程序设计 1.2 传统结构化程序设计 1.3 面向对象与面向过程举例 2.类之间的关系 2.1 泛化/继承 2.2 实现 2.3 依赖 2.4 关联 ...

  8. 《Java 核心技术卷1 第10版》学习笔记------ 类之间的关系

    在类之间, 最常见的关系有 •依赖 (" uses-a") •聚合(" has-a") •继承(" is-a") 依赖( dependenc ...

  9. python类的空间问题及类之间的关系

    类的空间问题及类之间的关系 类的空间问题 1.何处可以添加对象属性 class A:def __init__(self,name):self.name = namedef func(self,sex) ...

  10. 接口、抽象类、类之间的关系

    接口.抽象类.类之间的关系 接口和接口:继承 接口和抽象类:抽象类实现接口 类和抽象类:类继承抽象类 类和类:继承

最新文章

  1. ppt转换成pdf转换器免费版
  2. 几十万人同时在线的直播间聊天,如何设计服务端架构?
  3. 语言 micropython_MicroPython蓝牙BLE例程实操(一)
  4. 牛顿新定律:凭本事得到的,凭什么要还?
  5. [Linux] 修改系统默认编码
  6. Leetcode--322. 零钱兑换
  7. Mac生成ssh,并添加公钥到Github
  8. aws s3 獲取所有文件_Url从Amazon S3获取文件
  9. 语音识别技术发展迅速,这本书是你需要的全方位解读语音识别的最新著作!
  10. java 经纬度距离_java根据经纬度计算距离
  11. JS 不可逆加密后半部分,去混淆还原代码。
  12. 软考信息系统项目管理师考试论文写作技巧
  13. pip报错 Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-wa
  14. 【mysql】mysql 中 text,longtext,mediumtext 字段类型的意思, 以及区别
  15. tf15: 中文语音识别
  16. AS608指纹模块于51单片机的二次开发
  17. 网易我的世界服务器正在维护,网易《我的世界》Hypixel中国版服务器将停止运营...
  18. java如何通过域名查ip_JAVA实现通过IP反查域名
  19. 图文解决系列之解决Submit including parents
  20. 如何设置条码数据的对齐方式

热门文章

  1. Docker的核心概念镜像Images, 容器Containers, 数据卷Volumes
  2. golang 大数据平台_一文读懂数据平台、大数据平台、数据中台
  3. linux系统数据库导出语句,数据库应用-SQL语句导入导出大全
  4. 8月7日晚八点分享-推荐系统面试/概览
  5. linux系统同时安装python2.x和3.x
  6. linux 系统内存占用高,linux free 命令以及系统内存占用过高的处理方法
  7. 组合数学专项练习笔记
  8. 【生信进阶练习1000days】day7-RSQLite的使用
  9. 【GYM-100889 D】Dicy Numbers【数学推导求解】
  10. 应急响应的基本流程二