运维攻坚之JDK升级导致接口404问题

背景

某项目接口采用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配置




    
        jersey
        org.glassfish.jersey.servlet.ServletContainer
        
            jersey.config.server.provider.packages
            com.xx.poc
        
        1
    
    
        jersey
        /*
    

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 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

 
     jersey.config.server.provider.classnames
     com.fuyao.poc.JerseyService
 

重新部署,问题解决

总结

对jersey的印象一直就不大好,兼容性差,依赖多,很容易出问题,这次更是验证了jersey确实不是一个成熟的框架,最终也没找到根本原因算是一个遗憾,另一方面,weblogic的兼容性也是出名的差,很多应用在tomcat上跑的好好的,在weblogic就是有问题,还要依靠各种奇技淫巧解决,比如spring boot。很大一部分原因是weblogic并不像tomcat那样只提供环境,weblogic本身自带另很多库,导致和应用的jar包冲突,所以在部署weblogic应用时,最好在weblogic.xml上加上以下配置,减少冲突。




   
      true
      true
   


   appContextRoot

你可能感兴趣的