应重写 findclass 而非 loadclass,因其是双亲委派中父加载器失败后调用的安全钩子,可安全插入加密 jar 的读取与解密逻辑,避免破坏委派机制导致核心类加载异常。

为什么重写 findClass 而不是 loadClass
loadClass 是双亲委派的入口,负责检查缓存、委托父加载器、最终才调用 findClass。如果直接重写 loadClass,容易破坏委派机制,导致核心类(如 String、Object)被错误加载,引发 NoClassDefFoundError 或 LinkageError。而 findClass 是留给子类自定义字节码来源的“安全钩子”——它只在父加载器查不到类时被调用,且默认抛出 ClassNotFoundException,正适合我们插入加密 Jar 的读取与解密逻辑。
加密 Jar 的典型结构与字节码提取
假设你已用 AES 对 jar 中所有 .class 文件进行了逐文件加密,并在 jar 包末尾追加了加密元信息(如偏移表、密钥盐值)。自定义加载器需完成三件事:打开 JarFile、定位目标类对应的加密 entry、按规则读取并解密字节流。
- 用 JarFile 打开 jar,避免用 FileInputStream 直读 —— JarFile 支持随机访问,可快速跳转到指定 entry 的压缩/未压缩数据区
- 通过 jarFile.getJarEntry(className.replace('.', '/') + ".class") 获取加密项;若 entry 不存在,直接 return null 让上层抛异常
- 用 jarFile.getInputStream(entry) 获取原始加密流,再经对称解密(如 AES/GCM 模式)还原为标准 class 字节码
findClass 的关键实现要点
重写 findClass 时,不能简单返回 defineClass(bytes),还需处理类名校验、解密失败降级、以及 defineClass 的 ClassFormatError 容错。
- 类名必须严格匹配 jar 中路径格式(如 java/lang/Object → java/lang/Object.class),否则 getJarEntry 返回 null
- 解密过程应捕获 BadPaddingException / InvalidKeyException,此时可记录日志并 rethrow ClassNotFoundException,避免静默失败
- defineClass 前建议先校验 magic number(bytes[0]==(byte)0xca && bytes[1]==(byte)0xfe && ...),防止解密错误导致 VM 崩溃
- 若需支持同一类多次加载(如热替换),需确保 defineClass 传入的 protectionDomain 合理,或显式使用 defineClass(name, bytes, 0, bytes.length, pd)
配合 SecurityManager 与模块隔离(可选增强)
仅靠类加载器无法阻止反射获取私有字段或绕过加密逻辑。若需更强保护,可在 defineClass 后立即调用 setSigners 绑定自定义 CodeSource,再配合自定义 SecurityManager 拦截 ClassLoader.defineClass 和 ClassLoader.findLoadedClass 等敏感调用。
注意:JDK 9+ 模块系统下,自定义加载器需继承 SecureClassLoader 并重写 getPermissions(CodeSource),否则模块类加载可能因权限不足失败。











