Overview
我对类隔离简单的理解:
在一个应用内,有类名为clazz
,模块A
使用了clazz
,模块B
也使用了clazz
。
模块A
不会用到模块B
ClassLoader
加载的clazz
,模块B
不会用到模块A
ClassLoader
加载的clazz
。
例如下图,App用到的中间件依赖了ClassA,App也依赖了ClassA。通过类隔离后,App不会使用用中间件依赖的ClassA,中间件不会使用App依赖的ClassA。
对于fat jar
和业务jar
类加载隔离,我们会基于ClassLoader
模型来做,但是不会打破这个模型规范。
方案
ClassLoader
在加载Java
类时,会先从下到上将class
交给父classloader
加载,如果父classloader
无法加载,再从上到下交给子classloader
加载。直到某个classloader
加载成功或抛出异常。
原始模型
下图是我们平时运行项目时,classloader
的上下级关系。应用本身的class
和三方包class
一般由AppClassLoader
加载。
中间件本身的类加载脱离AppClassLoader
首先我们退一步,期望中间件的类由单独的classloader
加载,中间件依赖的三方包还是由AppClassLoader
加载。
对上图的继承关系做一定的修改,添加新的中间件classloader
,MiddlewareClassLoader
能够加载中间件的jar
包(此时还是一个普通的jar
包)。
AppClassLoader
加载中间件class
时,先交由MiddlewareClassLoader
加载,MiddlewareClassLoader
也会交由父classloader
加载中间件class
,当然他的所有父classloader
都是加载不到中间件class
的。最后加载流程又回到了MiddlewareClassLoader
,它能够加载到中间件class
。MiddlewareClassLoader
将加载到的中间件class
返回给AppClassLoader
。
通过这种方式,中间件本身的类已经由MiddlewareClassLoader
加载,和业务AppClassLoader
加载隔离了,但是中间件依赖的三方包加载还是通过AppClassLoader
完成。
中间件三方依赖和本身类加载脱离AppClassLoader
构建新的classloader
继承模型
我们之前将中间件的类和业务的类的classloader
做了隔离。根据上面描述的classloader
继承关系,做进一步扩展。
下图是抽象出来的新的模型关系。FatJarDelegateClassLoader
代替了原来的MiddlewareClassLoader
,FatJarDelegateClassLoader
管理者多个FatJarClassLoader
。FatJarClassLoader
对应一个中间件模块。
现在中间件class
类加载分为如下步骤:
AppClassLoader
开始加载class
,AppClassLoader
委托给父classloader
"FatJarDelegateClassLoader"
加载。FatJarDelegateClassLoader
本身不会加载类,而是委派给它管理的FatJarClassLoader
。- 在
FatJarDelegateClassLoader
内部,还会判断委托加载的类是否为中间件类,如果不是则不会交由管理的FatJarClassLoader
加载。否则交给FatJarClassloader
加载。
到这里我们做的工作和上图的MiddlewareClassLoader
类似,还是没有完全解决三方依赖的问题。
修改中间件打包方式
为了解决三方依赖加载隔离,将中间件打包成为fat jar
,新的jar
包内包含所有的中间件三方依赖。结合上图,每个中间件fat jar
由FatJarClassloader
加载,由于中间件通过FatJarClassloader
加载,且它的父classloader
为null
,那么中间件使用到的三方依赖的类,也会由FatJarClassLoader
加载,不会用到业务的类。
在FatJarDelegantClassLoader
里会判断当前加载的类是否属于中间件,如果不是则不会交由FatJarClassLoader
加载,所以应用使用的三方依赖不会用到中间件依赖三方包,做到了中间件依赖和应用依赖隔离。
总结
经过上面几次模型演进,我们可以做到中间件三方包和业务三方包加载隔离,中间件和中间件之间加载隔离。