通过BeanFactotyPostProcessor动态修改@FeignClient的path

最近项目有个需求,要在启动后,动态修改@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基本可以确定是这行代码在处理了,点进去

这个方法比较长,这里就只贴些关键代码

	LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet();Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());............ClassPathScanningCandidateComponentProvider scanner = this.getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));Set<String> 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");Map<String, 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,然后遍历,其中有一行代码

Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());

这里就获取了@FeignClient里定义的各种属性,比如value 、path 、contextId等等
然后调用registerFeignClient方法完成注册,进入这个方法

    private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, 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 List<String> scanFeignClient() {ClassPathScanningCandidateComponentProvider scanner = this.getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));Set<BeanDefinition> 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中

	List<String> 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);Map<String, 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;@Override@SuppressWarnings("unchecked")public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {List<String> 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);Map<String, String> map = (Map)field.get(instanceSupplier);String basePath = map.get("value");map.put("path", basePath + interfacePath);}}} catch (Exception e) {log.error("初始化FeignClient失败:", e);}});}private List<String> scanFeignClient() {ClassPathScanningCandidateComponentProvider scanner = this.getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));Set<BeanDefinition> 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");}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/94948.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring 体系架构模块和三大核心组件介绍

Spring架构图 模块介绍 1. Spring Core&#xff08;核心容器&#xff09;&#xff1a;提供了IOC,DI,Bean配置装载创建的核心实现。 spring-core &#xff1a;IOC和DI的基本实现 spring-beans&#xff1a;BeanFactory和Bean的装配管理(BeanFactory) spring-context&#xff1…

【计算机网络】HTTPS协议详解

文章目录 一、HTTPS协议 介绍 1、1 HTTP协议不安全的体现 1、2 什么是 HTTPS协议 二、加密的一些概念 2、1 怎么理解加密 2、2 为什么要加密 2、3 常见的加密方式 2、2、1 对称加密 2、2、2 非对称加密 三、HTTPS协议探究加密过程 3、1 只使用对称加密 3、2 只是用非对称加密 3…

【应用层协议】HTTPS的加密流程

文章目录 1. 认识HTTPS2. 密文3. HTTPS加密流程3.1 对称加密3.2 非对称加密3.3 证书 1. 认识HTTPS HTTPS&#xff08;超文本传输协议安全&#xff09;也是一个应用层协议&#xff0c;它是在HTTP协议的基础上引入了一个加密层。 也就是HTTP协议传输文本的方式是明文&#xff0c;…

flink选择slot

flink选择slot 在这个类里修改 package org.apache.flink.runtime.resourcemanager.slotmanager.SlotManagerImpl; findMatchingSlot(resourceProfile)&#xff1a;找到满足要求的slot&#xff08;负责从哪个taskmanager中获取slot&#xff09;对应上图第8&#xff0c;9&…

国庆中秋特辑(八)Spring Boot项目如何使用JPA

目录 一、Spring Boot 项目使用 JPA 的步骤二、Spring Boot 项目使用 JPA 注意事项三、Spring Boot 项目使用 JPA 常用语法 Spring Boot项目如何使用JPA&#xff0c;具体如下 一、Spring Boot 项目使用 JPA 的步骤 添加依赖 在项目的 pom.xml 文件中添加 Spring Boot JPA 和数…

基于SpringBoot的网上超市系统

基于SpringBoot的网上超市系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 角色&#xff1a;用户、管理员 管理员&#xff1a;个人中心、用户管理、商品分类…

Electron.js入门-构建第一个聊天应用程序

什么是electron 电子是一个开源框架&#xff0c;用于使用web技术构建跨平台桌面应用程序&#xff1b;即&#xff1a; HTML、CSS和JavaScript&#xff1b;被集成为节点模块&#xff0c;我们可以为我们的应用程序使用节点的所有功能&#xff1b;组件&#xff0c;如数据库、Api休…

【HUAWEI】VLAN+OSPF+单臂路由

目录 &#x1f96e;写在前面 &#x1f96e;3.1、拓扑图 &#x1f96e;3.2、操作思路 &#x1f96e;3.3、配置操作 &#x1f363;3.3.1、LSW2配置 &#x1f363;3.3.2、LSW3配置 &#x1f363;3.3.3、R1配置 &#x1f363;3.3.4、R2配置 &#x1f363;3.3.5、LSW1配置 &#x1f…

