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加载,所以应用使用的三方依赖不会用到中间件依赖三方包,做到了中间件依赖和应用依赖隔离。
总结
经过上面几次模型演进,我们可以做到中间件三方包和业务三方包加载隔离,中间件和中间件之间加载隔离。