背景
某项目接口采用jersey开发,部署在weblogic上,升级JDK至jdk1.7.0_191后所有接口无法访问,返回404 Not Found。
测试
首先怀疑是JDK升级后部分java类初始化失败导致404,但重启服务器,重启应用,重新部署应用都未报错,说明应用启动是成功的,一开始也没怀疑到jersey框架上,因此走了很多弯路,这里就不赘述。到后面所有的猜测都验证后,还是没有解决,于是决定本地用jersey写一个最简单的接口部署上去看下结果。
- 接口部署到线上weblogic访问404
- 接口部署到本地tomcat,访问正常
- 接口部署到其他weblogic环境,访问正常,jdk版本为
jdk1.7.0_80
- 正常的环境将jdk切换至
jdk1.8.0_171
,访问404
基于以上实验可以看出,**jersey在weblogic环境下,高版本的jdk会报404,tomcat没有这问题**,确定了问题后排查方向就比较清楚,就是搞清楚为什么高版本的jdk会报错。
解决
以下是jersey的web.xml配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"> <servlet> <servlet-name>jersey</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>jersey.config.server.provider.packages</param-name> <param-value>com.xx.poc</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jersey</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
jersey工作原理很简单,所有的请求(/*)都由统一的servlet(org.glassfish.jersey.servlet.ServletContainer)进行处理,jersey会扫描jersey.config.server.provider.packages
下所有的类,ServletContainer根据url进行分发请求处理。所以首先从ServletContainer入手,通过查看jersey源码找到以下代码(位于org.glassfish.jersey.server.ServerRuntime.process)
final Ref<Endpoint> endpointRef = Refs.emptyRef(); final RequestProcessingContext data = Stages.process(context, requestProcessingRoot, endpointRef); final Endpoint endpoint = endpointRef.get(); if (endpoint == null) { // not found throw new NotFoundException(); }
看来就是在这里导致了404,进一步追踪,以下代码set了一个空,也就是Stages.extractInflector(lastStage)返回了空
inflectorRef.set(Stages.extractInflector(lastStage));
使用arthas进行调试证实了猜想
watch org.glassfish.jersey.process.internal.Stages extractInflector "{returnObj}" -x 2 Press Q or Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) cost in 69 ms. ts=2020-11-21 13:14:36; [cost=0.652066ms] result=@ArrayList[ null, ]
原因应该是web.xml中jersey.config.server.provider.packages
不生效,rest相关类没有被扫描到,这里也找到了扫描的代码位于org.glassfish.jersey.server.ResourceConfig.scanClasses
中
final String[] classNames = parsePropertyValue(ServerProperties.PROVIDER_CLASSNAMES); if (classNames != null) { for (final String className : classNames) { try { result.add(_state.getClassLoader().loadClass(className)); } catch (final ClassNotFoundException e) { LOGGER.log(Level.CONFIG, LocalizationMessages.UNABLE_TO_LOAD_CLASS(className)); } } } final String[] packageNames = parsePropertyValue(ServerProperties.PROVIDER_PACKAGES); if (packageNames != null) { final Object p = getProperty(ServerProperties.PROVIDER_SCANNING_RECURSIVE); final boolean recursive = p == null || PropertiesHelper.isProperty(p); rfs.add(new PackageNamesScanner(packageNames, recursive)); }
从代码上看,jersey支持两种定义扫描类方式
- 直接定义类名
- 定义包路径,扫描该路径下所有类
包路径扫描由org.glassfish.jersey.server.internal.scanning.PackageNamesScanner
负责,遗憾的是最终还是未找到未扫描到的原因,因为扫描过程程序启动时就完成了,很难使用arthas进行跟踪调试。但我们也从代码中发现了另外一种方法,就是直接指定rest类。修改web.xml添加ServerProperties.PROVIDER_CLASSNAMES配置,也就是jersey.config.server.provider.classnames
<init-param> <param-name>jersey.config.server.provider.classnames</param-name> <param-value>com.fuyao.poc.JerseyService</param-value> </init-param>
重新部署,问题解决
## 总结
对jersey的印象一直就不大好,兼容性差,依赖多,很容易出问题,这次更是验证了jersey确实不是一个成熟的框架,最终也没找到根本原因算是一个遗憾,另一方面,weblogic的兼容性也是出名的差,很多应用在tomcat上跑的好好的,在weblogic就是有问题,还要依靠各种奇技淫巧解决,比如spring boot。很大一部分原因是weblogic并不像tomcat那样只提供环境,weblogic本身自带另很多库,导致和应用的jar包冲突,所以在部署weblogic应用时,最好在weblogic.xml上加上以下配置,减少冲突。
<?xml version='1.0' encoding='UTF-8'?> <wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd http://xmlns.oracle.com/weblogic/weblogic-web-app http://xmlns.oracle.com/weblogic/weblogic-web-app/1.4/weblogic-web-app.xsd"> <wls:container-descriptor> <wls:prefer-web-inf-classes>true</wls:prefer-web-inf-classes> <wls:show-archived-real-path-enabled>true</wls:show-archived-real-path-enabled> </wls:container-descriptor> <wls:context-root>appContextRoot</wls:context-root> </wls:weblogic-web-app>
no comment untill now