jPOS GenericPackager在JAR运行时抛空指针异常,IDE中正常(含根因与解决方案)
jPOS GenericPackager JAR运行时抛出NullPointerException问题分析与解决
根因分析
问题核心在于线程上下文类加载器差异:
CompletableFuture.runAsync(Runnable)默认使用ForkJoinPool.commonPool(),该线程池的线程上下文类加载器与Spring Boot应用主线程的类加载器不一致。- JAR包运行环境中,类资源被打包在JAR内部,GenericPackager初始化过程依赖线程上下文类加载器访问内部资源,使用ForkJoinPool线程时无法正确加载资源,进而触发NullPointerException。
- IDE运行时类资源以散列文件形式存在,类加载器环境更宽松,因此不会触发该问题。
解决方案
替换默认异步执行方式,指定自定义线程池,确保异步线程的上下文类加载器继承主线程的类加载器:
修改createCompletableFutureRun方法,将无参数的runAsync替换为带自定义线程池的版本:
private CompletableFuture<Void> createCompletableFutureRun(Runnable runnable) { // 使用自定义线程池替代默认ForkJoinPool return CompletableFuture.runAsync(runnable, Executors.newFixedThreadPool(1)); }
注:生产环境建议使用Spring管理的线程池(如ThreadPoolTaskExecutor),避免临时创建线程池导致的资源泄漏问题。
原问题信息
问题复现步骤
- 执行
mvn clean package生成JAR包 - 在IDE中运行
IsoPackagerExceptionApplication类,运行正常 - 使用
java -jar target/ISOPackagerException-0.0.1-SNAPSHOT.jar运行JAR包,抛出异常 - 通过Postman或cURL发起请求:
curl --location --request POST 'http://localhost:8123/test'
JAR运行时异常栈信息
org.jpos.iso.ISOException: java.lang.NullPointerException at org.jpos.iso.packager.GenericPackager.readFile(GenericPackager.java:224) ~[jpos-2.1.8.jar!/:2.1.8] at org.jpos.iso.packager.GenericPackager.<init>(GenericPackager.java:126) ~[jpos-2.1.8.jar!/:2.1.8] at com.example.ISOPackagerException.controller.Controller.lambda$testMethod$0(Controller.java:52) ~[classes!/:0.0.1-SNAPSHOT]
核心代码
package com.example.ISOPackagerException.controller; import org.jpos.iso.packager.GenericPackager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import java.io.InputStream; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; @RestController @RequestMapping(value = "/test") public class Controller { private static final Logger logger = LoggerFactory.getLogger(Controller.class); @PostMapping @ResponseStatus(HttpStatus.OK) public void test() { testMethod(); } public void testMethod() { logger.info("Testing GenericPackager1 (This works fine)"); try { InputStream inputStream = getClass().getResourceAsStream("/some.xml"); if (inputStream == null) { logger.error("GenericPackager1 inputStream is null"); } else { logger.info("GenericPackager1 inputStream is NOT null"); GenericPackager genericPackager = new GenericPackager(inputStream); logger.info("Successful GenericPackager1: " + genericPackager); } } catch (Exception e) { logger.error("GenericPackager1 error", e); } createCompletableFutureRun(() -> { logger.info("Testing GenericPackager2 in CompletableFuture (This throws the NPE when running from JAR)"); try { InputStream inputStream = getClass().getResourceAsStream("/some.xml"); if (inputStream == null) { logger.error("GenericPackager2 inputStream is null"); } else { logger.info("GenericPackager2 inputStream is NOT null"); GenericPackager genericPackager = new GenericPackager(inputStream); logger.info("Successful GenericPackager2 in CompletableFuture: " + genericPackager); } } catch (Exception e) { logger.error("GenericPackager2 error", e); } }); } private CompletableFuture<Void> createCompletableFutureRun(Runnable runnable) { return CompletableFuture.runAsync(runnable); // 解决方案 // return CompletableFuture.runAsync(runnable, Executors.newFixedThreadPool(1)); } }
内容的提问来源于stack exchange,提问作者Alarka Sanyal
相关产品推荐
相关产品推荐

