免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
tomcat插件類加載一個(gè)“坑”問(wèn)題排查

昨天遇到一個(gè)詭異但是很有趣的類加載問(wèn)題,雖然很快解決了,但是我還是打算剖根問(wèn)底,分析內(nèi)部問(wèn)題出現(xiàn)的原因,畢竟類加載機(jī)制雖然說(shuō)都知道怎么回事,但是還沒(méi)在實(shí)戰(zhàn)中實(shí)踐過(guò),也考慮到有個(gè)項(xiàng)目可能需要用到自定義類加載器,趁此機(jī)會(huì)先初步了解一下。

問(wèn)題描述

  1. 我采用了Servlet3.0,新增加了SPI加載機(jī)制,會(huì)自動(dòng)掃描classpath:META-INF/services/javax.servlet.ServletContainerInitializer中的所有這個(gè)文件,并加載其中的所有javax.servlet.ServletContainerInitializer的實(shí)現(xiàn)類,實(shí)現(xiàn)替換web.xml的功能,讓你的項(xiàng)目war可以不需要web.xml也能正常在tomcat運(yùn)行。
  2. 然后呢,日志我采用了logback,很可愛(ài)的是這個(gè)jar中ch.qos.logback.classic.servlet.LogbackServletContainerInitializer就實(shí)現(xiàn)了javax.servlet.ServletContainerInitializer,因此呢,tomcat在啟動(dòng)時(shí)就會(huì)自動(dòng)加載這個(gè)類初始化一些配置。
  3. LogbackServletContainerInitializer是在logback-classic包中的,javax.servlet.ServletContainerInitializer是在javax.servlet-api包中的。
  • 有了這些前提信息,我們來(lái)說(shuō)下我遇到的問(wèn)題,在這樣的背景下,我采用tomcat7-maven-plugin進(jìn)行啟動(dòng)測(cè)試

以下tomcat:run...命令為tomcat7-maven-plugin的命令,scope為javax.servlet-api包在maven中的scope。

  1. tomcat:run scope=provided:正常啟動(dòng)
  2. tomcat:run scope=compile:?jiǎn)?dòng)失敗
  3. tomcat:run-war scope=provided:正常啟動(dòng)
  4. tomcat:run-war scope=compile:正常啟動(dòng)
  • 詭異了吧,如果是2和4一起啟動(dòng)失敗,那我也沒(méi)什么探索的欲望了,合乎情理,雖然其中還有很多細(xì)節(jié)模棱兩可。
  • 另外提前貼下2報(bào)錯(cuò)的核心信息:
java.lang.ClassCastException: ch.qos.logback.classic.servlet.LogbackServletContainerInitializer cannot be cast to javax.servlet.ServletContainerInitializer
  • 可以明確LogbackServletContainerInitializer是實(shí)現(xiàn)了javax.servlet.ServletContainerInitializer接口的,這邊類型轉(zhuǎn)換失敗只有一個(gè)原因:類加載器不對(duì)?。?!

問(wèn)題排查

先看看這兩個(gè)類的類加載器

寫(xiě)個(gè)Servlet監(jiān)聽(tīng)器,在啟動(dòng)時(shí)打出加載器和jar包信息

