Gradle Shadow打包BIRT Report Engine为单可执行Jar时的空指针异常及报表运行问题排查求助
大家好,我现在正尝试用Java程序调用BIRT的Report Engine API来运行并渲染报表,使用的是Gradle 8.5和JDK 21构建运行程序。
用Gradle的run任务执行程序、生成报表都没问题,但打包成单个可执行Jar文件时遇到了麻烦。
问题重现:初始化Report Engine时的空指针异常
我的Report Engine初始化代码如下:
EngineConfig engineConfig = new EngineConfig(); engineConfig.setResourcePath(getConfigValue("resource_path")); // 返回报表资源文件夹路径 Platform.startup(engineConfig); // 这个方法返回NULL IReportEngineFactory engineFactory = (IReportEngineFactory) Platform .createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY); // 这里抛出空指针异常 // java.lang.NullPointerException: Cannot invoke "org.eclipse.birt.report.engine.api.IReportEngineFactory.createReportEngine(org.eclipse.birt.report.engine.api.EngineConfig)" because "engineFactory" is null engine = engineFactory.createReportEngine(engineConfig);
我的build.gradle配置:
plugins { id 'com.github.johnrengelman.shadow' version '8.1.1' id 'application' } version = '1.0.0' repositories { mavenCentral() } dependencies { implementation 'org.slf4j:slf4j-simple:2.0.9' implementation 'com.rabbitmq:amqp-client:5.20.0' implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0' implementation 'info.picocli:picocli:4.7.5' implementation fileTree('lib') { include '*.jar' } } java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } application { mainClass = 'report.engine.Main' } shadowJar { zip64 true archiveClassifier.set('standalone') }
lib文件夹里放的是birt-runtime-4.14.0/ReportEngine/lib下的所有Jar包,shadowJar任务能正常完成并生成包含所有依赖的Fat Jar,但用java -jar "/path/to/fat.jar"运行时就会抛出上面的空指针异常,程序执行失败。
后续测试发现的现象
经过进一步测试,我发现如果把birt-runtime-4.14.0/ReportEngine/lib里的org.eclipse.birt.runtime_4.14.0-202312020807.jar放到Fat Jar所在的文件夹,再用同样的命令运行,createFactoryObject方法就不会返回null了,也不会触发之前的空指针。
但这时候尝试运行渲染报表(在正常用run任务、依赖文件夹在类路径下时能正常运行),又会抛出另一个异常,日志如下:
2023-12-28 07:40:52 | [FINE] EngineTask.setParameterValue: theme=1_default [java.lang.String] 2023-12-28 07:40:52 | [FINE] EngineTask.setParameterValue: ids=[Ljava.lang.Object;@669ed014 [[Ljava.lang.Object;] 2023-12-28 07:40:53 | [FINE] Running the report with paramters: theme:1_default ids:[Ljava.lang.Object;@669ed014 2023-12-28 07:40:53 | [FINE] handle type: org.eclipse.birt.report.model.api.OdaDataSetHandle 2023-12-28 07:40:53 | [FINE] Start to prepare a PreparedQuery. 2023-12-28 07:40:53 | [FINE] Finished preparing the PreparedQuery. 2023-12-28 07:40:53 | [FINE] Finished preparing query. 2023-12-28 07:40:53 | [SEVERE] An error happened while running the report. Cause: 2023-12-28 07:40:53 | [SEVERE] org.eclipse.birt.report.engine.api.EngineException: Error happened while running the report. at org.eclipse.birt.report.engine.api.impl.EngineTask.handleFatalExceptions(EngineTask.java:1867) at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.doRun(RunAndRenderTask.java:153) at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.run(RunAndRenderTask.java:69) at ivertix.bi.report.engine.ReportTask.run(ReportTask.java:73) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:833) Caused by: java.lang.NullPointerException: Cannot read the array length because "extensions" is null at org.eclipse.datatools.connectivity.oda.spec.manifest.ResultExtensionExplorer.addAllExtensions(ResultExtensionExplorer.java:541) at org.eclipse.datatools.connectivity.oda.spec.manifest.ResultExtensionExplorer.<init>(ResultExtensionExplorer.java:122) at org.eclipse.datatools.connectivity.oda.spec.manifest.ResultExtensionExplorer.getInstance(ResultExtensionExplorer.java:86) at org.eclipse.birt.data.engine.impl.DataEngineImpl.getValidationContext(DataEngineImpl.java:681) at org.eclipse.birt.data.engine.impl.OdaDataSetRuntime.<init>(OdaDataSetRuntime.java:54) at org.eclipse.birt.data.engine.impl.DataSetRuntime.newInstance(DataSetRuntime.java:356) at org.eclipse.birt.data.engine.impl.PreparedDataSourceQuery.configureDataSetCache(PreparedDataSourceQuery.java:198) at org.eclipse.birt.data.engine.impl.PreparedDataSourceQuery.execute(PreparedDataSourceQuery.java:149) at org.eclipse.birt.data.engine.impl.PreparedOdaDSQuery.execute(PreparedOdaDSQuery.java:172) at org.eclipse.birt.report.data.adapter.impl.DataRequestSessionImpl.execute(DataRequestSessionImpl.java:594) at org.eclipse.birt.report.engine.data.dte.DteDataEngine.doExecuteQuery(DteDataEngine.java:138) at org.eclipse.birt.report.engine.data.dte.AbstractDataEngine.execute(AbstractDataEngine.java:254) at org.eclipse.birt.report.engine.executor.ExecutionContext.executeQuery(ExecutionContext.java:1624) at org.eclipse.birt.report.engine.executor.QueryItemExecutor.executeQuery(QueryItemExecutor.java:73) at org.eclipse.birt.report.engine.executor.TableItemExecutor.execute(TableItemExecutor.java:60) at org.eclipse.birt.report.engine.internal.executor.dup.SuppressDuplicateItemExecutor.execute(SuppressDuplicateItemExecutor.java:41) at org.eclipse.birt.report.engine.internal.executor.wrap.WrappedReportItemExecutor.execute(WrappedReportItemExecutor.java:45) at org.eclipse.birt.report.engine.internal.executor.l18n.LocalizedReportItemExecutor.execute(LocalizedReportItemExecutor.java:34) at org.eclipse.birt.report.engine.layout.html.HTMLBlockStackingLM.layoutNodes(HTMLBlockStackingLM.java:62) at org.eclipse.birt.report.engine.layout.html.HTMLPageLM.layout(HTMLPageLM.java:92) at org.eclipse.birt.report.engine.layout.html.HTMLReportLayoutEngine.layout(HTMLReportLayoutEngine.java:97) at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.doRun(RunAndRenderTask.java:145) ... 5 more
看起来问题根源在于BIRT Runtime的Jar包是被打包在Fat Jar内部,而不是和Fat Jar放在同一文件夹下。因为如果我把ReportEngine/lib下的所有Jar包都放到Fat Jar所在文件夹,再用java -jar "/path/to/fat.jar"运行,就不会有任何错误,报表能正常渲染。
这里有两点需要注意:
- 使用
java -jar命令时会忽略系统的classpath - Fat Jar所在文件夹里并没有其他非BIRT的外部依赖(比如slf4j、jackson、rabbitmq这些),它们只在Fat Jar内部,但运行完全正常
我觉得很奇怪,为什么BIRT Runtime的依赖和其他普通依赖表现不一样?
我们的目标是生成单个可执行Jar文件,这样作为服务部署时只需要上传这一个文件就能运行,不用额外操作——这应该是很常规的做法,但BIRT Runtime好像没法这么用?我不是Java专家,可能漏看了什么关键点,有没有大佬能指点一下?
备注:内容来源于stack exchange,提问作者dariobella

