基于Spring的Jar包动态加载方案

3,799 阅读2分钟

背景

最近由于业务需求,后端需要满足在程序运行期间动态加载或修改一些Class的行为,这些Class可能有以下特点:

  • 一个普通的类
  • 一个Spring的bean(依赖注入功能)
  • 依赖于其他的动态Class
  • 可以动态更替

我们最后实现了一个infra级别的动态加载服务,通过这个服务你可以自定义动态资源,并通过服务提供的工程化的方式管理这些资源,当然,这些资源最终都会以Jar包的形式被加载管理。这篇文章是对服务设计的一个分享

服务实现

下图大概描述了从请求一个资源到资源实例返回这整个流程

dynamic-loading-service
下面将逐步分析一下每个流程所做的事情:

  1. 应用A向动态加载服务请求类型为UserService的动态资源(dynamic resource)
  2. 服务根据应用A传来的Id从数据库读取数据,dynamic resource在数据库中存储结构参考图左上角json描述。之后通过对config字段内容的hash生成一个版本号version,这个version有两个目的:一是实现版本更替功能,一旦配置改变,reload此资源。二是用来做Spring BeanName
  3. 每次load一个资源,我们会有一个ResourceDescriptor类对象来描述这个资源,并且这个对象会被缓存。在每次请求中会先查询缓存,若缓存命中走第4步,若缓存未命中直接跳到第6步
  4. 缓存命中,检查version是否相同,若相同直接走第7步返回,若不相同说明此资源在上次加载之后配置有变,需要reload,走第5步
  5. 在reload之前我们需要把旧的已过期的资源摧毁掉,这个摧毁主要有三项:
    • 在Spring容器中通过beanName删除过期的BeanDefinition,beanName即为version
    • 通过缓存中的resourceDescriptor.getClassLoader().close()关闭过期的URLClassLoader
    • 通过dynamicResourceId移除ResourceDescriptor缓存
  6. 加载新资源的过程有下面四步:
    • 用资源的jarUrl与dependencyJarUrls组成的urls创建新的URLClassLoader
    • 用上步创建的URLClassLoader把配置中指定的class字节码加载到内存
    • 向Spring BeanFactory中注册该class的BeanDefinition
    • 创建资源描述符ResourceDescriptor,存入缓存
  7. 调用Spring applicationContext.getBean(version);
  8. 返回实例到服务A

这样就完成了一个基本的动态加载服务。

注意,这套流程并没有解决两个dynamic resource有依赖的场景