插件热插拔管理机制，简称：H-Spi。是框架提供的生产时用的另一种高级扩展方案。相对E-Spi，H-Spi 更侧重隔离、热插热拔、及管理性。

应用时，是以一个业务模块为单位进行开发，且封装为一个独立插件包。

### 1、特点说明

* 所有插件包 “独享” ClassLoader、AppContext、配置；完全隔离
  * 可通过 Solon.app(), Solon.cfg(), Solon.context() 等...手动获取主程序或全局的资源
* 模块可以打包成一个独立的插件包（放在体外加载），也可以与主程序一起打包。“分”或“合”也是自由！
* 更新插件包，不需要重启主服务。热更新！
* 开发时，所有资源完全独立自控。且必须完成资源移除的任务
* 模块之间的通讯，尽量交由事件总线（EventBus）处理。且尽量用弱类型的事件数据（如Map，或 JsonString）。<mark>建议结合 "[DamiBus](https://gitee.com/noear/dami)" 一起使用，它能帮助解耦</mark>
* 主程序需要引入 "[solon-hotplug](/article/273)" 依赖，对业务插件包进行管理

### 2、关于 ClassLoader 隔离

在 ClassLoader 隔离下，开发业务是比较麻烦的。注意：

* 父级 ClassLoader （一般，公共资源放这里）
  * 子级，可以获取并使用它的类或资源
  * 如果有东西注册，在插件 stop 事件里要注销掉
*  同级 ClassLoader
  *  同级，无法相互使用类或资源
  *  不要有显示类的交互
  *  一般通过事件总线进行交互
      *  交互的数据一般用父级 ClassLoader 的实体类
      *  或者用弱类型的数据，如 json（像使用远程接口那样对待）

尽量让插件之间，相互比较独立，不需要什么交互（或少量使用事件总线交互）。

### 3、插件开发注意示例

* 插件在“启动”时添加到公共场所的资源或对象，必须在插件停止时须移除掉（为了能热更新）：

```java
public class Plugin1Impl implements Plugin {
    AppContext context;
    StaticRepository staticRepository;

    @Override
    public void start(AppContext context) {
        this.context = context;
        
        //添加自己的配置文件
        context.cfg().loadAdd("demo1011.plugin1.yml");
        //扫描自己的bean
        context.beanScan(Plugin1Impl.class);

        //添加自己的静态文件仓库（注册classloader）
        staticRepository = new ClassPathStaticRepository(context.getClassLoader(), "plugin1_static");
        StaticMappings.add("/html/", staticRepository);
    }

    @Override
    public void stop() throws Throwable {
        //移除http处理。//用前缀，方便移除
        Solon.app().router().remove("/user");

        //移除定时任务（如果有定时任务，选支持手动移除的方案）
        JobManager.getInstance().jobRemove("job1");

        //移除事件订阅
        context.beanForeach(bw -> {
            if (bw.raw() instanceof EventListener) {
                EventBus.unsubscribe(bw.raw());
            }
        });

        //移除静态文件仓库
        StaticMappings.remove(staticRepository);
    }
}
```

* 一些涉及 classloader 的相关细节，需要多多注意。比如后端模板的渲染：

```java
public class BaseController implements Render {
    //要考虑模板所在的classloader
    static final FreemarkerRender viewRender = new FreemarkerRender(BaseController.class.getClassLoader());

    @Override
    public void render(Object data, Context ctx) throws Throwable {
        if (data instanceof Throwable) {
            throw (Throwable) data;
        }

        if (data instanceof ModelAndView) {
            viewRender.render(data, ctx);
        } else {
            ctx.render(data);
        }
    }
}
```

* 更多细节，需要根据业务情况多多注意。

### 4、插件管理

插件有管理能力后，还可以仓库化，平台化。详见：[《solon-hotplug》](/article/273)