public class Callback implements ServletContextListener { public void doCallback() { System.out.println('查看看類加載器 ... '); System.out.println('LogbackServletContainerInitializer = ' LogbackServletContainerInitializer.class.getClassLoader()); System.out.println('ServletContainerInitializer = ' ServletContainerInitializer.class.getClassLoader()); System.out.println('查看加載類所在jar包路徑 ... '); System.out.println('LogbackServletContainerInitializer = ' LogbackServletContainerInitializer.class.getProtectionDomain().getCodeSource().getLocation()); System.out.println('ServletContainerInitializer = ' ServletContainerInitializer.class.getProtectionDomain().getCodeSource().getLocation()); } @Override public void contextInitialized(ServletContextEvent servletContextEvent) { doCallback(); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { doCallback(); }}

在web.xml中配置好,啟動(dòng),發(fā)現(xiàn):
tomcat:run scope=compile啟動(dòng)失敗,無(wú)法打印出類加載信息。。。
tomcat:run scope=provided
tomcat:run-war scope=provided
tomcat:run-war scope=compile
這三個(gè)的類加載信息是一致的,如下:

查看看類加載器 ... LogbackServletContainerInitializer = WebappClassLoader context: delegate: false repositories:----------> Parent Classloader:ClassRealm[plugin>org.apache.tomcat.maven:tomcat7-maven-plugin:2.2, parent: sun.misc.Launcher$AppClassLoader@18b4aac2]ServletContainerInitializer = ClassRealm[plugin>org.apache.tomcat.maven:tomcat7-maven-plugin:2.2, parent: sun.misc.Launcher$AppClassLoader@18b4aac2]查看加載類所在jar包路徑 ... LogbackServletContainerInitializer = file:/Users/coselding/.m2/repository-weidian/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jarServletContainerInitializer = file:/Users/coselding/.m2/repository-weidian/org/apache/tomcat/embed/tomcat-embed-core/7.0.47/tomcat-embed-core-7.0.47.jar

Tomcat類加載器架構(gòu)

tomcat類加載器架構(gòu).jpg

結(jié)合Tomcat的類加載器架構(gòu),ServletContainerInitializer的類加載器ClassRealm應(yīng)該就是對(duì)應(yīng)的Common ClassLoader,而LogbackServletContainerInitializer就是WebappClassLoader,是Common ClassLoader的子加載器。和上面的場(chǎng)景結(jié)合起來(lái)就是,如果javax.servlet-api scope=compile,那么javax.servlet-api這個(gè)包就會(huì)在tomcat/lib下和應(yīng)用WEB-INF/lib下各有一份,加載器分別是Common ClassLoader和WebappClassLoader。
我們知道JavaEE的規(guī)范中在應(yīng)用間依賴隔離作了規(guī)定:***tomcat/lib下和應(yīng)用WEB-INF/lib如果有相同的依賴,WEB-INF/lib是優(yōu)先于tomcat/lib的,這個(gè)邏輯是為了支持tomcat部署多應(yīng)用時(shí)應(yīng)用間依賴隔離,打破了雙親委派原則 ***,如下圖:


WebAppClassLoader加載邏輯.jpg

因此你的WEB-INF/lib目錄下的javax.servlet-api會(huì)被會(huì)在LogbackServletContainerInitializer加載時(shí)加載WebappClassLoader,而Tomcat啟動(dòng)自己加載自己lib目錄下的那份WebappClassLoader,導(dǎo)致了ClassCastException。這個(gè)過(guò)程用圖示如下:


ServletContainerInitializer類加載.jpg

因此LogbackServletContainerInitializer實(shí)現(xiàn)的ServletContainerInitializer接口和tomcat識(shí)別的ServletContainerInitializer不是同一個(gè)類加載器加載的,故報(bào)錯(cuò)。

  • 到這里解決了scope=compile和scope=provided所造成的區(qū)別。
  • 但是很遺憾,場(chǎng)景2由于類加載失敗,程序直接無(wú)法啟動(dòng),我無(wú)法查看其類加載器的情況。

tomcat:run和tomcat:run-war的區(qū)別

我們用ServletContainerInitializer.class.getProtectionDomain().getCodeSource().getLocation()打出類加載所在jar包的路徑,來(lái)確認(rèn)下,tomcat:run-war加載的到底是哪個(gè)類,這段代碼由于是放在webapp中的,如果WEB-INF/lib目錄下存在javax.servlet-api的話應(yīng)該優(yōu)先加載的。

