Java Web Start中Jersey Client发起意外请求问题排查
使用的Jersey依赖
<dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey-client</artifactId> <version>2.34</version> <type>jar</type> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-jackson</artifactId> <version>2.34</version> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-sse</artifactId> <version>2.34</version> <type>jar</type> </dependency> <dependency> <groupId>org.glassfish.jersey.inject</groupId> <artifactId>jersey-hk2</artifactId> <version>2.34</version> </dependency>
应用背景
我的Java应用打包成包含所有依赖的uber-jar(JNLP文件中名为MyApplication-1.0.jar),是一款无界面应用,基于JAX-RS和Jersey库发起API请求,通过Java Web Start(基于AdoptOpenJDK/IcedTea-Web)在多设备上运行。
异常现象
部分设备运行应用一段时间(至少2-3天)后,应用仍存活的情况下会持续发起对MyApplication-1.0.jar的请求,服务器访问日志记录如下:
172.53.45.128 (81.53.51.24) - - [03/Jan/2020:12:49:01 +0000] "GET /MyApplication-1.0.jar HTTP/1.1" 200 20308737 "-" "Java/1.8.0_222"
测试验证
移除服务器上的JNLP和jar文件后,原本持续请求该jar的运行中设备,每次发起Rest API请求时都会抛出异常:
org.glassfish.jersey.internal.inject.InjectionManagerFactory: : java.io.FileNotFoundException: https://dev.myapplication.com/MyApplication-1.0.jar
栈跟踪信息
触发该请求的Jersey库代码位于org.glassfish.jersey.internal.inject.Injections类的lookupService方法,完整栈跟踪如下:
org.glassfish.jersey.internal.ServiceConfigurationError: org.glassfish.jersey.internal.inject.InjectionManagerFactory: : java.io.FileNotFoundException: https://dev.myapplication.com/MyApplication-1.0.jar at org.glassfish.jersey.internal.ServiceFinder.fail(ServiceFinder.java:410) at org.glassfish.jersey.internal.ServiceFinder.parse(ServiceFinder.java:489) at org.glassfish.jersey.internal.ServiceFinder.access$400(ServiceFinder.java:132) at org.glassfish.jersey.internal.ServiceFinder$LazyObjectIterator.hasNext(ServiceFinder.java:686) at org.glassfish.jersey.internal.inject.Injections.lookupService(Injections.java:88) at org.glassfish.jersey.internal.inject.Injections.lookupInjectionManagerFactory(Injections.java:73) at org.glassfish.jersey.internal.inject.Injections.createInjectionManager(Injections.java:44) at org.glassfish.jersey.client.ClientConfig$State.initRuntime(ClientConfig.java:413) at org.glassfish.jersey.internal.util.collection.Values$LazyValueImpl.get(Values.java:317) at org.glassfish.jersey.client.ClientConfig.getRuntime(ClientConfig.java:819) at org.glassfish.jersey.client.ClientRequest.getClientRuntime(ClientRequest.java:176) at org.glassfish.jersey.client.ClientRequest.getInjectionManager(ClientRequest.java:567) at org.glassfish.jersey.client.JerseyWebTarget.onBuilder(JerseyWebTarget.java:371) at org.glassfish.jersey.client.JerseyWebTarget.request(JerseyWebTarget.java:206) at org.glassfish.jersey.client.JerseyWebTarget.request(JerseyWebTarget.java:38) at com.mycompany.myapp.restclient.MyAppJerseyClient.updateUserData(MyAppJerseyClient.java:164) <------ 我的应用API请求
疑问
这是什么原因?为何在Java Web Start中运行的Jersey Client会出现这类异常请求?
核心原因
问题根源是Jersey的ServiceFinder机制与Java Web Start的类加载行为冲突:
- Jersey通过
ServiceFinder读取META-INF/services下的配置文件来加载SPI实现(比如InjectionManagerFactory)。 - 在Java Web Start环境中,uber-jar的URL是远程地址,应用运行一段时间后,JWS的本地缓存可能失效或类加载器无法访问缓存文件,导致
ServiceFinder尝试重新从远程URL读取jar内的服务配置。 - 每次发起API请求时,Jersey需要初始化或重新获取
InjectionManager,触发ServiceFinder的查找逻辑,进而尝试下载整个jar包,最终在文件移除后抛出FileNotFoundException。
触发逻辑
从栈跟踪可见,调用JerseyWebTarget.request()时会触发ClientConfig的Runtime初始化,进而调用Injections.createInjectionManager(),该方法依赖ServiceFinder查找InjectionManagerFactory实现。如果此时JWS类加载器无法访问本地缓存的jar,就会发起远程请求。
解决方案
1. 显式指定InjectionManagerFactory(推荐)
创建Jersey Client时,直接指定实现类,跳过ServiceFinder的动态查找:
import org.glassfish.jersey.inject.hk2.Hk2InjectionManagerFactory; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.JerseyClientBuilder; // 显式配置InjectionManagerFactory ClientConfig config = new ClientConfig(); config.property("jersey.config.inject.injectionManagerFactory", Hk2InjectionManagerFactory.class.getName()); // 基于配置构建Client Client client = JerseyClientBuilder.createClient(config);
2. 优化Java Web Start缓存策略
在JNLP文件中添加配置,确保JWS稳定缓存jar文件:
- 添加
<offline-allowed/>标签,允许离线运行,强制缓存jar。 - 设置
<update check="background"/>,避免频繁检查更新,减少远程请求。
3. 拆分uber-jar
将应用与Jersey依赖拆分为多个独立jar,在JNLP中明确列出所有依赖,让JWS单独缓存每个jar,降低单个缓存失效的影响。
4. 升级Jersey版本
尝试升级到2.35及以上版本,后续版本针对JWS环境的类加载行为做了兼容性优化,可减少此类问题发生。
内容的提问来源于stack exchange,提问作者ansanes

