开源人方腾飞(蚂蚁金服-技术专家)
JarsLink (原名Titan) 是一个基于JAVA的模块化开发框架,它提供在运行时动态加载模块(一个JAR包)、卸载模块和模块间调用的API。目前在微贷事业群广泛使用。
JarsLink的方便之处在于可动态加载jar、还能卸载jar, 同样是Java语言,Android似乎不能动态卸载jar。
动态加载的核心原理是使用了类隔离,做到类隔离是实例化了一个上下文和classLoader
/*** 根据本地临时文件Jar,初始化模块自己的ClassLoader,初始化Spring Application Context,同时要设置当前线程上下文的ClassLoader问模块的ClassLoader
*
*@parammoduleConfig
*@paramtempFileJarURLs
*@return
*/
private ClassPathXmlApplicationContext loadModuleApplication(ModuleConfig moduleConfig, ListtempFileJarURLs) {
ClassLoader currentClassLoader=Thread.currentThread().getContextClassLoader();//获取模块的ClassLoader
ClassLoader moduleClassLoader = newModuleClassLoader(moduleConfig.getModuleUrl(), applicationContext.getClassLoader(),
getOverridePackages(moduleConfig));try{//把当前线程的ClassLoader切换成模块的
Thread.currentThread().setContextClassLoader(moduleClassLoader);
ModuleApplicationContext moduleApplicationContext= newModuleApplicationContext(applicationContext);
Properties properties=getProperties(moduleConfig);
moduleApplicationContext.setProperties(properties);
moduleApplicationContext.setClassLoader(moduleClassLoader);
moduleApplicationContext.setConfigLocations(findSpringConfigs(tempFileJarURLs, moduleClassLoader,
getExclusionConfigeNameList(properties)));
moduleApplicationContext.refresh();returnmoduleApplicationContext;
}catch(Throwable e) {
CachedIntrospectionResults.clearClassLoader(moduleClassLoader);throwThrowables.propagate(e);
}finally{//还原当前线程的ClassLoader
Thread.currentThread().setContextClassLoader(currentClassLoader);
}
}
View Code
ClassLoader默认逻辑是双亲委托, 即同一个类只能被加载一次, JarsLink覆盖了这个逻辑从而能多次加载同一个类;
/*** 覆盖双亲委派机制
*
*@seeClassLoader#loadClass(String, boolean)*/@Overrideprotected Class> loadClass(String name, boolean resolve) throwsClassNotFoundException {
Class> result = null;synchronized (ModuleClassLoader.class) {if(isEligibleForOverriding(name)) {if(LOGGER.isInfoEnabled()) {
LOGGER.info("Load class for overriding: {}", name);
}
result=loadClassForOverriding(name);
}if (result != null) {//链接类
if(resolve) {
resolveClass(result);
}returnresult;
}
}//使用默认类加载方式
return super.loadClass(name, resolve);
}
View Code
JarsLink可通过设置可自动扫描Jar的版本,如果发现新版本后可卸载老版本Jar,装载新版本Jar。可通过设置线程池的定时器自动扫描,默认是60秒扫一次。
/*** 模块刷新默认间隔,单位秒*/
private static final int DEFAULT_REFRESH_DELAY = 60;/*** 初始化ScheduledExecutor,启动定时任务,扫描数据库的ModuleConfig,并根据逻辑判断启动和卸载模块
*
*@seeInitializingBean#afterPropertiesSet()*/@Overridepublic void afterPropertiesSet() throwsException {//先刷新一次
refreshModuleConfigs();
scheduledExecutor= new ScheduledThreadPoolExecutor(1,new BasicThreadFactory.Builder().namingPattern("module_refresh-schedule-pool-%d").daemon(true).build());
scheduledExecutor
.scheduleWithFixedDelay(this, initialDelay, refreshDelay, TimeUnit.SECONDS);if(LOGGER.isInfoEnabled()) {
LOGGER.info("AbstractModuleRefreshScheduler start");
}
}
View Code
类加载机制
OSGI(java动态模块化系统的一系列规范,由OSGI联盟制定)入门学习资源类加载机制的关系采用的是网状结构,每个模块通过 Export-Package 来声明我要给别人用哪些类,通过 Import-Package来声明我要用别人的哪些类。而TITAN框架采用的是扁平化管理,每个模块都有一个共同的父类,这个父类加载器就是加载ModuleLoader类的加载器,好处是便于维护,每个模块的类做到充分隔离,缺点是会加载重复的Class,适用于模块较少的场景。
框架类图
主要接口
AbstractModuleRefreshScheduler:入口类,负责定期扫描本地和内存中的模块是否发生变更,如果变更,则更新模块。
ModuleLoader:模块加载接口(引擎),负责模块的加载。
ModuleManager:模块管理接口(引擎),负责模块管理,在运行时负责注册、卸载及查找模块,并执行Action
Module:模块,一个模块有多个Action。
Action:模块里的执行者。
使用步骤-maven框架下:
1.引入JarsLink的POM
com.alipay.jarslink
jarslink-api
1.5.0.20180213
引入依赖的包
1.7.7
2.6
3.2.1
org.springframework
spring-aop
${org.springframework.version}
org.springframework
spring-web
${org.springframework.version}
true
commons-logging
commons-logging
1.1.3
org.apache.commons
commons-lang3
3.3.2
commons-lang
commons-lang
${apache.commons.lang.version}
org.slf4j
slf4j-api
${slf4j.version}
commons-collections
commons-collections
${apache.commons.collections.version}
com.google.guava
guava
17.0
junit
junit
4.12
2.Spring配置中加载两个主要
3.继承AbstractModuleRefreshScheduler,并提供模块的配置信息
public class ModuleRefreshSchedulerImpl extendsAbstractModuleRefreshScheduler {
@Overridepublic ListqueryModuleConfigs() {returnImmutableList.of(ModuleManagerTest.buildModuleConfig());
}public staticModuleConfig buildModuleConfig() {
URL demoModule= Thread.currentThread().getContextClassLoader().getResource("META-INF/spring/demo-1.0.0.jar");
ModuleConfig moduleConfig= newModuleConfig();
moduleConfig.setName("demo");
moduleConfig.setEnabled(true);
moduleConfig.setVersion("1.0.0.20170621");
moduleConfig.setProperties(ImmutableMap.of("svnPath", newObject()));
moduleConfig.setModuleUrl(ImmutableList.of(demoModule));returnmoduleConfig;
}