力扣 -- 518. 零钱兑换 II(完全背包问题)

解题步骤&#xff1a; 参考代码&#xff1a; 未优化代码&#xff1a; class Solution { public:int change(int amount, vector<int>& coins) {int ncoins.size();//多开一行&#xff0c;多开一列vector<vector<int>> dp(n1,vector<int>(amount1…

Python3数据科学包系列(三):数据分析实战

Python3中类的高级语法及实战 Python3(基础|高级)语法实战(|多线程|多进程|线程池|进程池技术)|多线程安全问题解决方案 Python3数据科学包系列(一):数据分析实战 Python3数据科学包系列(二):数据分析实战 Python3数据科学包系列(三):数据分析实战 一: 数据分析与挖掘认知…

Linux apt-get update - Could not connect to XXX(Connection refused)

Linux: apt-get update ----Err:Could not connect to XXX(Connection refused) - 知乎 先换源&#xff08;vi不好使用&#xff0c;可以换成gedit&#xff09; 若还是不行&#xff0c;可以再尝试执行&#xff1a; unset http_proxy unset https_proxy

图像分割中的色块的提取

一 色块提取方法&#xff1a; ①首先是色彩模型的转换 由RGB颜色空间转到HSV颜色空间 原因&#xff1a;RGB颜色空间适合显示系统&#xff0c;但是各分量间相关性很强&#xff0c;比如当图像亮度发生变化时&#xff0c;RGB三个分量都会发生相应改变 但是HSV颜色空间更能感知颜色…

ubuntu22.04 x11窗口环境手势控制

ubuntu22.04 x11窗口环境手势控制 ubuntu x11窗口环境的手势控制并不优秀&#xff0c;我们可以使用touchegg去代替 这个配置过程非常简单&#xff0c;并且可以很容易在一定范围内达到你想到的效果&#xff0c;类比mac的手势控制 关于安装 首先添加源&#xff0c;并安装 sud…

【NeurIPS 2023】Backdoor对抗攻防论文汇总

NeurIPS 对抗攻防论文 NeurIPS2022|对抗攻防论文整理 - 知乎 NeurIPS 2023 Papers BIRD: Generalizable Backdoor Detection and Removal for Deep Reinforcement Learning https://neurips.cc/virtual/2023/poster/70618 摘要&#xff1a; 后门攻击对深度强化学习&…

基于Java的飞机航班订票购票管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

Leetcode219. 存在重复元素 II

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 给你一个整数数组 nums 和一个整数 k &#xff0c;判断数组中是否存在两个 不同的索引 i 和 j &#xff0c;满足 nums[i] nums[j] 且 abs(i - j) < k 。如果存在&#xff0c;返回 true &#xff1b…

深入了解 RabbitMQ:高性能消息中间件

目录 引言&#xff1a;一、RabbitMQ 介绍二、核心概念三、工作原理四、应用场景五、案例实战 引言&#xff1a; 在现代分布式系统中&#xff0c;消息队列成为了实现系统间异步通信、削峰填谷以及解耦组件的重要工具。而RabbitMQ作为一个高效可靠的消息队列解决方案&#xff0c;…

Python爬虫解决中文乱码

目录 一、中文乱码 二、chardet.detect()解决 三、在页面查找编码格式解决 一、中文乱码 问题在于文本的编码格式不正确 import requestsurlhttps://www.shicimingju.com/book/sanguoyanyi.html headers{User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKi…

C++_pen_静态与常量

成员 常成员、常对象&#xff08;C推荐使用 const 而不用#define,mutable&#xff09; const 数据成员只在某个对象生存周期内是常量&#xff0c;而对于整个类而言却是可变的&#xff08;static除外&#xff09; 1.常数据成员&#xff08;构造函数初始化表赋值&#xff09; c…

C#开发的OpenRA游戏之电力系统之一

C#开发的OpenRA游戏之电力系统之一 前面已经分析过雷达地图显示,在雷达地图的下面有一个显示电力数量显示,如下图: 从上图看到电力的数量为0,所以这时候就处于低电力的模式。 要在这里显示电力,那么就需要在界面上做一点编辑,是在yaml文件定义它: Container@POWERBAR…