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 包的类。
领取专属 10元无门槛券
私享最新 技术干货