我的目的是参照原本openfeign的注解,但是我想实现每个外部服务的URL可以自己指定生成的规则。
自定义EnableMyFeignClients
自定义MyImportBeanDefinitionRegistrar,更改获取URL的逻辑。这个应该是个可行的方式,但是RIBBON的视线方式应该会更好之后再看。
//TODO modify here to set the rule to get the url
factoryBean.setUrl(getUrl(beanFactory, attributes));
自定义MyFeignClient
package com.example.demospringboot3.FactoryBean;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;import org.springframework.context.annotation.Import;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface EnableMyFeignClients {String[] value() default {};String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<?>[] defaultConfiguration() default {};Class<?>[] clients() default {};}
package com.example.demospringboot3.FactoryBean;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.*;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientFactoryBean;
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.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.*;//注意这里不能加注解,要通过Import导入进去。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {registerFeignClients(metadata, registry);通过工具类生成一个bd,只是这个Db对象比较纯洁没有绑定任何类//BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();为什么要转成GenericBeanDefinition这种类型。因为GenericBeanDefinition有更多修改bd属性的方法。后面我会介绍为什么要修改属性。//GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();//这里很重要。getConstructorArgumentValues是为了获取该bd的所有构造方法,因为我们重写了有参构造方法,所有我们需要带参数过去不然spring没法帮我们实例化,addGenericArgumentValue是添加参数,该代码会执行两步第一步是匹配对应的构造方法,第二步是实例化。//beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserServiceTestInterface.class.getName());因为代理对象类型的,实例化的时候走的是代理类的构造方法//beanDefinition.setBeanClass(MyFactoryBean.class);注册bd//registry.registerBeanDefinition("userServiceTest", beanDefinition);}private void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {//扫描指定的包下,包含MyFeignClient注解的接口LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableMyFeignClients.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(MyFeignClient.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) {//获取包含MyFeignClient注解的接口 的各项参数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(MyFeignClient.class.getCanonicalName());String name = getClientName(attributes);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(attributes);FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(Boolean.FALSE);BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {//TODO modify here to set the rule to get the urlfactoryBean.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 nullboolean 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 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 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 getUrl(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {String url = resolve(beanFactory, (String) attributes.get("url"));return getUrl(url);}private String getPath(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {String path = resolve(beanFactory, (String) attributes.get("path"));return getPath(path);}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 e) {}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;}private String getContextId(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {String contextId = (String) attributes.get("contextId");if (!StringUtils.hasText(contextId)) {return getName(attributes);}contextId = resolve(beanFactory, contextId);return getName(contextId);}/* for testing */ String getName(Map<String, Object> attributes) {return getName(null, attributes);}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);}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 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());}private Environment environment;private ResourceLoader resourceLoader;@Overridepublic void setEnvironment(Environment environment) {this.environment = environment;}@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableFeignClients.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;}protected ClassPathScanningCandidateComponentProvider getScanner() {return new ClassPathScanningCandidateComponentProvider(false, this.environment) {@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {boolean isCandidate = false;if (beanDefinition.getMetadata().isIndependent()) {if (!beanDefinition.getMetadata().isAnnotation()) {isCandidate = true;}}return isCandidate;}};}
}
package com.example.demospringboot3.FactoryBean;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;import org.springframework.core.annotation.AliasFor;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyFeignClient {@AliasFor("name")String value() default "";String contextId() default "";@AliasFor("value")String name() default "";@DeprecatedString qualifier() default "";String[] qualifiers() default {};String url() default "";/*** @return whether 404s should be decoded instead of throwing FeignExceptions*/boolean decode404() default false;Class<?>[] configuration() default {};Class<?> fallback() default void.class;Class<?> fallbackFactory() default void.class;String path() default "";boolean primary() default true;}
使用
启动类加上
@EnableMyFeignClients(basePackages = {"com.example"})
package com.example.demospringboot3.openfeign;import com.example.demospringboot3.FactoryBean.MyFeignClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;@MyFeignClient(name = "baiduMyFeignClient", url = "http://127.0.0.1:8888")
public interface BaiduMyFeignClient {@RequestMapping("/test")void getMy1();
}