前面几节科普了阅读jarslink需要的一些背景知识,接下来我们来看一下它的源码。
包结构
其中,modulemanager和moduleservice仅是提供了管理module的接口,这里就不做解析了,重点看一下SpringModule、ModuleLoaderImpl以及ModuleClassLoader这三个类,我们从最基础的SpringModule说起。
SpringModule主要有以下几个功能:
- 提供模块基础信息
- 扫描定义好的Action
- 提供Action的执行方法
- 提供模块的卸载方法(上下文、类加载器)
private <T> Map<String, T> scanActions(ApplicationContext applicationContext, Class<T> type,
Function<T, String> keyFunction) {
Map<String, T> actions = Maps.newHashMap();
//find Action in module
for (T action : applicationContext.getBeansOfType(type).values()) {
String actionName = keyFunction.apply(action);
if (isBlank(actionName)) {
throw new ModuleRuntimeException("JarsLink scanActions actionName is null");
}
String key = actionName.toUpperCase(Locale.CHINESE);
checkState(!actions.containsKey(key), "Duplicated action %s found by: %s",
type.getSimpleName(), key);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JarsLink Scan action: {}: bean: {}", key, action);
}
actions.put(key, action);
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("JarsLink Scan actions finish: {}", ToStringBuilder.reflectionToString(actions));
}
return actions;
}
我们可以看到,jarslink通过已经加载好的上下文中的bean信息,匹配定义好的Action,至于上下文的加载,我们下面的文章会提到。
public <R, T> T doAction(String actionName, R actionRequest) {
checkNotNull(actionName, "actionName is null");
checkNotNull(actionRequest, "actionRequest is null");
return (T) doActionWithinModuleClassLoader(getAction(actionName), actionRequest);
}
protected <R, T> T doActionWithinModuleClassLoader(Action<R, T> action, R actionRequest) {
checkNotNull(action, "action is null");
checkNotNull(actionRequest, "actionRequest is null");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
ClassLoader moduleClassLoader = action.getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(moduleClassLoader);
return action.execute(actionRequest);
} catch (Exception e) {
LOGGER.error("调用模块出现异常,action=" + action, e);
throw new ModuleRuntimeException("doActionWithinModuleClassLoader has error,action=" + action, e);
} finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
上面的代码展示了执行Action的流程,值得注意的是执行时应该用Action的ClassLoader,基于这一点,我建议你在正式环境使用中可以根据此模式的写法,去实现基于自己Action的统一Gateway处理接口。
public static void clear(ClassLoader classLoader) {
checkNotNull(classLoader, "classLoader is null");
Introspector.flushCaches();
//从已经使用给定类加载器加载的缓存中移除所有资源包
ResourceBundle.clearCache(classLoader);
//Clear the introspection cache for the given ClassLoader
CachedIntrospectionResults.clearClassLoader(classLoader);
LogFactory.release(classLoader);
}
/**
* 关闭Spring上下文
* @param applicationContext
*/
private static void closeQuietly(ConfigurableApplicationContext applicationContext) {
checkNotNull(applicationContext, "applicationContext is null");
try {
applicationContext.close();
} catch (Exception e) {
LOGGER.error("Failed to close application context", e);
}
}
可以看到,在卸载这个模块的时候,jarslink做了两件事,卸载类加载器中的资源以及关闭上下文,模块的卸载需要以下几个条件:
模块里的实例对象没有被引用
模块里的Class没有被引用
类加载器没有被引用
基于上面的第三个条件,jarslink最新的版本中有一个bug(我自己认为的),在生产环境中应用后,卸载模块出现资源占用无法删除旧资源的问题,经过排查,发现是ClassLoader没有关闭造成的,所以我建议你使用的时候在clear方法中加上下面的代码:
try {
((ModuleClassLoader)classLoader).close();
} catch (IOException e) {
e.printStackTrace();
}