利用FatJar彻底解决Jar包冲突(三)

系列文章目录

利用FatJar彻底解决Jar包冲突(一)
利用FatJar彻底解决Jar包冲突(二)
利用FatJar彻底解决Jar包冲突(三)


Spring 容器的加载与隔离

⽀持注解

这个⽐较容易,主要是我们之前的应⽤不⽀持⼆⽅包内部的注解,现在在隔离的情况下通过配置⽂件设置component-scan 来⽀持@Resource,@PostConstruct,@Component 注解。
在这里插入图片描述
在这里插入图片描述

配置⽂件定位与容器初始化

由于FatJarClassLoader为我们所有,因此使⽤ClassPathXmlApplicationContext通过classpath的⽅式定位配置⽂件最⽅便。 由于隔离,我们需要使⽤⼆⽅包中Spring的 来初始化容器,通过反射调⽤:
在这里插入图片描述
如果直接从构造函数中传⼊xml路径会提示⽂件找不到,通过debug和分析,唯⼀的原因就是在定位classpath的时候使⽤的 不是FatJarClassLoader,接下来⼀步步分析相关源码。
定位最初传⼊(创建)classloader的位置在DefaultResourceClassloader类:
在这里插入图片描述
getClassloader⽅法返回的是该类的属性,在构造函数中初始化:
在这里插入图片描述
初始化时调⽤ClassUtils的getDefaultClassLoader⽅法:
在这里插入图片描述
同时DefaultResourceLoader也提供了setClassloader⽅法设置属性:
在这里插入图片描述
⽽ClassPathXmlApplicationContext是继承⾃DefaultResourceLoader,⼤致加载类图如下:
在这里插入图片描述
现在就是要找可以hack的地⽅了,即在定位配置⽂件前设置classloader,如果直接使⽤带参的构造函数,调⽤的⽅法如下:
在这里插入图片描述
所以可以使⽤⽆参的构造函数创建实例之后设置classloader,设置配置⽂件位置,最后refresh:
在这里插入图片描述

嵌套Spring容器的加载

在迁移某个二方包业务时,发现它使⽤了地址库的jar,⽽地址库本身也使⽤了Spring容器,通过直接创建 ClassPathXmlApplicationContext带参(配置⽂件位置)构造函数初始化⾃身的Spring容器。这样我们就⽆法再修改地址库 配置⽂件加载流程了,下图是⽬前Spring容器结构:
在这里插入图片描述
根据上⼀节的分析,我们知道ClassUtils的getDefaultClassLoader⽅法返回的当前线程上下⽂的ClassLoader:
在这里插入图片描述
因此,这边我们只能通过设置当前线程的ContextClassLoader来确保容器中的容器能从ClassPath加载到配置⽂件并成功初始化容器。

隔离优化

由于完全隔离,⼆⽅包和应⽤⽆法共享⼀些通⽤的⼯具类⽐如中间件,这样对于应⽤和⼆⽅包都需要使⽤中间件且中间件 不⽀持同时创建多个实例(因为不同classloader加载的相同类实际是不同的两个类)。⽐如在迁移智能发货过程中发现应 ⽤和⼆⽅包都使⽤了Switch,⽽Switch是通过服务端推送的⽅式更新客户端,这就需要客户端开启端⼝监听,由于服务端 是向固定端⼝发送请求的所以⼆⽅包或应⽤会通知不到;再⽐如 HSFprovider也会开启端⼝监听,开启同⼀个端⼝的时候会出错。虽然⽬前是通过折中的⽅式解决的,⽐如利⽤Diamond代替Switch,或者在应⽤层不使⽤HSF provider,只由⼆⽅包 ⾃⼰提供HSF 接⼝。但是这种⽅式明显不是⻓久之际,最终⽅案应该是类共享与隔离共存。如何实现类共享?⼀开始想到 的⽅案是利⽤委托机制,在加载AppClassloader前先创建 SDKClassloader,由SDKClassLoader加载公共类,并创建 AppClassLoader和FatJarClassLoader,并把SDKClassLoader作为他俩的parent。同时容器层⾯将应⽤的Spring容器作为⼆⽅包容器的⽗容器,如图:
在这里插入图片描述
这种⽅式⼀个很⼤的缺点是需要在AppClassLoader创建之前就创建SDKClassLoader,这就需要修改系统启动时的默认 ClassLoader。
另⼀种⽅案是在⿊客⻢拉松时和队友讨论出来的,参考pandora插件隔离与共享的⽅式,使⽤⼀个Map缓存共享类,FatJarClassLoader优先从Map加载类。
在这里插入图片描述

EagleEye traceId不⼀致问题

原因

在迁移某个业务过程中,发现由于应⽤和⼆⽅包使⽤的是不同的classloader,即EagleEye也是不同的两个类。由于不同 classloader context不同,在通过反射调⽤服务的时候应⽤和⼆⽅包打印的eagle eye traceId不⼀致,需要在调⽤前设置 context并在调⽤完成之后恢复,类似加载Spring容器时设置的contextClassLoader。

解决

通过询问EagleEye相关的同学,发现EagelEye提供了设置context的⽅法,现在就是怎么设置的问题了。从之前的分析中我 们知道应⽤和⼆⽅包使⽤的是两个不同的ClassLoader即两个EagleEye,现在就需要设置⼆⽅包加载的EagleEye的context即 可,通过反射设置:
在这里插入图片描述
在调⽤⼆⽅包中服务逻辑⽅法前设置并在调⽤后恢复(为了不影响⼆⽅包⾃身内部的 traceId 获取)即可:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值