概述
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<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
// 注入多个容器配置工厂对象
@Bean
public 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
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
// 注入默认的Targeter
// 与上面的HystrixTargeter互斥
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public 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() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public 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);
}
@PreDestroy
public 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);
}
@Bean
public 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;
}
@PreDestroy
public 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
@ConditionalOnMissingBean
public 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());
}
@Override
public 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());
}
@Override
public void loadProperties(String clientName) {
}
@Override
public 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 {
@Autowired
private 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
@ConditionalOnMissingBean
public 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")
@ConditionalOnMissingBean
public Encoder feignEncoderPageable() {
return new PageableSpringEncoder(new SpringEncoder(this.messageConverters));
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
@Bean
public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
}
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public 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/3101
context.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;
}
@Override
public 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);
}
@Override
public int hashCode() {
return Objects.hash(this.name, this.configuration);
}
@Override
public 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() {
@Override
protected 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 interface
AnnotatedBeanDefinition 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"));
// 往容器中注册该接口的工厂bd
registerFeignClient(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配置隔离的实现的全部内容,希望文章能够帮你解决SpringCloud整合Feign配置类之间的关系以及feign配置隔离的实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复