简单说明
四个新建类
DynamicFeignClient
EnableDynamicFeignClients
DynamicFeignClientsRegistrar
DynamicFeignClientSpecification
启动类加上注解@EnableDynamicFeignClients("com.xxx)
服务接口换注解@DynamicFeignClient(value = "${xxx.service.demo}")
思路
OpenFeign是通过FeignClientsRegistrar实现代理类的注入的。
通过实现ImportBeanDefinitionRegistrar接口在容器启动过程中,注册bean定义,这个接口的调用比较靠前,关键点。
代码不详叙,读者可以自己看相应源码。下面列出关键。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
略过一部分代码,为什么通过@FeignClient注解不能使用参数注入读取spring中配置的参数在于下面。
registerFeignClients方法中
//这里获取到注解的属性,也就是我们@FeignClient(value ="xxx",url="xxxx.com")中的键值对
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
//
//省略
//
//这里有一次属性验证,会验证value、contextId 是否正确,所以通过SpEL取配置值会直接报错
//就算没有这行代码是否会过未验证,可能不行,因为这个调用加载比较靠前了
validate(attributes);
//以取name为例,调用了resolve方法
String getName(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
String name = (String) attributes.get("serviceId");
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("name");
}
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value");
}
name = resolve(beanFactory, name);
return getName(name);
}
//resolve方法中做了一次取值,这里有个关键
private String resolve(ConfigurableBeanFactory beanFactory, String value) {
if (StringUtils.hasText(value)) {
if (beanFactory == null) {
return this.environment.resolvePlaceholders(value);
}
BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver();
//这里和@value获取配置文件的原理部分是差不多了,通过了一个内置的resolver,
String resolved = beanFactory.resolveEmbeddedValue(value);
if (resolver == null) {
return resolved;
}
Object evaluateValue = resolver.evaluate(resolved, new BeanExpressionContext(beanFactory, null));
if (evaluateValue != null) {
return String.valueOf(evaluateValue);
}
return null;
}
return value;
}
AbstractBeanFactory
@Override
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {
if (value == null) {
return null;
}
String result = value;
//该方法这里有个List,embeddedValueResolvers在容器的加载过程中会装入几个内置的resolver
//我们用到的内置的@value同款resolver就在里面,这里就有个问题,我们调用的时候需要在resolver装入后。
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
if (result == null) {
return null;
}
}
return result;
}
所以我们的思路就明确了:
1.新建一个注解@DynamicFeignClient
2重写一个DynamicFeignClientsRegistrar,
3 扫描并读取这个注解,在容器启动时注入Feign客户端代理
4.新建@EnableDynamicFeignClients("com.xxx") 告诉scanner要扫描哪些包
这里要注意注入bean的时候需要在spring容器调用ImportBeanDefinitionRegistrar之后,因为需要读取配置,配置的加载需要在注入前。spring容器启动的生命周期可以看别的博文,这里只需要实现BeanFactoryPostProcessor,在bean工厂后置处理器接口调用就行了。
实现
DynamicFeignClient
原样照抄@FeignClient就行了
package com.cscn.cloud.auth.anotations;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* @author Caelumlux
* @ClassName DynamicFeignClient
* @Description
* @Date 2024/3/7 17:14
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Component
public @interface DynamicFeignClient {
/**
* The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}.
* @return the name of the service with optional protocol prefix
*/
@AliasFor("name")
String value() default "";
/**
* This will be used as the bean name instead of name if present, but will not be used
* as a service id.
* @return bean name instead of name if present
*/
String contextId() default "";
/**
* @return The service id with optional protocol prefix. Synonym for {@link #value()
* value}.
*/
@AliasFor("value")
String name() default "";
/**
* @return the <code>@Qualifier</code> value for the feign client.
* @deprecated in favour of {@link #qualifiers()}.
*
* If both {@link #qualifier()} and {@link #qualifiers()} are present, we will use the
* latter, unless the array returned by {@link #qualifiers()} is empty or only
* contains <code>null</code> or whitespace values, in which case we'll fall back
* first to {@link #qualifier()} and, if that's also not present, to the default =
* <code>contextId + "FeignClient"</code>.
*/
@Deprecated
String qualifier() default "";
/**
* @return the <code>@Qualifiers</code> value for the feign client.
*
* If both {@link #qualifier()} and {@link #qualifiers()} are present, we will use the
* latter, unless the array returned by {@link #qualifiers()} is empty or only
* contains <code>null</code> or whitespace values, in which case we'll fall back
* first to {@link #qualifier()} and, if that's also not present, to the default =
* <code>contextId + "FeignClient"</code>.
*/
String[] qualifiers() default {};
/**
* @return an absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";
/**
* @return whether 404s should be decoded instead of throwing FeignExceptions
*/
boolean decode404() default false;
/**
* A custom configuration class for the feign client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
* @return list of configurations for feign client
*/
Class<?>[] configuration() default {};
/**
* Fallback class for the specified Feign client interface. The fallback class must
* implement the interface annotated by this annotation and be a valid spring bean.
* @return fallback class for the specified Feign client interface
*/
Class<?> fallback() default void.class;
/**
* Define a fallback factory for the specified Feign client interface. The fallback
* factory must produce instances of fallback classes that implement the interface
* annotated by {@link FeignClient}. The fallback factory must be a valid spring bean.
*
* @see FallbackFactory for details.
* @return fallback factory for the specified Feign client interface
*/
Class<?> fallbackFactory() default void.class;
/**
* @return path prefix to be used by all method-level mappings.
*/
String path() default "";
/**
* @return whether to mark the feign proxy as a primary bean. Defaults to true.
*/
boolean primary() default true;
}
EnableDynamicFeignClients
原样照抄@EnableFeignClients就行了,但是要删去@Import(DynamicFeignClientsRegistrar.class),下面代码我注释掉了这行,因为我们的DynamicFeignClientsRegistrar不实现ImportBeanDefinitionRegistrar,不通过@Import导入
这个注解其实可以不用的,为什么还是有了呢?
1.我懒得在再去研究读取包路劲,写Registrar中scanner部分的代码了
2.Registrar中AnnotationMetadata获取方便,不用找别的类
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cscn.cloud.auth.anotations;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import java.lang.annotation.*;
/**
* Scans for interfaces that declare they are feign clients (via
* {@link FeignClient} <code>@FeignClient</code>).
* Configures component scanning directives for use with
* {@link org.springframework.context.annotation.Configuration}
* <code>@Configuration</code> classes.
*
* @author Spencer Gibb
* @author Dave Syer
* @since 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//@Import(DynamicFeignClientsRegistrar.class)
public @interface EnableDynamicFeignClients {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
* @return list of default configurations
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath
* scanning.
* @return list of FeignClient classes
*/
Class<?>[] clients() default {};
}
DynamicFeignClientsRegistrar
根据FeignClientsRegistrar修改过来,不实现ImportBeanDefinitionRegistrar,改用实现BeanFactoryPostProcessor接口
实现方法:
@SneakyThrows
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory(resourceLoader);
//这里注意!!!XxxxApplication.class需要换成你的启动类,并且打上@EnableDynamicFeignClients("com.xxx")注解包路劲正确,不要问我为什么需要加包路径,因为我没去看怎么读取默认的@ComponentScan或者@SpringBootApplication的包路径
AnnotationMetadata annotationMetadata = simpleMetadataReaderFactory.
getMetadataReader(XxxxApplication.class.getName()).getAnnotationMetadata();
//这两行就是就的FeignClientsRegistrar中的调用,两个参数通过上面几行代码来
registerDefaultConfiguration(annotationMetadata, registry);
registerDynamicFeignClients(annotationMetadata, registry);
}
}
以下是完整代码,根据需要改动
package com.cscn.cloud.auth.config;
import com.cscn.cloud.auth.AuthAutoConfig;
import com.cscn.cloud.auth.anotations.DynamicFeignClient;
import com.cscn.cloud.auth.anotations.EnableDynamicFeignClients;
import com.cscn.cloud.auth.feign.DynamicFeignClientSpecification;
import feign.Request;
import lombok.SneakyThrows;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.*;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cloud.openfeign.FeignClientFactoryBean;
import org.springframework.cloud.openfeign.OptionsFactoryBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
/**
* @author Caelumlux
* @ClassName DynamicFeignClientsRegistrar
* @Description
* @Date 2024/3/7 17:27
*/
@Configuration
public class DynamicFeignClientsRegistrar implements ResourceLoaderAware, EnvironmentAware, BeanFactoryPostProcessor {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
DynamicFeignClientsRegistrar() {
}
@SneakyThrows
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory(resourceLoader);
AnnotationMetadata annotationMetadata = simpleMetadataReaderFactory.
getMetadataReader(AuthAutoConfig.class.getName()).getAnnotationMetadata();
registerDefaultConfiguration(annotationMetadata, registry);
registerDynamicFeignClients(annotationMetadata, registry);
}
}
private void validateFallback(final Class clazz) {
Assert.isTrue(!clazz.isInterface(), "Fallback class must implement the interface annotated by @FeignClient");
}
private void validateFallbackFactory(final Class clazz) {
Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances "
+ "of fallback classes that implement the interface annotated by @FeignClient");
}
private String FormatURI(String name) {
if (!StringUtils.hasText(name)) {
return "";
}
String host = null;
try {
String url;
if (!name.startsWith("http://") && !name.startsWith("https://")) {
url = "http://" + name;
} else {
url = name;
}
host = new URI(url).getHost();
} catch (URISyntaxException e) {
}
Assert.state(host != null, "Service id not legal hostname (" + name + ")");
return name;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableDynamicFeignClients.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"));
}
}
public void registerDynamicFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableDynamicFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(DynamicFeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
} else {
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
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");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(DynamicFeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
String name = getName(beanFactory, attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
factoryBean.setRefreshableClient(isClientRefreshEnabled());
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[]{contextId + "FeignClient"};
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
registerOptionsBeanDefinition(registry, contextId);
}
private void validate(Map<String, Object> attributes) {
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
// This blows up if an aliased property is overspecified
// FIXME annotation.getAliasedString("name", DynamicFeignClient.class, null);
validateFallback(annotation.getClass("fallback"));
validateFallbackFactory(annotation.getClass("fallbackFactory"));
}
String getName(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
String name = (String) attributes.get("serviceId");
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("name");
}
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value");
}
name = resolve(beanFactory, name);
return FormatURI(name);
}
private String getContextId(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
String contextId = (String) attributes.get("contextId");
if (!StringUtils.hasText(contextId)) {
return getName(beanFactory, attributes);
}
contextId = resolve(beanFactory, contextId);
return FormatURI(contextId);
}
private String getUrl(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
String url = resolve(beanFactory, (String) attributes.get("url"));
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
if (!url.contains("://")) {
url = "http://" + url;
}
try {
new URL(url);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(url + " is malformed", e);
}
}
return url;
}
private String getPath(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
String path = resolve(beanFactory, (String) attributes.get("path"));
if (StringUtils.hasText(path)) {
path = path.trim();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
}
return path;
}
private String resolve(ConfigurableBeanFactory beanFactory, String value) {
if (StringUtils.hasText(value)) {
if (beanFactory == null) {
return this.environment.resolvePlaceholders(value);
}
BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver();
String resolved = beanFactory.resolveEmbeddedValue(value);
if (resolver == null) {
return resolved;
}
Object evaluateValue = resolver.evaluate(resolved, new BeanExpressionContext(beanFactory, null));
if (evaluateValue != null) {
return String.valueOf(evaluateValue);
}
return null;
}
return value;
}
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) {
if (!beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
}
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableDynamicFeignClients.class.getCanonicalName());
Set<String> basePackages = new HashSet<>();
for (String pkg : (String[]) attributes.get("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : (String[]) attributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
private String getQualifier(Map<String, Object> client) {
if (client == null) {
return null;
}
String qualifier = (String) client.get("qualifier");
if (StringUtils.hasText(qualifier)) {
return qualifier;
}
return null;
}
private String[] getQualifiers(Map<String, Object> client) {
if (client == null) {
return null;
}
List<String> qualifierList = new ArrayList<>(Arrays.asList((String[]) client.get("qualifiers")));
qualifierList.removeIf(qualifier -> !StringUtils.hasText(qualifier));
if (qualifierList.isEmpty() && getQualifier(client) != null) {
qualifierList = Collections.singletonList(getQualifier(client));
}
return !qualifierList.isEmpty() ? qualifierList.toArray(new String[0]) : null;
}
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 @" + DynamicFeignClient.class.getSimpleName());
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(DynamicFeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + DynamicFeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* This method is meant to create {@link Request.Options} beans definition with
* refreshScope.
*
* @param registry spring bean definition registry
* @param contextId name of feign client
*/
private void registerOptionsBeanDefinition(BeanDefinitionRegistry registry, String contextId) {
if (isClientRefreshEnabled()) {
String beanName = Request.Options.class.getCanonicalName() + "-" + contextId;
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(OptionsFactoryBean.class);
definitionBuilder.setScope("refresh");
definitionBuilder.addPropertyValue("contextId", contextId);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(definitionBuilder.getBeanDefinition(),
beanName);
definitionHolder = ScopedProxyUtils.createScopedProxy(definitionHolder, registry, true);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
}
private boolean isClientRefreshEnabled() {
return environment.getProperty("feign.client.refresh-enabled", Boolean.class, false);
}
}
DynamicFeignClientSpecification
DynamicFeignClientsRegistrar要用,基本是复制的FeignClientsRegistrar,这里因为不该的话会因为和原生OpenFeign出现同名类无法注入的问题。
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cscn.cloud.auth.feign;
import org.springframework.cloud.context.named.NamedContextFactory;
import java.util.Arrays;
import java.util.Objects;
/**
* @author Dave Syer
* @author Gregor Zurowski
*/
public class DynamicFeignClientSpecification implements NamedContextFactory.Specification {
private String name;
private Class<?>[] configuration;
DynamicFeignClientSpecification() {
}
public DynamicFeignClientSpecification(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;
}
DynamicFeignClientSpecification that = (DynamicFeignClientSpecification) 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();
}
}
总结
主要就是通过扫描新的DynamicFeignClient,生成的Feign代理客户端时候支持获取spring配置的值。
后面的动作都是围绕这个进行的,容器启动->基本加载完毕->后置处理器扫描自定义注解->生成代理类
后面就是正常调用了,和@FeignClient使用一样的。
需要在启动类上加上
@EnableFeignClients
@EnableDynamicFeignClients("com.xxx")
@EnableDiscoveryClient
网友评论