  1. 啟動(dòng)信息分析(tomcat:run-war scope=compile)
查看加載類所在jar包路徑 ... LogbackServletContainerInitializer = file:/Users/coselding/Projects/vweex/test/target/test-1.0.0-SNAPSHOT/WEB-INF/lib/logback-classic-1.2.3.jarServletContainerInitializer = file:/Users/coselding/.m2/repository-weidian/org/apache/tomcat/embed/tomcat-embed-core/7.0.47/tomcat-embed-core-7.0.47.jar

加載的確實(shí)是tomcat內(nèi)部自帶的javax.servlet-api,那我們放在WEB-INF/lib下的javax.servlet-api被忽略了?答案是的!?。∥覀兛锤暾娜罩荆?/p>

七月 24, 2018 3:36:57 下午 org.apache.catalina.loader.WebappClassLoader validateJarFile信息: validateJarFile(/Users/coselding/Projects/vweex/test/target/test-1.0.0-SNAPSHOT/WEB-INF/lib/javax.servlet-api-3.0.1.jar) - jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class查看加載類所在jar包路徑 ... LogbackServletContainerInitializer = file:/Users/coselding/Projects/vweex/test/target/test-1.0.0-SNAPSHOT/WEB-INF/lib/logback-classic-1.2.3.jarServletContainerInitializer = file:/Users/coselding/.m2/repository-weidian/org/apache/tomcat/embed/tomcat-embed-core/7.0.47/tomcat-embed-core-7.0.47.jar

看見(jiàn)了嗎?我們WEB-INF/lib目錄下的jar被忽略了,WebappClassLoader在加載時(shí)做了校驗(yàn),給出了警告,但是tomcat自己仍然會(huì)加載自身的javax.servlet-api,確保程序正常,這也是我們平時(shí)在項(xiàng)目中不太在意這個(gè)細(xì)節(jié),但是程序仍然能正確執(zhí)行的原因

  • 我們先區(qū)分下這兩種啟動(dòng)方式的差別:
  1. tomcat:run scope=compile
    是以你的項(xiàng)目源文件目錄作為執(zhí)行目錄的,不會(huì)在target目錄下生成war文件,如下圖:


    tomcat-run目錄結(jié)構(gòu).png

他的好處是什么呢?這是一個(gè)開(kāi)發(fā)時(shí)工具,你修改代碼會(huì)自動(dòng)進(jìn)行熱部署,避免每次改代碼都需要重新啟動(dòng)!那么我們可以了解下熱部署的原理:深入理解Java類加載器(2):線程上下文類加載器,這是為了開(kāi)發(fā)方便而把類加載過(guò)程復(fù)雜化了,這個(gè)過(guò)程暫時(shí)不做了解,但是可以大致定位是這個(gè)復(fù)雜的類加載過(guò)程中有bug,導(dǎo)致了加載javax.servlet-api時(shí)沒(méi)像正式部署時(shí)WebAppClassLoader正確過(guò)濾。

  1. tomcat:run-war scope=compile
    會(huì)先把你的項(xiàng)目打包成war,再啟動(dòng)tomcat容器加載這個(gè)war,所以tomcat:run-war方式和我們?cè)诎l(fā)布系統(tǒng)打包發(fā)布的流程是類似的,缺點(diǎn)是這種啟動(dòng)方式你更改代碼是不會(huì)運(yùn)行時(shí)生效的,需要重新啟動(dòng),因?yàn)榇a改動(dòng)不會(huì)影響target/{projectName}目錄下的文件,目錄結(jié)構(gòu)如下圖:


    tomcat-run-war目錄結(jié)構(gòu).png

解決方式

主要你保證你的項(xiàng)目依賴中mvn dependency:tree查到的所有servlet-api依賴都是provided,就能從根源上避免這個(gè)問(wèn)題,這里有個(gè)坑:
Servlet2.0依賴坐標(biāo)

<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope></dependency>

Servlet3.0依賴坐標(biāo)

<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope></dependency>

呵呵。。。我們的dubbo中這兩個(gè)包都依賴了,需要全部exclude。。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class
validateJarFile jar not loaded. See Servlet S...
如何查看 JSP 和 Servlet 的版本
jdk6下開(kāi)發(fā)webservice示例
如何進(jìn)行j2sdk和tomcat的安裝及配置
JDK1.6.10和tomcat6.0配置方法
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服