package io.dataease.rpc; import feign.Request; import io.dataease.feign.DeFeign; import org.springframework.aop.scope.ScopedProxyUtils; 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.*; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 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.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.*; public class DeFeignRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { private ResourceLoader resourceLoader; private Environment environment; public DeFeignRegister() { } static void validateFallback(final Class clazz) { Assert.isTrue(!clazz.isInterface(), "Fallback class must implement the interface annotated by @DeFeign"); } static void validateFallbackFactory(final Class clazz) { Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances " + "of fallback classes that implement the interface annotated by @DeFeign"); } static String getName(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 ignored) { } Assert.state(host != null, "Service id not legal hostname (" + name + ")"); return name; } static String getUrl(String 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; } static String getPath(String 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; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDeFeigns(metadata, registry); } public void registerDeFeigns(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { LinkedHashSet candidateComponents = new LinkedHashSet<>(); Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.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(DeFeign.class)); Set 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 beanDefinition) { // verify annotated class is an interface AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@DeFeign can only be specified on an interface"); Map attributes = annotationMetadata .getAnnotationAttributes(DeFeign.class.getCanonicalName()); registerDeFeign(registry, annotationMetadata, attributes); } } } private void registerDeFeign(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map attributes) { String className = annotationMetadata.getClassName(); if (String.valueOf(false).equals( environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) { eagerlyRegisterDeFeignBeanDefinition(className, attributes, registry); } else { lazilyRegisterDeFeignBeanDefinition(className, attributes, registry); } } private void eagerlyRegisterDeFeignBeanDefinition(String className, Map attributes, BeanDefinitionRegistry registry) { validate(attributes); BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class); definition.addPropertyValue("url", getUrl(null, attributes)); definition.addPropertyValue("path", getPath(null, attributes)); String name = getName(attributes); definition.addPropertyValue("name", name); String contextId = getContextId(null, attributes); definition.addPropertyValue("contextId", contextId); definition.addPropertyValue("type", className); definition.addPropertyValue("dismiss404", Boolean.parseBoolean(String.valueOf(attributes.get("dismiss404")))); Object fallback = attributes.get("fallback"); if (fallback != null) { definition.addPropertyValue("fallback", (fallback instanceof Class ? fallback : ClassUtils.resolveClassName(fallback.toString(), null))); } 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); definition.addPropertyValue("refreshableClient", isClientRefreshEnabled()); String[] qualifiers = getQualifiers(attributes); if (ObjectUtils.isEmpty(qualifiers)) { qualifiers = new String[]{contextId + "DeFeign"}; } // This is done so that there's a way to retrieve qualifiers while generating AOT // code definition.addPropertyValue("qualifiers", qualifiers); AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className); // has a default, won't be null boolean primary = (Boolean) attributes.get("primary"); beanDefinition.setPrimary(primary); BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class); registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class); } private void lazilyRegisterDeFeignBeanDefinition(String className, Map attributes, BeanDefinitionRegistry registry) { ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) registry : null; Class clazz = ClassUtils.resolveClassName(className, null); String contextId = getContextId(beanFactory, attributes); String name = getName(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.setDismiss404(Boolean.parseBoolean(String.valueOf(attributes.get("dismiss404")))); 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("DeFeignsRegistrarFactoryBean", 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 + "DeFeign"}; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); registerRefreshableBeanDefinition(registry, contextId, Request.Options.class, OptionsFactoryBean.class); registerRefreshableBeanDefinition(registry, contextId, RefreshableUrl.class, RefreshableUrlFactoryBean.class); } private void validate(Map attributes) { AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes); // This blows up if an aliased property is overspecified // FIXME annotation.getAliasedString("name", DeFeign.class, null); validateFallback(annotation.getClass("fallback")); validateFallbackFactory(annotation.getClass("fallbackFactory")); } /* for testing */ String getName(Map attributes) { return getName(null, attributes); } String getName(ConfigurableBeanFactory beanFactory, Map 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); } private String getContextId(ConfigurableBeanFactory beanFactory, Map attributes) { String contextId = (String) attributes.get("contextId"); if (!StringUtils.hasText(contextId)) { return getName(attributes); } contextId = resolve(beanFactory, contextId); return getName(contextId); } 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; } private String getUrl(ConfigurableBeanFactory beanFactory, Map attributes) { String url = resolve(beanFactory, (String) attributes.get("url")); return getUrl(url); } private String getPath(ConfigurableBeanFactory beanFactory, Map attributes) { String path = resolve(beanFactory, (String) attributes.get("path")); return getPath(path); } 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 getBasePackages(AnnotationMetadata importingClassMetadata) { Map attributes = importingClassMetadata .getAnnotationAttributes(EnableFeignClients.class.getCanonicalName()); Set 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 client) { if (client == null) { return null; } String qualifier = (String) client.get("qualifier"); if (StringUtils.hasText(qualifier)) { return qualifier; } return null; } private String[] getQualifiers(Map client) { if (client == null) { return null; } List 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; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } /** * This method registers beans definition with refreshScope. * * @param registry spring bean definition registry * @param contextId name of feign client * @param beanType type of bean * @param factoryBeanType points to a relevant bean factory */ private void registerRefreshableBeanDefinition(BeanDefinitionRegistry registry, String contextId, Class beanType, Class factoryBeanType) { if (isClientRefreshEnabled()) { String beanName = beanType.getCanonicalName() + "-" + contextId; BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(factoryBeanType); 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("spring.cloud.openfeign.client.refresh-enabled", Boolean.class, false); } }