首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

浅析Tomcat防止资料被锁的方式

2013-01-26 
浅析Tomcat防止文件被锁的方式说明:测试应用是Struts 2.3.8自带的struts2-blank.war源码分析对象是Tomcat

浅析Tomcat防止文件被锁的方式

说明:

测试应用是Struts 2.3.8自带的struts2-blank.war

源码分析对象是Tomcat 7.0.23

1. 文件是如何被锁的?

Tomcat会为每个应用创建一个单独的ClassLoader(WebappClassLoader),负责加载应用使用的Java类和资源。
WebappClassLoader是java.net.URLClassLoader的子类,URLClassLoader在加载资源的时候会使用getResource方法去访问资源,如果资源文件在Jar包里,那就会打开到Jar包的URL连接,而URLConnection缺省会打开一个缓存、将创建过连接的内容缓存起来,一旦内容被缓存,那相应的Jar包就会被锁定。

2. 解决方案

针对Windows上文件被锁、不能重部署应用的问题,Tomcat给出了两个解决方案:

?

2.1 从Tomcat 5.0开始,可以在context.xml的Context元素上设置antiJARLocking属性为true;从Tomcat 5.5开始,可以在context.xml的Context元素上设置antiResourceLocking属性为true(说明)

?

但在Tomcat 7.0.23(注释掉server.xml里Server元素下的JreMemoryLeakPreventionListener,这个监听器在后面分析)和Tomcat 6.0.20里用struts2-blank.war测试,只有将antiResourceLocking设置为true,Struts和XWork的Jar包才会在解部署时删除。接下来分析一下这两个属性的工作原理。

?

(1)antiJARLocking

先来看看应用的antiJARLocking属性设置为true时,Tomcat是怎么处理的。
针对antiJARLocking属性的处理集中在WebappClassLoader的getResource和findResourceInternal方法里,主要原理是将包含在Jar包里的资源抽取放到应用的工作目录(work里应用对应的目录)下去。findResourceInternal的主要代码为:

if (antiJARLocking && !(path.endsWith(".class"))) {    byte[] buf = new byte[1024];    File resourceFile = new File        (loaderDir, jarEntry.getName());    if (!resourceFile.exists()) {        Enumeration<JarEntry> 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 {                    // 关闭流、释放资源                }            }        }    }}

把这个属性设置为true之后,部署应用就可以在work\Catalina\localhost\struts2-blank\loader目录下看到被解压的Jar包内容。

antiJARLocking属性在有的时候并不会生效,从WebappClassLoader的getResource和findResource方法逻辑里可以看出一些端倪,在一些情况下(通过对Loader的delegate、searchExternalFirst等
antiLocking的主要代码和主要逻辑为:

    protected void antiLocking() {        // 只针对应用做处理,应用的antiResourceLocking属性设置为true        if ((context instanceof StandardContext)            && ((StandardContext) context).getAntiResourceLocking()) {                       // 获取应用原始的doc base(docBaseFile变量,缺省是webapps下以应用名称为名的目录)            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);            }                       // 获取应用的“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);            }            // 将应用的内容从原始doc base拷贝到java.io.tmpdir下新建的目录中,            // 并将临时目录下的内容作为应用的doc base            ExpandWar.delete(file);            if (ExpandWar.copy(docBaseFile, file)) {                context.setDocBase(file.getAbsolutePath());            }                   }           }

总结一下,就是如果应用的antiResourceLocking属性设置为true,就将应用的doc base移到临时目录下,让Tomca不会占用webapps下的文件。Tomcat里java.io.tmpdir默认指向Tomcat的temp目录。

?

副作用
从上面的分析来看,antiResourceLocking为true有几个副作用:
1) 会延长应用的启动时间,因为多了临时目录的清理和往临时目录拷贝应用内容的操作;
2) 如果不知道这个属性的原理,修改webapps下应用的JSP,那就不会动态重加载到新的页面内容了,因为应用的doc base已经不再在webapps下了;
3) 停止Tomcat的时候,临时目录下实际的doc base会被删掉,代码如下:

    protected synchronized void configureStop() {        ......        Host host = (Host) context.getParent();        String appBase = host.getAppBase();        String docBase = context.getDocBase();        // originalDocBase变量初始为null,只有antiResourceLocking为true时才会赋值        if ((docBase != null) && (originalDocBase != null)) {            File docBaseFile = new File(docBase);            if (!docBaseFile.isAbsolute()) {                docBaseFile = new File(appBase, docBase);            }            // 删除            ExpandWar.delete(docBaseFile, false);        }        ......    }

结合第二条和第三条,如果要修改应用的JSP,那必须将改动同时拷贝到两个目录下(原始doc base和临时目录下的doc base)。
所以Tomcat里这个属性缺省为false。在使用Tomcat 6.0.24之前的版本时,如果要用这个属性解决文件被锁的问题,三思而行。


2.2 从Tomcat 6.0.24开始,可以在server.xml的Server元素下配置JreMemoryLeakPreventionListener的urlCacheProtection属性为true(说明)


这个监听器有诸多属性,其中解决文件被锁的是urlCacheProtection属性。urlCacheProtection的原理很简单,就是针对文件被锁的根本原因进行处理——在Server(Tomcat的顶级容器)初始化之前就将URLConnection的缓存关掉。

// Set the default URL caching policy to not to cacheif (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私有的静态变量defaultUserCaches    } catch (MalformedURLException e) {        log.error(sm.getString("jreLeakListener.jarUrlConnCacheFail"), e);    } catch (IOException e) {        log.error(sm.getString("jreLeakListener.jarUrlConnCacheFail"), e);    }}

这个监听器是缺省配置的,urlCacheProtection属性也缺省为true,所以从Tomcat 6.0.24开始,文件被锁定的问题就不存在了。

?

另外需要注意的是,JreMemoryLeakPreventionListener这个监听器只能设置给Server(Tomcat的顶级容器),所以urlCacheProtection设置为true的话,对所有应用都会生效。