郑州做网站公司汉狮价格,Wordpress 提交百度,网站权重如何查询,南昌易动力网站建设公司最近项目有个需求#xff0c;要在启动后#xff0c;动态修改FeignClient的请求路径#xff0c;网上找到的基本都是在FeignClient里使用${…}#xff0c;通过配置文件来定义Feign的接口路径#xff0c;这并不能满足我们的需求
由于某些特殊原因#xff0c;我们的每个接口…最近项目有个需求要在启动后动态修改FeignClient的请求路径网上找到的基本都是在FeignClient里使用${…}通过配置文件来定义Feign的接口路径这并不能满足我们的需求
由于某些特殊原因我们的每个接口都有一个interfacePath定义在接口上的自定义注解中 也就是说FeignClient定义的接口继承自其他模块而其他模块的接口上有个自定义注解描述了该接口的interfacePath如下
FeignClient(value x-module)
public interface XXXService extends XApi{
}XXXMapping(/member)
public interface XApi {所以我们需要在每个FeignClient中将这个XXXMapping的值添加到path属性作为跨服务调用的前缀如果要手动处理每个FeignClient前缀未免太不友好我们希望这个能由程序自动处理
首先看下FeignClient扫描的过程看看有没有合适的时机来处理这个问题
使用Feign的项目一般会在启动类添加注解EnableFeignClients先点进这个注解看下
Retention(RetentionPolicy.RUNTIME)
Target({ElementType.TYPE})
Documented
Import({FeignClientsRegistrar.class})
public interface EnableFeignClients {String[] value() default {};String[] basePackages() default {};Class?[] basePackageClasses() default {};Class?[] defaultConfiguration() default {};Class?[] clients() default {};
}它通过Import注解导入了一个类FeignClientsRegistrar
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {这个类实现了ImportBeanDefinitionRegistrar接口这个接口用于在Spring容器初始化过程中向容器注册一些BeanDefinition这属于Spring源码的范畴这里就不再赘述直接看它的registerBeanDefinitions方法实现 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {this.registerDefaultConfiguration(metadata, registry);this.registerFeignClients(metadata, registry);}只有两行代码看名字第二行是注册FeignClient那么我们的FeignClient基本可以确定是这行代码在处理了点进去
这个方法比较长这里就只贴些关键代码 LinkedHashSetBeanDefinition candidateComponents new LinkedHashSet();MapString, Object attrs metadata.getAnnotationAttributes(EnableFeignClients.class.getName());............ClassPathScanningCandidateComponentProvider scanner this.getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));SetString basePackages this.getBasePackages(metadata);Iterator var8 basePackages.iterator();while(var8.hasNext()) {String basePackage (String)var8.next();candidateComponents.addAll(scanner.findCandidateComponents(basePackage));}Iterator var13 candidateComponents.iterator();while(var13.hasNext()) {BeanDefinition candidateComponent (BeanDefinition)var13.next();if (candidateComponent instanceof AnnotatedBeanDefinition) {AnnotatedBeanDefinition beanDefinition (AnnotatedBeanDefinition)candidateComponent;AnnotationMetadata annotationMetadata beanDefinition.getMetadata();Assert.isTrue(annotationMetadata.isInterface(), FeignClient can only be specified on an interface);MapString, Object attributes annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());String name this.getClientName(attributes);this.registerClientConfiguration(registry, name, attributes.get(configuration));this.registerFeignClient(registry, annotationMetadata, attributes);}}............先定义了一个扫描器通过FeignClient过滤出一组BeanDefinition也就是上面的candidateComponents然后遍历其中有一行代码
MapString, Object attributes annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());这里就获取了FeignClient里定义的各种属性比如value 、path 、contextId等等 然后调用registerFeignClient方法完成注册进入这个方法 private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, MapString, Object attributes) {String className annotationMetadata.getClassName();Class clazz ClassUtils.resolveClassName(className, (ClassLoader)null);ConfigurableBeanFactory beanFactory registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory)registry : null;String contextId this.getContextId(beanFactory, attributes);String name this.getName(attributes);FeignClientFactoryBean factoryBean new FeignClientFactoryBean();factoryBean.setBeanFactory(beanFactory);factoryBean.setName(name);factoryBean.setContextId(contextId);factoryBean.setType(clazz);factoryBean.setRefreshableClient(this.isClientRefreshEnabled());BeanDefinitionBuilder definition BeanDefinitionBuilder.genericBeanDefinition(clazz, () - {factoryBean.setUrl(this.getUrl(beanFactory, attributes));factoryBean.setPath(this.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(), (ClassLoader)null));}Object fallbackFactory attributes.get(fallbackFactory);if (fallbackFactory ! null) {factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class)fallbackFactory : ClassUtils.resolveClassName(fallbackFactory.toString(), (ClassLoader)null));}return factoryBean.getObject();});definition.setAutowireMode(2);definition.setLazyInit(true);this.validate(attributes);AbstractBeanDefinition beanDefinition definition.getBeanDefinition();beanDefinition.setAttribute(factoryBeanObjectType, className);beanDefinition.setAttribute(feignClientsRegistrarFactoryBean, factoryBean);boolean primary (Boolean)attributes.get(primary);beanDefinition.setPrimary(primary);String[] qualifiers this.getQualifiers(attributes);if (ObjectUtils.isEmpty(qualifiers)) {qualifiers new String[]{contextId FeignClient};}BeanDefinitionHolder holder new BeanDefinitionHolder(beanDefinition, className, qualifiers);BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);this.registerOptionsBeanDefinition(registry, contextId);}这个方法虽然长但很清晰先定义了一个FeignClientFactoryBean然后生成一个BeanDefinitionBuilder通过lambda传入了一个InstanceSupplier其持有了FactoryBean在InstanceSupplier中通过设置FactoryBean的url、path属性确定了FeignClient请求的路径
我们通过debug观察一下最终生成的BeanDefinition长什么样子进入registerBeanDefinition方法先获取了beanName这个名字就是我们自己接口的全路径名 第二行代码真正地往容器注册了BeanDefinition在这行打个断点并设置断点的条件方便定位到我们的FeignClient类
可以看到生成的BeanDefinition有个instanceSupplier属性 其内部的AnnotationAttributes就是从FeignClient注解中解析到的配置包括value、path等是 一个Map结构 看到这里便已经有了大致思路了这里的InstanceSupplier持有了从FeignClinent中解析到的各种属性并在将来实例化的时候将这些属性处理为FeignClient的请求路径
那么我们只要在这步之后实例化之前将InstanceSupplier持有的属性修改掉就可以实现动态修改FeignClient的请求path了
ImportBeanDefinitionRegistrar的处理发生在BeanFactoryPostProcessor的处理流程中那么我们可以自定义一个BeanFactoryPostProcessor来获取Feign处理后的BeanDefinition取其InstanceSupplier反射修改其属性
自定义一个BeanFactoryPostProcessor
public class FeignClientProcessor implements BeanFactoryPostProcessor, ResourceLoaderAware, EnvironmentAware {private String feignClientPackage;private ResourceLoader resourceLoader;private Environment environment;............实现ResourceLoaderAware和EnvironmentAware接口 是为了扫描得到加了FeignClient注解的类因为Feign注册的BeanDefinition的名字就是我们接口的全路径名所以可以扫描后到容器里根据类名取上面看到有Feign扫描的过程就直接copy过来用了
其中在EnvironmentAware的回调中设置了一个Feign的扫描路径因为此时还在Spring容器刷新的早期阶段通过Value注解是取不到配置的 Overridepublic void setEnvironment(Environment environment) {this.environment environment;this.feignClientPackage environment.getProperty(feign.client.package);}扫描的代码基本上就是Feign的源码略微修改扫描的路径是自定义的而不是从根路径扫因为我们自己的项目Feign接口是在指定位置的然后扫描到的BeanDefinition转化为类名这样就得到了所有FeignClient标注的类名列表 private ListString scanFeignClient() {ClassPathScanningCandidateComponentProvider scanner this.getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));SetBeanDefinition candidateComponents scanner.findCandidateComponents(feignClientPackage);return candidateComponents.stream().map(BeanDefinition::getBeanClassName).collect(Collectors.toList());}private ClassPathScanningCandidateComponentProvider getScanner() {return new ClassPathScanningCandidateComponentProvider(false, this.environment) {protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {boolean isCandidate false;if (beanDefinition.getMetadata().isIndependent() !beanDefinition.getMetadata().isAnnotation()) {isCandidate true;}return isCandidate;}};}然后针对每个类进行处理 先通过类实现的interface获取自定义注解的接口路径然后通过类名从容器中获取到Feign处理过的BeanDefinition取其InstanceSupplier反射找到存储FeignClient属性的map拼接请求前缀作为path添加到map中 ListString feignClientList scanFeignClient();feignClientList.forEach(item - {GenericBeanDefinition beanDefinition (GenericBeanDefinition)configurableListableBeanFactory.getBeanDefinition(item);Class? clazz beanDefinition.getBeanClass();Class? apiInterface Arrays.stream(clazz.getInterfaces()).filter(i - i.getName().startsWith(com.xxx) i.getName().endsWith(Api)).findAny().orElseThrow(() - new RuntimeException(基础路径未定义));XXXMapping annotation apiInterface.getAnnotation(XXXMapping.class);String interfacePath annotation.value();Supplier? instanceSupplier beanDefinition.getInstanceSupplier();try {Field[] declaredFields instanceSupplier.getClass().getDeclaredFields();for (Field field : declaredFields) {if (field.getType().isAssignableFrom(Map.class)) {field.setAccessible(true);MapString, String map (Map)field.get(instanceSupplier);String basePath map.get(value);map.put(path, basePath interfacePath);}}} catch (Exception e) {log.error(初始化FeignClient失败, e);}});完整代码
Component
Log4j2
public class FeignClientProcessor implements BeanFactoryPostProcessor, ResourceLoaderAware, EnvironmentAware {private String feignClientPackage;private ResourceLoader resourceLoader;private Environment environment;OverrideSuppressWarnings(unchecked)public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {ListString feignClientList scanFeignClient();feignClientList.forEach(item - {GenericBeanDefinition beanDefinition (GenericBeanDefinition)configurableListableBeanFactory.getBeanDefinition(item);Class? clazz beanDefinition.getBeanClass();Class? apiInterface Arrays.stream(clazz.getInterfaces()).filter(i - i.getName().startsWith(com.aic) i.getName().endsWith(Api)).findAny().orElseThrow(() - new RuntimeException(基础路径未定义));XXXMapping annotation apiInterface.getAnnotation(XXXMapping.class);String interfacePath annotation.value();Supplier? instanceSupplier beanDefinition.getInstanceSupplier();try {Field[] declaredFields instanceSupplier.getClass().getDeclaredFields();for (Field field : declaredFields) {Class? type field.getType();if (type.isAssignableFrom(Map.class)) {field.setAccessible(true);MapString, String map (Map)field.get(instanceSupplier);String basePath map.get(value);map.put(path, basePath interfacePath);}}} catch (Exception e) {log.error(初始化FeignClient失败, e);}});}private ListString scanFeignClient() {ClassPathScanningCandidateComponentProvider scanner this.getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));SetBeanDefinition candidateComponents scanner.findCandidateComponents(feignClientPackage);return candidateComponents.stream().map(BeanDefinition::getBeanClassName).collect(Collectors.toList());}private ClassPathScanningCandidateComponentProvider getScanner() {return new ClassPathScanningCandidateComponentProvider(false, this.environment) {protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {boolean isCandidate false;if (beanDefinition.getMetadata().isIndependent() !beanDefinition.getMetadata().isAnnotation()) {isCandidate true;}return isCandidate;}};}Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader resourceLoader;}Overridepublic void setEnvironment(Environment environment) {this.environment environment;this.feignClientPackage environment.getProperty(feign.client.package);}
}