从问题了解Jetty类加载机制

1

问题导出

由于机器的原因,将服务从一台机器迁移到另外的机器上,在迁移后,接受邮件请求,并将其发送出去的邮件基础服务 messages 不可使用了。现象就是服务仍旧可以接受请求,但不能异步的将邮件正常的发送出去,并报有以下错误。

2

问题分析

那么有相应的方法,为什么还会报这样的错误:java.lang.NoSuchMethodError: javax.mail.internet.MimeMessage.setFrom(Ljava/lang/String;)V?最终怀疑是容器的问题,于是查看了所使用的 jetty 容器。

MimeMessage 类,也确实有 setFrom 方法,但是没有参数是 String 的 setFrom 方法。

这说明 jetty 容器优先使用了容器中 lib 下的 jar,而非 WEB-INF/lib下的 jar,那么为什么优先使用 jetty 容器中 lib 下的 jar 包,而非 WEB-INF/lib 下的 jar 呢?

3

Jetty中lib下jar先于WEB-INF/lib下的jar加载

Jetty,Tomcat 等 web 容器通常都会对 ClassLoader 做扩展,因为一个正常的容器至少要保证其内部运行的多个 webapp 之间:私有的类库不受影响,并且公有的类库可以共享。这正好发挥 ClassLoader 的层级划分优势。Jetty 中有一个 org.eclipse.jetty.webapp.WebAppClassLoader,负责加载一个 webapp context 中的应用类,WebAppClassLoader 以系统类加载器作为 parent,用于加载系统类。不过servlet 规范使得 web 容器的 ClassLoader 比正常的 ClassLoader 委托模型稍稍复杂。下面我们先看一下关于 servlet 容器的 JSR 规范。

JSR 规范

Jetty 是 servlet 容器,这里查了一下 JSR315 servlet 3 中对 web application class loader 的要求:

不允许应用去覆盖 JAVA SE 的系统类

不允许应用覆盖或存取容器的实现类

每个应用调用

getContextClassLoader()返回的都应该是实现了本规范中定义的 class loader。

每个应用的 class loader 必须要是独立的实例。

源码阅读

通过对 JSR 规范的理解,下面我们来阅读一下 Jetty 容器的代码实现,了解一下关于 JSR 规范的部分实现:

1、 系统类

Jetty 中以类的 package 路径名来区分,当类的 package 路径名位包含于以下路径时,会被认为是系统类。WebAppContext 中配置如下:

因此,我们可以通过 :

org.eclipse.jetty.webapp.WebAppContext.setSystemClasses(String Array) 或者 org.eclipse.jetty.webapp.WebAppContext.addSystemClass(String) 来设置系统类。 系统类是对多应用都可见。

2、 Server类

Server 类不对任何应用可见,Jetty 同样是用 package 路径名来区分哪些是 Server 类。WebAppContext 中配置如下:

我们可以通过:

org.eclipse.jetty.webapp.WebAppContext.setServerClasses(String Array) 或 org.eclipse.jetty.webapp.WebAppContext.addServerClass(String) 方法设置 Server 类。 注意,Server 类是对所有应用都不可见的,但是 WEB-INF/lib 下的类可以替换 Server 类。

3、自定义 WebApp ClassLoader

当默认的 WebAppClassLoader 不能满足需求时,可以自定义 WebApp ClassLoader,不过 Jetty 建议自定义的 ClassLoader 要扩展于默认的 WebAppClassLoader 实现。这里我们来看一下 WebAppClassLoader:

WebAppClassLoader的构造器:

WebAppClassLoader 还是按照正常的范式设置 parent ClassLoader,如果当前线程上下文中设定了 ClassLoader 就以当前线程上下文类加载器为父 ClassLoader,否则使用 WebAppClassLoader 的加载器,如果还没有,就采用系统类加载器。详细的加载过程请看 WebAppClassLoader的loadClass() 方法:

通过阅读源码,我们了解到,当在容器中启动一个服务的时候,容器的 jar 包和 class 文件加载顺序是:

优先加载 JDK 和 JRE 所需的 jar 包和 class 文件

加载容器所需的 jar 包和 class 文件

加载项目路径 /WEB-INF/class 下的文件

加载项目路径 /WEB-INF/lib 下的 jar 文件

注意:同一个文件夹下,jar包是按顺序从上到下依次加载

这里列举了启动一个 tomcat 服务的时候,jar 包和 class 文件的加载顺序:

$java_home/lib 目录下的 java 核心 api

$java_home/lib/ext 目录下的 java 扩展 jar 包

java -classpath/-Djava.class.path 所指的目录下的类与 jar 包

$CATALINA_HOME/common 目录下按照文件夹的顺序从上往下依次加载

$CATALINA_HOME/server 目录下按照文件夹的顺序从上往下依次加载

$CATALINA_BASE/shared 目录下按照文件夹的顺序从上往下依次加载

我们的项目路径 /WEB-INF/classes 下的 class 文件

我们的项目路径 /WEB-INF/lib下的 jar 文件

4

总结

通过以上分析,对于该问题的最终的解释就是:jetty 容器中 lib 下的 jar 包先于 WEB-INF中lib 下 jar 包加载,而且 WEB-INF/lib下的 jar包中类不能覆盖容器下 jar 包的类。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180807B18T3Z00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券