tomcat防cc_浅析Tomcat防止资料被锁的方式
在Windows平臺(tái)的Tomcat上部署應(yīng)用后,應(yīng)用下的個(gè)別文件可能會(huì)被Tomcat鎖住,解部署的時(shí)候刪除不掉那些內(nèi)容,就會(huì)導(dǎo)致無(wú)法重部署。如果解部署刪除不掉被鎖的文件,Tomcat會(huì)在日志中警告說(shuō):
2013-1-9 15:44:09 org.apache.catalina.startup.ExpandWar delete
嚴(yán)重: [D:\tomcat\apache-tomcat-7.0.32\webapps\struts2-blank] could not be completely deleted. The presence of the remaining files may cause problems
被鎖的文件通常是/WEB-INF/lib下的Jar包,又以Struts2和XWork的Jar包為甚。
遇到這個(gè)問(wèn)題,最簡(jiǎn)單但最折騰的做法就是停止Tomcat、手動(dòng)刪除webapps目錄下殘留的文件,再重啟、重新部署應(yīng)用。
說(shuō)明:
測(cè)試應(yīng)用是Struts 2.3.8自帶的struts2-blank.war
源碼分析對(duì)象是Tomcat 7.0.23
1. 文件是如何被鎖的?
Tomcat會(huì)為每個(gè)應(yīng)用創(chuàng)建一個(gè)單獨(dú)的ClassLoader(WebappClassLoader),負(fù)責(zé)加載應(yīng)用使用的Java類和資源。
WebappClassLoader是java.net.URLClassLoader的子類,URLClassLoader在加載資源的時(shí)候會(huì)使用getResource方法去訪問(wèn)資源,如果資源文件在Jar包里,那就會(huì)打開(kāi)到Jar包的URL連接,而URLConnection缺省會(huì)打開(kāi)一個(gè)緩存、將創(chuàng)建過(guò)連接的內(nèi)容緩存起來(lái),一旦內(nèi)容被緩存,那相應(yīng)的Jar包就會(huì)被鎖定。
2. 解決方案
針對(duì)Windows上文件被鎖、不能重部署應(yīng)用的問(wèn)題,Tomcat給出了兩個(gè)解決方案:
2.1 從Tomcat 5.0開(kāi)始,可以在context.xml的Context元素上設(shè)置antiJARLocking屬性為true;從Tomcat 5.5開(kāi)始,可以在context.xml的Context元素上設(shè)置antiResourceLocking屬性為true(說(shuō)明)
但在Tomcat 7.0.23(注釋掉server.xml里Server元素下的JreMemoryLeakPreventionListener,這個(gè)監(jiān)聽(tīng)器在后面分析)和Tomcat 6.0.20里用struts2-blank.war測(cè)試,只有將antiResourceLocking設(shè)置為true,Struts和XWork的Jar包才會(huì)在解部署時(shí)刪除。接下來(lái)分析一下這兩個(gè)屬性的工作原理。
(1)antiJARLocking
先來(lái)看看應(yīng)用的antiJARLocking屬性設(shè)置為true時(shí),Tomcat是怎么處理的。
針對(duì)antiJARLocking屬性的處理集中在WebappClassLoader的getResource和findResourceInternal方法里,主要原理是將包含在Jar包里的資源抽取放到應(yīng)用的工作目錄(work里應(yīng)用對(duì)應(yīng)的目錄)下去。findResourceInternal的主要代碼為:
if (antiJARLocking && !(path.endsWith(".class"))) {
byte[] buf = new byte[1024];
File resourceFile = new File
(loaderDir, jarEntry.getName());
if (!resourceFile.exists()) {
Enumeration entries =
jarFiles[i].entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry2 = entries.nextElement();
if (!(jarEntry2.isDirectory())
&& (!jarEntry2.getName().endsWith
(".class"))) {
resourceFile = new File
(loaderDir, jarEntry2.getName());
......
File parentFile = resourceFile.getParentFile();
if (!parentFile.mkdirs() && !parentFile.exists()) {
// Ignore the error (like the IOExceptions below)
}
FileOutputStream os = null;
InputStream is = null;
try {
is = jarFiles[i].getInputStream
(jarEntry2);
os = new FileOutputStream
(resourceFile);
while (true) {
int n = is.read(buf);
if (n <= 0) {
break;
}
os.write(buf, 0, n);
}
resourceFile.setLastModified(
jarEntry2.getTime());
} catch (IOException e) {
// Ignore
} finally {
// 關(guān)閉流、釋放資源
}
}
}
}
}
把這個(gè)屬性設(shè)置為true之后,部署應(yīng)用就可以在work\Catalina\localhost\struts2-blank\loader目錄下看到被解壓的Jar包內(nèi)容。
antiJARLocking屬性在有的時(shí)候并不會(huì)生效,從WebappClassLoader的getResource和findResource方法邏輯里可以看出一些端倪,在一些情況下(通過(guò)對(duì)Loader的delegate、searchExternalFirst等相關(guān)屬性進(jìn)行配置),資源的獲取并不是WebappClassLoader去做的,而是其父加載器的getResource方法或父類的findResource方法去做的,WebappClassLoader的父類是URLClassLoader、父加載器是URLClassLoader實(shí)例。
(2)antiResourceLocking
當(dāng)antiResourceLocking設(shè)置為true的時(shí)候,Tomcat不會(huì)鎖定應(yīng)用下的任何文件。那Tomcat是怎么做到這一點(diǎn)的呢?
在Tomcat的架構(gòu)里,應(yīng)用也是一個(gè)級(jí)別的容器,對(duì)應(yīng)的接口是Context;各級(jí)容器本身都具備生命周期,而且配置了多個(gè)生命周期監(jiān)聽(tīng)器來(lái)監(jiān)聽(tīng)容器不同的生命周期過(guò)程。Tomcat在初始化的時(shí)候,給Context增加了一個(gè)生命周期監(jiān)聽(tīng)器org.apache.catalina.startup.ContextConfig;然后在Context真正開(kāi)始啟動(dòng)之前,會(huì)有一個(gè)BEFORE_START_EVENT狀態(tài),ContextConfig監(jiān)聽(tīng)到這個(gè)狀態(tài)的事件后,就會(huì)針對(duì)antiResourceLocking進(jìn)行處理。
antiLocking的主要代碼和主要邏輯為:
protected void antiLocking() {
// 只針對(duì)應(yīng)用做處理,應(yīng)用的antiResourceLocking屬性設(shè)置為true
if ((context instanceof StandardContext)
&& ((StandardContext) context).getAntiResourceLocking()) {
// 獲取應(yīng)用原始的doc base(docBaseFile變量,缺省是webapps下以應(yīng)用名稱為名的目錄)
Host host = (Host) context.getParent();
String appBase = host.getAppBase();
String docBase = context.getDocBase();
if (docBase == null)
return;
if (originalDocBase == null) {
originalDocBase = docBase;
} else {
docBase = originalDocBase;
}
File docBaseFile = new File(docBase);
if (!docBaseFile.isAbsolute()) {
File file = new File(appBase);
if (!file.isAbsolute()) {
file = new File(getBaseDir(), appBase);
}
docBaseFile = new File(file, docBase);
}
// 獲取應(yīng)用的“path + version”,作為新的doc base,version通常是空
String path = context.getPath();
if (path == null) {
return;
}
ContextName cn = new ContextName(path, context.getWebappVersion());
docBase = cn.getBaseName();
// 在java.io.tmpdir下新建目錄
File file = null;
if (originalDocBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
file = new File(System.getProperty("java.io.tmpdir"),
deploymentCount++ + "-" + docBase + ".war");
} else {
file = new File(System.getProperty("java.io.tmpdir"),
deploymentCount++ + "-" + docBase);
}
// 將應(yīng)用的內(nèi)容從原始doc base拷貝到j(luò)ava.io.tmpdir下新建的目錄中,
// 并將臨時(shí)目錄下的內(nèi)容作為應(yīng)用的doc base
ExpandWar.delete(file);
if (ExpandWar.copy(docBaseFile, file)) {
context.setDocBase(file.getAbsolutePath());
}
}
}
總結(jié)一下,就是如果應(yīng)用的antiResourceLocking屬性設(shè)置為true,就將應(yīng)用的doc base移到臨時(shí)目錄下,讓Tomca不會(huì)占用webapps下的文件。Tomcat里java.io.tmpdir默認(rèn)指向Tomcat的temp目錄。
副作用
從上面的分析來(lái)看,antiResourceLocking為true有幾個(gè)副作用:
1) 會(huì)延長(zhǎng)應(yīng)用的啟動(dòng)時(shí)間,因?yàn)槎嗔伺R時(shí)目錄的清理和往臨時(shí)目錄拷貝應(yīng)用內(nèi)容的操作;
2) 如果不知道這個(gè)屬性的原理,修改webapps下應(yīng)用的JSP,那就不會(huì)動(dòng)態(tài)重加載到新的頁(yè)面內(nèi)容了,因?yàn)閼?yīng)用的doc base已經(jīng)不再在webapps下了;
3) 停止Tomcat的時(shí)候,臨時(shí)目錄下實(shí)際的doc base會(huì)被刪掉,代碼如下:
protected synchronized void configureStop() {
......
Host host = (Host) context.getParent();
String appBase = host.getAppBase();
String docBase = context.getDocBase();
// originalDocBase變量初始為null,只有antiResourceLocking為true時(shí)才會(huì)賦值
if ((docBase != null) && (originalDocBase != null)) {
File docBaseFile = new File(docBase);
if (!docBaseFile.isAbsolute()) {
docBaseFile = new File(appBase, docBase);
}
// 刪除
ExpandWar.delete(docBaseFile, false);
}
......
}
結(jié)合第二條和第三條,如果要修改應(yīng)用的JSP,那必須將改動(dòng)同時(shí)拷貝到兩個(gè)目錄下(原始doc base和臨時(shí)目錄下的doc base)。
所以Tomcat里這個(gè)屬性缺省為false。在使用Tomcat 6.0.24之前的版本時(shí),如果要用這個(gè)屬性解決文件被鎖的問(wèn)題,三思而行。
2.2 從Tomcat 6.0.24開(kāi)始,可以在server.xml的Server元素下配置JreMemoryLeakPreventionListener的urlCacheProtection屬性為true(說(shuō)明)
這個(gè)監(jiān)聽(tīng)器有諸多屬性,其中解決文件被鎖的是urlCacheProtection屬性。urlCacheProtection的原理很簡(jiǎn)單,就是針對(duì)文件被鎖的根本原因進(jìn)行處理——在Server(Tomcat的頂級(jí)容器)初始化之前就將URLConnection的緩存關(guān)掉。
// Set the default URL caching policy to not to cache
if (urlCacheProtection) {
try {
// Doesn't matter that this JAR doesn't exist - just as
// long as the URL is well-formed
URL url = new URL("jar:file://dummy.jar!/");
URLConnection uConn = url.openConnection();
uConn.setDefaultUseCaches(false); // 修改URLConnection私有的靜態(tài)變量defaultUserCaches
} catch (MalformedURLException e) {
log.error(sm.getString("jreLeakListener.jarUrlConnCacheFail"), e);
} catch (IOException e) {
log.error(sm.getString("jreLeakListener.jarUrlConnCacheFail"), e);
}
}
這個(gè)監(jiān)聽(tīng)器是缺省配置的,urlCacheProtection屬性也缺省為true,所以從Tomcat 6.0.24開(kāi)始,文件被鎖定的問(wèn)題就不存在了。
另外需要注意的是,JreMemoryLeakPreventionListener這個(gè)監(jiān)聽(tīng)器只能設(shè)置給Server(Tomcat的頂級(jí)容器),所以u(píng)rlCacheProtection設(shè)置為true的話,對(duì)所有應(yīng)用都會(huì)生效。
總結(jié)
以上是生活随笔為你收集整理的tomcat防cc_浅析Tomcat防止资料被锁的方式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: c语言商品货架管理_汽配仓库布局及管理
- 下一篇: bootstrap 富文本_入坑吗?说说