SOFAArk启动源码分析
一、背景
首先来个SOFAArk官方介绍:SOFAArk 是一款基于 Java 实现的轻量级类隔离容器,由蚂蚁金服公司开源贡献;主要提供类隔离和应用(模块)动态部署能力;基于 Fat Jar 技术,可以将多个应用(模块)打包成一个自包含可运行的 Fat Jar,应用既可以是简单的单模块 Java 应用也可以是 Spring Boot/SOFABoot 应用;访问网址进入快速开始并获取更多详细信息;
所以SOFAArk其实是提供一个类隔离能力,那么具体可以解决什么问题?举个栗子,我们知道springboot引用的jar包都放在同一个pom.xml文件中。当工程比较小的时候,一切ok,但如果工程是由多个业务方和团队协作开发时,就会带来不少风险和工作量。比如工程中有两个独立的功能FunA和FunB都依赖了C这个jar包,现在FunA需要升级,那么就需要排查FunB是否兼容,不兼容的代码改动就带来了额外的风险,拔出萝卜带出泥,甚至涉及long long ago的祖传代码。。。
imageSOFAArk不仅仅可以解决这么一个问题,还有包冲突、动态装载卸载模块、合并部署等。但总体来说核心就是类隔离。
二、术语
SOFAArk框架中最重要的三个术语Ark Container, Ark Plugin 和 Ark Biz,看下官方的图示。简单来说SOFAArk框架运行时分为三层:
底层是容器,干啥的呢?就是启动并加载所有plugin和biz模块,然后将他们都部署起来,它会执行一个pipeline按顺序先部署plugin,然后部署biz模块。
中间层是插件,一般是可以共享的中间件和通用服务,例如常见的消息中间件、rpc等,如果你需要提供一个给各个上层业务方使用的通用能力,可以定义在这一层。
最上层就是biz模块层,就是具体的业务代码了。
另外提一点,biz层可以使用plugin的所有导出类,但plugin不能依赖biz层的类。其实原因也很好理解,因为biz的模块是支持动态加载和卸载的,如果plugin对biz有依赖,那么biz一次动态卸载和加载,依赖的类就混乱了。
image三、类加载器模型
首先SOFAArk容器通过监听springboot启动事件来启动自己,启动类SofaArkBootstrap是由AppClassLoader加载的,然后会创建一个新的ContainerClassLoader来加载容器相关的类。容器启动过程中会为每一个plugin和biz模块都创建一个对应的PluginClassLoader和BizClassLoader,注意是每一个plugin都有一个独立的PluginClassLoader加载,biz模块也一样。这样就做到了类隔离。
因此SOFAArk启动后,会有一个ContainerClassLoader,若干个PluginClassLoader和BizClassLoader。
image四、启动分析
4.1 源码分析
4.1.1 通过监听springboot的启动事件,来启动ark容器
public void onApplicationEvent(SpringApplicationEvent event) {
。。。
startUpArk(event);
。。。
}
public void startUpArk(SpringApplicationEvent event) {
if (LAUNCH_CLASSLOADER_NAME.equals(this.getClass().getClassLoader().getClass().getName())) {
// zy 1 启动sofaark
SofaArkBootstrap.launch(event.getArgs());
}
}
4.1.2 SofaArkBootstrap的launch方法通过一个单线程启动容器
public static void launch(String[]args) {
try {
if (!isSofaArkStarted()) {
entryMethod =new EntryMethod(Thread.currentThread());
IsolatedThreadGroup threadGroup =new IsolatedThreadGroup(
entryMethod.getDeclaringClassName());
// zy 2 启动的runner方法, MAIN_ENTRY_NAME = "remain"
LaunchRunner launchRunner =new LaunchRunner(SofaArkBootstrap.class.getName(),
MAIN_ENTRY_NAME,args);
Thread launchThread =new Thread(threadGroup, launchRunner, entryMethod.getMethodName());
// zy 3 单线程启动sofaark
launchThread.start();
LaunchRunner.join(threadGroup);
threadGroup.rethrowUncaughtException();
System.exit(0);
}
}catch (Throwable e) {
throw new RuntimeException(e);
}
}
4.1.3 线程里面调用了这个LaunchRunner.run方法,当前这个类LaunchRunner由AppClassLoader加载生成。代码中注释可以看到最终又去执行了SofaArkBootstrap.remain方法
public void run() {
。。。
// zy 4 sun.misc.Launcher$AppClassLoader
ClassLoader classLoader = thread.getContextClassLoader();
try {
Class startClass = classLoader.loadClass(this.startClassName);
Method entryMethod;
try {
entryMethod = startClass.getMethod(startMethodName,String[].class);
}catch (NoSuchMethodException ex) {
entryMethod = startClass.getDeclaredMethod(startMethodName,String[].class);
}
if (!entryMethod.isAccessible()) {
entryMethod.setAccessible(true);
}
// zy 5 执行SofaArkBootstrap.remain方法
entryMethod.invoke(null,new Object[] {this.args });
。。。
}
4.1.4 回到SofaArkBootstrap类的remain方法,在第7步执行了ClasspathLauncher.launch方法
private static void remain(String[] args) throws Exception {// NOPMD
AssertUtils.assertNotNull(entryMethod, "No Entry Method Found.");
// zy 6 系统环境变量目录
URL[] urls = getURLClassPath();
// zy 7 lunch启动sofaark
new ClasspathLauncher(new ClassPathArchive(entryMethod.getDeclaringClassName(),
entryMethod.getMethodName(), urls)).launch(args, getClasspath(urls),
entryMethod.getMethod());
}
4.1.5 下面代码再AbstractLauncher.launch方法的第8步,创建了一个ContainerClassLoader类加载器,后面会用来加载SOFAArk相关的类
// zy 参数method是SofaArkBootstrap.remain方法
public Object launch(String[] args, String classpath, Method method) throws Exception {
。。。
// zy 8 ContainerClassLoader的父cls是null
ClassLoader classLoader = createContainerClassLoader(getContainerArchive());
。。。
return launch(attachArgs.toArray(new String[attachArgs.size()]), getMainClass(),
classLoader);
}
上面代码调用了下面的第二个launch方法,最终createMainMethodRunner方法返回的是MainMethodRunner这个工具类的实例,MainMethodRunner只是一个执行器,它实际是为了执行ArkContainer类的main方法。
/** zy
* mainClass是com.alipay.sofa.ark.container.ArkContainer
* classLoader是ContainerClassLoader
*/
protected Object launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
// zy 9 old是sun.misc.Launcher$AppClassLoader
ClassLoader old = Thread.currentThread().getContextClassLoader();
try {
// zy 10 classLoader是ContainerClassLoader, 其父cls是null
Thread.currentThread().setContextClassLoader(classLoader);
return createMainMethodRunner(mainClass, args).run();
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args) {
return new MainMethodRunner(mainClass, args);
}
我们看下MainMethodRunner类的代码,类里面我已经分场景注释了,也就是后面的代码通过这个工具类最终调用了ArkContainer.main()
/** zy com.alipay.sofa.ark.container.ArkContainer */
private final String mainClassName;
private final String[] args;
public MainMethodRunner(String mainClass, String[] args) {
this.mainClassName = mainClass;
this.args = (args == null ? null : args.clone());
}
public Object run() throws Exception {
/** zy
* arkContainer启动时, mainClass是ContainerClassLoader
* biz module启动时, mainClass是BizClassLoader
*/
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
/** zy
* arkContainer启动时, 执行com.alipay.sofa.ark.container.ArkContainer.main
* biz module启动时, 执行模块里面的main方法
*/
return mainMethod.invoke(null, new Object[] { this.args });
}
4.1.6 回到AbstractLauncher的第二个launch方法,可以看到,在第11步调用了ArkContainer.main方法
/** zy
* mainClass是com.alipay.sofa.ark.container.ArkContainer
* classLoader是ContainerClassLoader
*/
protected Object launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
// zy 9 old是sun.misc.Launcher$AppClassLoader
ClassLoader old = Thread.currentThread().getContextClassLoader();
try {
// zy 10 classLoader是ContainerClassLoader, 其父cls是null
Thread.currentThread().setContextClassLoader(classLoader);
// zy 11 执行ArkContainer.main
return createMainMethodRunner(mainClass, args).run();
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
4.1.7 进入ArkContainer.main方法,在第13步执行启动容器代码
/** zy 当前类属于ContainerClassLoader, 其父cls是null */
public static Object main(String[] args) throws ArkRuntimeException {
if (args.length < MINIMUM_ARGS_SIZE) {
throw new ArkRuntimeException("Please provide suitable arguments to continue !");
}
。。。
// zy 12 解析bizJar,className,methodName,classpath,profile等参数
LaunchCommand launchCommand = LaunchCommand.parse(args);
if (launchCommand.isExecutedByCommandLine()) {
。。。
// zy 13 启动容器; 当前属于ContainerClassLoader, 其父cls是null
return new ArkContainer(executableArchive, launchCommand).start();
}
。。。
}
4.1.8 进入ArkContainer.start方法,依次添加了jvm钩子、初始化配置、初始化log配置、启动ArkServiceContainer以及执行Pipeline。接下来依次详解ArkServiceContainer启动流程和Pipeline执行流程
public Object start() throws ArkRuntimeException {
。。。
if (started.compareAndSet(false, true)) {
// zy 14 JVM关闭时, 优雅的关闭arkContainer
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
stop();
}
}));
// zy 15 读取bootstrap.properties以及bootstrap-%s.properties中的属性, 并放入到ArkConfigs.CFG
prepareArkConfig();
// zy 16 初始化log相关配置
reInitializeArkLogger();
// zy 17 启动Ark Service Container
arkServiceContainer.start();
// zy 18 StandardPipeline
Pipeline pipeline = arkServiceContainer.getService(Pipeline.class);
// zy 19 执行pipeline
pipeline.process(pipelineContext);
。。。
}
4.1.9 首先进入ArkServiceContainer.start方法,在17.2步注册了pipeline相关的服务,哪些服务后面会说到,另外这里使用的是Guice框架。在17.3步,注册了biz模块管理,注入,异步事件服务。
public void start() throws ArkRuntimeException {
if (started.compareAndSet(false, true)) {
。。。
// zy 17.2 这里Modules值是 com.alipay.sofa.ark.container.guice.ContainerModule
injector = Guice.createInjector(findServiceModules());
。。。
/** zy 17.3 其中arkServiceList包含如下服务:
* @see PluginDeployServiceImpl 空执行
* @see BizDeployServiceImpl 空执行
* @see ClassLoaderServiceImpl 生成arkClassLoader,systemClassLoader等
* @see StandardTelnetServerImpl 开启telnet服务
*/
for (ArkService arkService : arkServiceList) {
arkService.init();
}
// zy 17.4 注册biz模块管理,注入,异步事件服务
ArkServiceContainerHolder.setContainer(this);
ArkClient.setBizFactoryService(getService(BizFactoryService.class));
ArkClient.setBizManagerService(getService(BizManagerService.class));
ArkClient.setInjectionService(getService(InjectionService.class));
ArkClient.setEventAdminService(getService(EventAdminService.class));
ArkClient.setArguments(arguments);
。。。
}
}
4.1.10 上面提到的findServiceModules代码最终注入的是下面这个模型,下面可以清楚的看到添加了插件、biz模块、扩展点、事件等服务。这些会在pipeline执行以及容器运行期会用到。
public class ContainerModule extends AbstractArkGuiceModule {
@Override
protected void configure() {
binder().bind(Pipeline.class).to(StandardPipeline.class);
Multibinder<ArkService> arkServiceMultibinder = Multibinder.newSetBinder(binder(),
ArkService.class);
arkServiceMultibinder.addBinding().to(PluginDeployServiceImpl.class);
arkServiceMultibinder.addBinding().to(BizDeployServiceImpl.class);
arkServiceMultibinder.addBinding().to(ClassLoaderServiceImpl.class);
arkServiceMultibinder.addBinding().to(StandardTelnetServerImpl.class);
binder().bind(PluginManagerService.class).to(PluginManagerServiceImpl.class);
binder().bind(BizManagerService.class).to(BizManagerServiceImpl.class);
binder().bind(ClassLoaderService.class).to(ClassLoaderServiceImpl.class);
binder().bind(PluginDeployService.class).to(PluginDeployServiceImpl.class);
binder().bind(BizDeployService.class).to(BizDeployServiceImpl.class);
binder().bind(RegistryService.class).to(RegistryServiceImpl.class);
binder().bind(InjectionService.class).to(InjectionServiceImpl.class);
binder().bind(TelnetServerService.class).to(StandardTelnetServerImpl.class);
binder().bind(BizFactoryService.class).to(BizFactoryServiceImpl.class);
binder().bind(PluginFactoryService.class).to(PluginFactoryServiceImpl.class);
binder().bind(ExtensionLoaderService.class).to(ExtensionLoaderServiceImpl.class);
binder().bind(EventAdminService.class).to(EventAdminServiceImpl.class);
}
}
4.1.11 接下来我们进入pipeline的代码StandardPipeline.process,执行代码很简单,注释上也说明了执行了哪些Stage以及他们的功能。
public void process(PipelineContext pipelineContext) throws ArkRuntimeException {
for (PipelineStage pipelineStage : stages) {
。。。
/**
* zy
*
* @see HandleArchiveStage 注册plugin和biz模块, 仅注册不部署
* @see RegisterServiceStage plugin,模块和事件这些内部服务不可被覆盖, 因此先发布
* @see ExtensionLoaderStage 扩展服务
* @see DeployPluginStage 部署插件
* @see DeployBizStage 部署biz模块
* @see FinishStartupStage 结束, 里面主要发送一个结束事件
*
*/
pipelineStage.process(pipelineContext);
。。。
}
}
4.1.12 我们分析下DeployPluginStage的代码,第一步是解析导入导出相关的类、加载器等资源,第二步是部署插件。
public void process(PipelineContext pipelineContext) throws ArkRuntimeException {
// zy 解析并缓存, 插件中导入导出的类,类加载和资源等
classloaderService.prepareExportClassAndResourceCache();
// zy 部署插件
pluginDeployService.deploy();
}
4.1.13 首先我们看下,第一步解析相关的代码,可以看到整个代码逻辑是比较清晰的,就是根据不同的类型进行解析。
public void prepareExportClassAndResourceCache() {
for (Plugin plugin : pluginManagerService.getPluginsInOrder()) {
for (String exportIndex : plugin.getExportPackageNodes()) {
exportNodeAndClassLoaderMap.putIfAbsent(exportIndex, plugin.getPluginClassLoader());
}
for (String exportIndex : plugin.getExportPackageStems()) {
exportStemAndClassLoaderMap.putIfAbsent(exportIndex, plugin.getPluginClassLoader());
}
for (String exportIndex : plugin.getExportClasses()) {
exportClassAndClassLoaderMap
.putIfAbsent(exportIndex, plugin.getPluginClassLoader());
}
for (String resource : plugin.getExportResources()) {
exportResourceAndClassLoaderMap.putIfAbsent(resource, new LinkedList<>());
exportResourceAndClassLoaderMap.get(resource).add(plugin.getPluginClassLoader());
}
for (String resource : plugin.getExportPrefixResourceStems()) {
exportPrefixStemResourceAndClassLoaderMap.putIfAbsent(resource, new LinkedList<>());
exportPrefixStemResourceAndClassLoaderMap.get(resource).add(
plugin.getPluginClassLoader());
}
for (String resource : plugin.getExportSuffixResourceStems()) {
exportSuffixStemResourceAndClassLoaderMap.putIfAbsent(resource, new LinkedList<>());
exportSuffixStemResourceAndClassLoaderMap.get(resource).add(
plugin.getPluginClassLoader());
}
}
}
4.1.14 然后我们看下,第二步插件部署逻辑PluginDeployServiceImpl.deploy代码,按照顺序,循环部署。
public void deploy() throws ArkRuntimeException {
for (Plugin plugin : pluginManagerService.getPluginsInOrder()) {
。。。
deployPlugin(plugin);
。。。
}
}
private void deployPlugin(Plugin plugin) throws ArkRuntimeException {
。。。
plugin.start();
。。。
}
4.1.15 继续往下看,在PluginModel.start方法中,整个代码逻辑是用插件自己的类加载器PluginClassLoader加载了插件中实现的启动类PluginActivator,最后通过执行pluginActivator.start来启动插件。
public void start() throws ArkRuntimeException {
。。。
EventAdminService eventAdminService = ArkServiceContainerHolder.getContainer().getService(
EventAdminService.class);
// zy 每个插件都有一个自己的类加载器: PluginClassLoader
ClassLoader oldClassLoader = ClassLoaderUtils
.pushContextClassLoader(this.pluginClassLoader);
try {
eventAdminService.sendEvent(new BeforePluginStartupEvent(this));
// zy 使用自己的PluginClassLoader加载插件的启动类
pluginActivator = (PluginActivator) pluginClassLoader.loadClass(activator)
.newInstance();
// zy 调用start方法, 启动插件
pluginActivator.start(pluginContext);
} catch (Throwable ex) {
throw new ArkRuntimeException(ex.getMessage(), ex);
} finally {
eventAdminService.sendEvent(new AfterPluginStartupEvent(this));
ClassLoaderUtils.popContextClassLoader(oldClassLoader);
}
}
4.1.16 至此插件的启动依然清晰,4.8.3中剩下biz模块也比较重要,我们再继续分析下,进入到DeployBizStage代码,不同于插件部署,biz模块部署后会通过eventAdminService.sendEvent发送一个事件,用于通知相关的事件钩子。
public void process(PipelineContext pipelineContext) throws ArkRuntimeException {
String[] args = pipelineContext.getLaunchCommand().getLaunchArgs();
bizDeployService.deploy(args);
eventAdminService.sendEvent(new AfterFinishDeployEvent());
}
4.1.17 继续看bizDeployService.deploy的代码,init方法就是给变量赋值了,没什么。继续跟bizDeployer.deploy的代码
public void deploy(String[] args) throws ArkRuntimeException {
ServiceReference<BizDeployer> serviceReference = registryService
.referenceService(BizDeployer.class);
bizDeployer = serviceReference.getService();
。。。。
bizDeployer.init(args);
bizDeployer.deploy();
}
4.1.18 进入bizDeployer.deploy的代码,可以看到和插件部署类似,也是按照顺序,循环启动biz模块。
public void deploy() {
for (Biz biz : bizManagerService.getBizInOrder()) {
。。。
biz.start(arguments);
。。。
}
}
4.1.19 进入BizModel.start方法,依然使用了MainMethodRunner这个工具类来启动当前biz模块,参数mainClass是biz模块中有main方法的启动类,是在创建BizModel对象的时候设置的,jar包的解析流程,后面我会再写一篇文章分析下。这里和插件启动一样,每个biz模块也会由一个独立的BizClassLoader负责加载模块所有的类。
public void start(String[] args) throws Throwable {
。。。
// zy 替换成BizClassLoader
ClassLoader oldClassLoader = ClassLoaderUtils.pushContextClassLoader(this.classLoader);
EventAdminService eventAdminService = ArkServiceContainerHolder.getContainer().getService(
EventAdminService.class);
。。。
eventAdminService.sendEvent(new BeforeBizStartupEvent(this));
resetProperties();
MainMethodRunner mainMethodRunner = new MainMethodRunner(mainClass, args);
// zy 部署biz模块
mainMethodRunner.run();
// this can trigger health checker handler
eventAdminService.sendEvent(new AfterBizStartupEvent(this));
。。。
}
4.2 启动流程回顾
从下面的类图中可以总结出启动过程中最重要的三个类:SofaArkBootstrap、ArkContainer以及ArkServiceContainer,插件和biz模块都是在ArkServiceContainer中通过pipeline机制,部署起来的。
image.png
4.3 类加载
从上面的代码分析可以看到,SOFAArk里面的ContainerClassLoader、PluginClassLoader以及BizClassLoader,都是打破了双亲委派机制,采用了一种分发机制去加载类。
以BizClassLoader为例,如下图所示,会根据类的类型,使用不同的方式加载类。所以SOFAArk里面的类加载是一种分发加载机制。
image.png