如何从不受保护的上下文(例如 Eclipse Birt)中加载受 jar2exe 加密或保护的 class 文件?

How to load a class file encrypted or protected by jar2exe from unprotected context, for example, Eclipse Birt?

我在我的项目中使用了 Eclipse Birt Engine 4.4.2 (birt-runtime-4_4_2),当我使用 Jar2exe 加密 java classes 时,即使没有隐藏“C:\ fx.jar|META-INF*|com\javafx**" , Birt 引擎 class 加载器无法加载报告处理程序 classes, 有没有办法传递这个错误?

    Error.ScriptClassNotFoundError ( 1 time(s) )
detail : org.eclipse.birt.report.engine.api.EngineException: Class com.osyslocal.management.view.report.handler.stimgun.general.ReportHandler not found.
    at org.eclipse.birt.report.engine.executor.EventHandlerManager.loadClass(EventHandlerManager.java:104)
    at org.eclipse.birt.report.engine.executor.EventHandlerManager.getInstance(EventHandlerManager.java:75)
    at org.eclipse.birt.report.engine.executor.EventHandlerManager.getInstance(EventHandlerManager.java:49)
    at org.eclipse.birt.report.engine.script.internal.ScriptExecutor.getInstance(ScriptExecutor.java:195)
    at org.eclipse.birt.report.engine.script.internal.ReportScriptExecutor.handleInitialize(ReportScriptExecutor.java:86)
    at org.eclipse.birt.report.engine.api.impl.EngineTask.loadDesign(EngineTask.java:1962)
    at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.doRun(RunAndRenderTask.java:99)
    at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.run(RunAndRenderTask.java:77)
    at com.osyslocal.management.view.results.analysis.AnalyzeResult.createReport(AnalyzeResult.java:376)
    at com.osyslocal.management.view.ui.ProjectView.showReport(ProjectView.java:1015)
    at com.osyslocal.management.view.results.stimgun.AnalyzeStimgunResult.execute(AnalyzeStimgunResult.java:90)
    at mysystem.controller.TaskManager.run(TaskManager.java:145)
    at org.eclipse.swt.widgets.RunnableLock.run(Unknown Source)
    at org.eclipse.swt.widgets.Synchronizer.runAsyncMessages(Unknown Source)
    at org.eclipse.swt.widgets.Display.runAsyncMessages(Unknown Source)
    at org.eclipse.swt.widgets.Display.readAndDispatch(Unknown Source)
    at org.eclipse.jface.window.Window.runEventLoop(Window.java:826)
    at org.eclipse.jface.window.Window.open(Window.java:802)
    at com.osyslocal.management.view.ui.Main.run(Main.java:463)
    at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)
    at com.osyslocal.management.view.ui.Main.main(Main.java:456)
Caused by: java.lang.ClassNotFoundException: com.osyslocal.management.view.report.handler.stimgun.general.ReportHandler
    at org.eclipse.birt.core.framework.URLClassLoader.findClass1(URLClassLoader.java:188)
    at org.eclipse.birt.core.framework.URLClassLoader.run(URLClassLoader.java:156)
    at org.eclipse.birt.core.framework.URLClassLoader.run(URLClassLoader.java:1)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.eclipse.birt.core.framework.URLClassLoader.findClass(URLClassLoader.java:151)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at org.eclipse.birt.report.engine.executor.ApplicationClassLoader.loadClass(ApplicationClassLoader.java:79)
    at org.eclipse.birt.report.engine.executor.EventHandlerManager.loadClass(EventHandlerManager.java:99)
    ... 20 more

问题: 根据jar2exe官网提供的解决方案,每个class都有一个ClassLoader加载class.

受保护(加密)classes的ClassLoader,是一个特殊的ClassLoader,而classes的未受保护的ClassLoader,则是另外一个ClassLoader。当程序要加载一个class或资源时,会默认使用当前class的ClassLoader,如“Class.forName()”。所以在Eclipse Birt程序中是在未受保护的class内加载受保护的资源,未受保护的class的ClassLoader无法加载受保护的资源。在这个问题中,程序试图从未受保护的 ApplicationClassLoader class.

加载 ReportHandler class

解决方法: 当一个生成的exe文件运行时,当前线程的context ClassLoader,是一个Special ClassLoader。所以我们尝试下载Eclipse Birt Engine的源码,然后修改了ApplicationClassLoader class的loadClass(className)方法的主体,并替换了下面一行:

Thread.currentThread().getContextClassLoader().loadClass(className);

因此,程序将 className 作为参数发送,其中包含受保护的 class (ReportHandler) 的 FQDN。然后Eclipse Birt jar文件中修改编译好的ApplicationClassLoader class成功运行程序,没有失败。该方法最终编辑版本如下:

public Class loadClass( String className ) throws ClassNotFoundException
    {
        return Thread.currentThread().getContextClassLoader().loadClass(className);
    }