PluginLoader

5 个集成的反射加载

基本信息

属性 值
路径 com.github.lunatrius.ingameinfo.integration.PluginLoader
行数 70
单例 懒加载 getInstance()(:17-23)

5 个集成的类名是字符串硬编码(:26-30)

final String[] names = new String[] {
    "com.github.lunatrius.ingameinfo.integration.bloodmagic.BloodMagic",
    "com.github.lunatrius.ingameinfo.integration.simplyjetpacks.SimplyJetpacks",
    "com.github.lunatrius.ingameinfo.integration.terrafirmacraft.TerraFirmaCraft",
    "com.github.lunatrius.ingameinfo.integration.thaumcraft.Thaumcraft",
    "com.github.lunatrius.ingameinfo.integration.gregtech.Gregtech" };

⚠️ 数组顺序与包名字母序不一致: bloodmagic → simplyjetpacks → terrafirmacraft → thaumcraft → gregtech (gregtech 在最后,不在 g 位置)。

⚠️ 用字符串反射而非 Class.forName on 直接引用, 是为了让未安装的集成不触发类加载。若直接 import + new, NoClassDefFoundError 会在 preInit 直接崩掉整个 mod。

⚠️ 代价:改名/移包不会编译期报错,只在运行期记 "{} not found, skipping..."。

preInit(:25-58)—— 反射加载 + 筛选

for (final String name : names) {
    try {
        final Class<?> clazz = Class.forName(name);
        if (!Plugin.class.isAssignableFrom(clazz)) { continue; }        // :36-38
        @SuppressWarnings("unchecked")
        final Class<? extends Plugin> clazzPlugin = (Class<? extends Plugin>) clazz;
        final Plugin plugin = clazzPlugin.newInstance();                 // :42
        if (plugin.canLoad()) { this.plugins.add(plugin); }             // :43-45
    } catch (ClassNotFoundException cnfe)    { logger.error("{} not found, skipping...", name); }
      catch (InstantiationException ie)      { logger.error("{} could not be created, skipping...", name); }
      catch (IllegalAccessException iae)     { logger.error("{} could not be accessed, skipping...", name); }
}
if (this.plugins.size() > 0) {
    event.getModMetadata().description += "\nCompiled against " + this.plugins;   // :56
}
步骤 说明
Class.forName 触发类加载;未安装的集成类仍在本 jar 内,所以这里不会抛 ClassNotFoundException
isAssignableFrom 类型检查,不匹配则静默 continue(不记日志)
newInstance() 已废弃 API(Java 9+),等价 getDeclaredConstructor().newInstance()
canLoad() Loader.isModLoaded(依赖 modid)

⚠️ Class.forName 在这里几乎不会抛 ClassNotFoundException —— 5 个集成类都编译进本 mod 的 jar(它们是本 mod 的源码), Class.forName 一定能找到。真正决定「集成是否生效」的是 canLoad()(目标 mod 是否安装)。

所以 catch (ClassNotFoundException) 是防御性代码, 只在 jar 损坏/类被裁剪时才触发。

⚠️ newInstance() 要求类有 public 无参构造器。 5 个插件类都满足(隐式 public 无参构造器)。

⚠️ isAssignableFrom 失败时静默 continue(:36-38), 不记日志 —— 若有人写了不继承 Plugin 的类,会无声消失。

追加到元数据(:55-57)

只在 plugins.size() > 0 时追加。 ⚠️ 该 += 无幂等保护,重复调 preInit 会重复追加 (FML 保证只调一次,实际不会)。

postInit(:60-69)—— 真正 load + 释放单例

for (final Plugin plugin : this.plugins) {
    plugin.load();
    Reference.logger.debug("Loaded {} integration.", plugin.getDependencyName());
}
instance = null;      // :68  "all plugins loaded, let it go~"
步骤 说明
plugin.load() 各插件调自己的 TagXxx.register(),把集成 tag 加进 TagRegistry
logger.debug debug 级别,默认不可见
instance = null 把单例置空,下次 getInstance() 会重建

⚠️ instance = null 是有意为之(源码注释 :67 “all plugins loaded, let it go~”), 目的是让 PluginLoader 可被 GC 回收。

⚠️ 但 ClientProxy 在 postInit 里 用 PluginLoader.getInstance().postInit(event)(:62)调用 —— 调用返回后实例已为 null。若之后再调 getInstance(), 会得到一个空的 PluginLoader(plugins 列表为空)。

postInit 之后不再有调用点,所以无实际问题。

服务端不执行 postInit

ServerProxy 未覆写 postInit (继承 CommonProxy 的空实现), 所以服务端的 PluginLoader.instance 永不被置 null。 详见 ServerProxy 条目。

调用时机(由 ClientProxy 决定)

阶段 调用 位置
preInit PluginLoader.getInstance().preInit(event) ClientProxy :37
postInit PluginLoader.getInstance().postInit(event) 同上 :62

⚠️ preInit 在 core.reloadConfig()(:43)之前, 但 postInit 在它之后 —— 所以集成 tag 的注册晚于配置解析, 首次启动时配置里的集成 tag 会解析成 <ERROR>。 详见 ClientProxy 条目。

相关