扩展包热加载
扩展包加载原理
- 解压扩展包。获取扩展包内在的信息,别名,优先级,依赖扩展包等等,解析extension.xml,web.xml的扩展包文件,获取extension.xml配置信息,例如是否启用jsp等等
- 调用init方法。初始化jsp,filter,servlet等资源,这些资源形成的包装对象,都保存在扩展包对象Extension对象的属性中。
- jsp:使用IJSPProcessor封装,会根据不同应用服务器生成JSPProcessor,访问扩展包jsp时候ExtensionFilter会调用扩展包的processJsp,然后会根据对应的JSPProcessor去处理指定的jsp,JSPProcessor会把扩展包的classloader作为当前线程的上下文classloader。
- servlet:包装为HttpServletWrapper,访问页面时候ExtensionFilter会遍历启用的扩展包,根据配置的信息查看是否需要使用扩展包的servlet处理。当需要访问扩展包servlet时候会把扩展包的classloader作为当前线程的上下文classloader,使得程序可以访问扩展包的类。
- filter:包装为FilterWrapper,和servlet处理逻辑类似。
注意:上述不管是filter、jsp、servlet,访问时都会经过ExtensionFilter,ExtensionFilter会遍历所有启用扩展包。
- 初始化扩展包静态文件的classloader和IResourceLocator。这一步会给DaoModule初始化一个classloader,为了寻找扩展包的静态文件资源和classloader(例如处理多语言文件时候,会在StringUtil使用这个Classloader获取静态文件)。注意这些静态文件是有缓存的,热部署扩展包需要清除缓存。前端文件则是通过IResourceLocator去获取,在前端请求一个文件时候,IResourceLocator会遍历扩展包,产品线目录,war包目录等文件目录,找到请求的js文件。
- 读取applicationContent.xml。直接让spring管理配置的bean对象,这里做了3件事。
- 注册全部bean到spring的ioc容器
- 所有配置在rmi中的bean对象,其module都会注入RMIModule的modules属性,配置在frameWork则会注入FrameWork的modules属性。这一步在SmartbiListableBeanFactory完成。
- 初始化全部扩展包的classloader,扩展包的classloader对象为ExtensionClassLoader(ExtensionClassLoader为URLClassLoader的子类)。如果配置了扩展包的depends,即依赖扩展包,会把当前扩展包的classLoader的parent修改为依赖扩展包的classloader。根据classloader的双亲委派原则,当前扩展包可以读取到依赖扩展包的类。如下图,上面为没有depends的情况,下面为有depends的情况。
- 激活module。这一步会调用module的activate方法,postActivate方法,afterStartup方法。这时候其他模块可能提供了接口,让扩展包在这3个方法传入对应接口的实现类。比如A模块提供了一个IA接口,和传入IA接口实现类的方法,并使用传入的实现类实现某些功能。扩展包B的A类实现了这个IA接口,并在activate方法传入了A类。这种情况我们在激活和禁用都是需要考虑的。
上述为加载扩展包的生命周期,那么有一个问题。
⚽
怎么调用扩展包的类?
在步骤2我们解释了jsp,filter,servlet是怎么调用的,那么扩展包的module还有其他类怎么访问呢?是否访问module会和jsp,filter,servlet也类似呢?
是的。我们都知道前端调用Util.remoteInvoke可以访问module,module中可以访问对应的类。Util.remoteInvoke其实是调用了RMIServlet,而RMIServlet实际是获取对应的module,再处理对应的功能逻辑,由于module的classloader是扩展包的classloader,那么自然可以拿到扩展包的类。
⚽
那RMIServlet获取对应module是怎么获取的呢?
其实是我们在smartbi激活module时候,RMIModule悄悄做了一些事情。
激活的时候,会把注册在rmi的module全部转为ClientService,ClientService持有一个module对象和module的全部public方法。当RMIServlet读取module时候其实是读取对应的ClientService。通过ClientService调用对应module的方法,而module的classloader是扩展包的classloader,那么自然能访问扩展包的类。
从上面步骤我们可以看出来,扩展包在smartbi的环境添加了几种东西
- spring容器配置的bean对象。
- 扩展包的类,其中又分为
- 实现并注入在其他模块提供的接口实现类
- hibernate实体类
- 配置在FrameWork等的module对象
- 配置在RMIModule的ClientService对象和module对象。
- servlet,jsp,filter等的包装对象。
这时可能会有疑问,扩展包前端的js,css,html是怎么合并的?
- 前端请求js时候,会通过一个IResourceLocator去获取请求的资源,这步在上文【初始化扩展包静态文件的classloader和IResourceLocator】提到,他会扩展包列表(不包括禁用的),然后再进行js,html,多语言等文件的合并,并放入缓存中。css则是在访问bof_merge.css.jsp的时候合并。
明白了加载扩展包的原理之后,我们就能得知热部署扩展包时候只需要处理后端部分类的逻辑。servlet,jsp,filter和前端资源,只要修改遍历扩展包的步骤,就等于在smartbi中去除了这部分资源。
热加载扩展包
启用
由上面可以明白加载扩展包需要干什么,启用时候其实是大同小异的。如下图
- 启用时候会解压扩展包文件,去查看是否有依赖的扩展包,有则先启用依赖扩展包,无则直接解压和init,和上述一致。
- 读取扩展包的applicationContent.xml,让spring管理配置的bean对象,和上述一致。
- 第一阶段激活,判断是否注册了hibernate实体类,是则重建hibernate,不是则继续第二阶段激活。
- 最后清除缓存,结束
- 禁用
禁用则是和启用反着思考,我们需要在smartbi服务中摘除我们所有的引用类,我们通过配置输入点的方式去摘除。这里首先要明白一个问题。
✍️
输入点指的是什么呢?
从前文得知我们会在smartbi添加几样东西
- spring容器配置的bean对象。
- 扩展包的类,其中又分为
- 实现并注入在其他模块的提供的接口实现类
- hibernate实体类
- 配置在FrameWork等的module对象
- 配置在RMIModule的ClientService对象和module对象。
- servlet,jsp,filter等的包装对象。
其中servlet,jsp,filter等资源都保存在对应扩展包对象的属性中,访问时候通过遍历启用扩展包去获取,我们在扩展包列表去除当前扩展包即可摘除引用。那么上面剩余就是我们的输入点。我们可以得知输入点的定义为。
smartbi提供给扩展包加载类和文件地方
对于输入点我们转为了几个对象,父类是EntryPoint,如下图
EntryPoint:输入点处理对象的抽象,提供disable方法,用于扩展包禁用时候摘除各个输入点引用。
FieldEntryPoint:用于摘除输入点中,其他模块的接口实现类引用。
HibernateEntryPoint:FieldEntryPoint子类,用于摘除输入点中的hibernate实体类对象引用。
ClientServiceEntryPoint:用于摘除输入点中,配置在RMIModule的ClientService对象和module对象引用。
FrameworkEntryPoint:用于摘除输入点中,配置在FrameWork等的module对象引用。
SpringEntryPoing:用于摘除输入点,中spring容器配置的bean对象引用。
这里还有一个隐含的输入点,就是扩展包的启用列表,在禁用过程中默认会从扩展包启用去除当前扩展包。
禁用流程如下图
- 和启用一样,先禁用依赖的扩展包
- 将输入点的引用全部摘除,通过判断当前类是不是属于当前禁用扩展包的classloader,是的话就去除。
- 在smartbi-config.xml添加禁用信息,为了下次启动时候不启用该禁用扩展包
- 清除缓存,禁用结束
重新加载
重新加载比较简单,先禁用再启用
上传扩展包
上传扩展包的流程为
上传了扩展包之后,会先生成临时文件存于临时目录,然后解压扩展包,判断是否需要升级
升级:提示用户是否跳转config界面备份升级,如果是的话则跳到升级界面,升级完则会加载扩展包,不是就结束。
不升级:复制临时文件到扩展包目录,加载扩展包。
在加载扩展包的过程中,如果是已加载的,则先禁用,然后删除以前临时文件,后解压启用,未加载的直接解压启用。