♻️ 重构 feign 导入支持单体版本通过SPI 导入

This commit is contained in:
冷冷 2024-04-04 21:32:23 +08:00
parent 1af0c068b0
commit d302fb71f1

View File

@ -34,6 +34,7 @@ import org.springframework.core.env.Environment;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.ArrayList; import java.util.ArrayList;
@ -48,199 +49,228 @@ import java.util.Map;
*/ */
public class PigFeignClientsRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, EnvironmentAware { public class PigFeignClientsRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, EnvironmentAware {
@Getter private final static String BASE_URL = "http://127.0.0.1:${server.port}${server.servlet.context-path}";
private ClassLoader beanClassLoader;
@Getter @Getter
private Environment environment; private ClassLoader beanClassLoader;
@Override @Getter
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { private Environment environment;
registerFeignClients(registry);
}
@Override @Override
public void setBeanClassLoader(ClassLoader classLoader) { public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
this.beanClassLoader = classLoader; registerFeignClients(registry);
} }
private void registerFeignClients(BeanDefinitionRegistry registry) { @Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
List<String> feignClients = new ArrayList<>( private void registerFeignClients(BeanDefinitionRegistry registry) {
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())); List<String> feignClients = new ArrayList<>();
// 支持 springboot 2.7 + 最新版本的配置方式 // 支持 springboot 2.7 + 最新版本的配置方式
ImportCandidates.load(FeignClient.class, getBeanClassLoader()).forEach(feignClients::add); ImportCandidates.load(FeignClient.class, getBeanClassLoader()).forEach(feignClients::add);
// 如果 spring.factories 里为空
if (feignClients.isEmpty()) {
return;
}
for (String className : feignClients) {
try {
Class<?> clazz = beanClassLoader.loadClass(className);
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(clazz,
FeignClient.class);
if (attributes == null) {
continue;
}
// 如果已经存在该 bean支持原生的 Feign
if (registry.containsBeanDefinition(className)) {
continue;
}
registerClientConfiguration(registry, getClientName(attributes), attributes.get("configuration"));
validate(attributes); // 如果 spring.factories 里为空
BeanDefinitionBuilder definition = BeanDefinitionBuilder if (feignClients.isEmpty()) {
.genericBeanDefinition(FeignClientFactoryBean.class); return;
definition.addPropertyValue("url", getUrl(attributes)); }
definition.addPropertyValue("path", getPath(attributes)); for (String className : feignClients) {
String name = getName(attributes); try {
definition.addPropertyValue("name", name); Class<?> clazz = beanClassLoader.loadClass(className);
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(clazz,
FeignClient.class);
if (attributes == null) {
continue;
}
// 兼容最新版本的 spring-cloud-openfeign尚未发布 // 如果是单体项目自动注入 & url 为空
StringBuilder aliasBuilder = new StringBuilder(18); Boolean isMicro = environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true);
if (attributes.containsKey("contextId")) { // 如果已经存在该 bean支持原生的 Feign
String contextId = getContextId(attributes); if (registry.containsBeanDefinition(className) && isMicro) {
aliasBuilder.append(contextId); continue;
definition.addPropertyValue("contextId", contextId); }
}
else {
aliasBuilder.append(name);
}
definition.addPropertyValue("type", className); registerClientConfiguration(registry, getClientName(attributes), className,
definition.addPropertyValue("decode404", attributes.get("decode404")); attributes.get("configuration"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); validate(attributes);
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("url", getUrl(registry, attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
// alias // 兼容最新版本的 spring-cloud-openfeign尚未发布
String alias = aliasBuilder.append("FeignClient").toString(); StringBuilder aliasBuilder = new StringBuilder(18);
if (attributes.containsKey("contextId")) {
String contextId = getContextId(attributes);
aliasBuilder.append(contextId);
definition.addPropertyValue("contextId", contextId);
} else {
aliasBuilder.append(name);
}
// has a default, won't be null definition.addPropertyValue("type", className);
boolean primary = (Boolean) attributes.get("primary"); definition.addPropertyValue("dismiss404",
Boolean.parseBoolean(String.valueOf(attributes.get("dismiss404"))));
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
definition.addPropertyValue("fallbackFactory", fallbackFactory instanceof Class ? fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
beanDefinition.setPrimary(primary); AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
String qualifier = getQualifier(attributes); // alias
if (StringUtils.hasText(qualifier)) { String alias = aliasBuilder.append("FeignClient").toString();
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, // has a default, won't be null
new String[] { alias }); boolean primary = (Boolean) attributes.get("primary");
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
} beanDefinition.setPrimary(primary);
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/** String qualifier = getQualifier(attributes);
* Return the class used by {@link SpringFactoriesLoader} to load configuration if (StringUtils.hasText(qualifier)) {
* candidates. alias = qualifier;
* @return the factory class }
*/
private Class<?> getSpringFactoriesLoaderFactoryClass() {
return PigFeignAutoConfiguration.class;
}
private void validate(Map<String, Object> attributes) { BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes); new String[]{alias});
// This blows up if an aliased property is overspecified BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
FeignClientsRegistrar.validateFallback(annotation.getClass("fallback"));
FeignClientsRegistrar.validateFallbackFactory(annotation.getClass("fallbackFactory"));
}
private String getName(Map<String, Object> attributes) { } catch (ClassNotFoundException e) {
String name = (String) attributes.get("serviceId"); e.printStackTrace();
if (!StringUtils.hasText(name)) { }
name = (String) attributes.get("name"); }
} }
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value");
}
name = resolve(name);
return FeignClientsRegistrar.getName(name);
}
private String getContextId(Map<String, Object> attributes) { /**
String contextId = (String) attributes.get("contextId"); * Return the class used by {@link SpringFactoriesLoader} to load configuration
if (!StringUtils.hasText(contextId)) { * candidates.
return getName(attributes); *
} * @return the factory class
*/
private Class<?> getSpringFactoriesLoaderFactoryClass() {
return PigFeignAutoConfiguration.class;
}
contextId = resolve(contextId); private void validate(Map<String, Object> attributes) {
return FeignClientsRegistrar.getName(contextId); AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
} // This blows up if an aliased property is overspecified
FeignClientsRegistrar.validateFallback(annotation.getClass("fallback"));
FeignClientsRegistrar.validateFallbackFactory(annotation.getClass("fallbackFactory"));
}
private String resolve(String value) { private String getName(Map<String, Object> attributes) {
if (StringUtils.hasText(value)) { String name = (String) attributes.get("serviceId");
return this.environment.resolvePlaceholders(value); if (!StringUtils.hasText(name)) {
} name = (String) attributes.get("name");
return value; }
} if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value");
}
name = resolve(name);
return FeignClientsRegistrar.getName(name);
}
private String getUrl(Map<String, Object> attributes) { private String getContextId(Map<String, Object> attributes) {
String url = resolve((String) attributes.get("url")); String contextId = (String) attributes.get("contextId");
return FeignClientsRegistrar.getUrl(url); if (!StringUtils.hasText(contextId)) {
} return getName(attributes);
}
private String getPath(Map<String, Object> attributes) { contextId = resolve(contextId);
String path = resolve((String) attributes.get("path")); return FeignClientsRegistrar.getName(contextId);
return FeignClientsRegistrar.getPath(path); }
}
@Nullable private String resolve(String value) {
private String getQualifier(@Nullable Map<String, Object> client) { if (StringUtils.hasText(value)) {
if (client == null) { return this.environment.resolvePlaceholders(value);
return null; }
} return value;
String qualifier = (String) client.get("qualifier"); }
if (StringUtils.hasText(qualifier)) {
return qualifier;
}
return null;
}
@Nullable private String getUrl(BeanDefinitionRegistry registry, Map<String, Object> attributes) {
private String getClientName(@Nullable 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( // 如果是单体项目自动注入 & url 为空
"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName()); Boolean isMicro = environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true);
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { if (isMicro) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class); return null;
builder.addConstructorArgValue(name); }
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
@Override String url = resolve(BASE_URL);
public void setEnvironment(Environment environment) {
this.environment = environment; return FeignClientsRegistrar.getUrl(url);
} }
private String getPath(Map<String, Object> attributes) {
String path = resolve((String) attributes.get("path"));
return FeignClientsRegistrar.getPath(path);
}
@Nullable
private String getQualifier(@Nullable Map<String, Object> client) {
if (client == null) {
return null;
}
String qualifier = (String) client.get("qualifier");
if (StringUtils.hasText(qualifier)) {
return qualifier;
}
return null;
}
@Nullable
private String getClientName(@Nullable 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());
}
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());
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(className);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
} }