Tomcat7自动加载类及检测文件变动原理

在一般的web应用开发里通常会使用开发工具(如Eclipse、IntelJ)集成tomcat,这样可以将web工程项目直接发布到tomcat中,然后一键启动。经常遇到的一种情况是直接修改一个类的源文件,此时开发工具会直接将编译后的class文件发布到tomcat的web工程里,但如果tomcat没有配置应用的自动加载功能的话,当前JVM中运行的class还是源文件修改之前编译好的class文件。可以重启tomcat来加载新的class文件,但这样做需要再手工点击一次【restart】,为了能够在应用中即时看到java文件修改之后的执行情况,可以在tomcat中将应用配置成自动加载模式,其配置很简单,只要在配置文件的Context节点中加上一个reloadable属性为true即可,示例如下:

Xml代码   收藏代码
  1. <Context path="/HelloWorld" docBase="C:/apps/apache-tomcat/DeployedApps/HelloWorld" reloadable="true"/>  

如果你的开发工具已经集成了tomcat的话应该会有一个操作界面配置来代替手工添加文件信息,如Eclipse中是如下界面来配置的:


此时需要把【Auto reloading enabled】前面的复选框钩上。其背后的原理实际也是在server.xml文件中加上Context节点的描述:

Xml代码   收藏代码
  1. <Context docBase="test" path="/test" reloadable="true"/>  

这样Tomcat就会监控所配置的web应用实际路径下的/WEB-INF/classes和/WEB-INF/lib两个目录下文件的变动,如果发生变更tomcat将会自动重启该应用。 

 

熟悉Tomcat的人应该都用过这个功能,就不再详述它的配置步骤了。我感兴趣的是这个自动加载功能在Tomcat7中是怎么实现的。

 

前面的文章中曾经讲过Tomcat7在启动完成后会有一个后台线程ContainerBackgroundProcessor[StandardEngine[Catalina]],这个线程将会定时(默认为10秒)执行Engine、Host、Context、Wrapper各容器组件及与它们相关的其它组件的backgroundProcess方法,这段代码在所有容器组件的父类org.apache.catalina.core.ContainerBase类的backgroundProcess方法中: 

Java代码   收藏代码
  1. public void backgroundProcess() {  
  2.       
  3.     if (!getState().isAvailable())  
  4.         return;  
  5.   
  6.     if (cluster != null) {  
  7.         try {  
  8.             cluster.backgroundProcess();  
  9.         } catch (Exception e) {  
  10.             log.warn(sm.getString("containerBase.backgroundProcess.cluster", cluster), e);                  
  11.         }  
  12.     }  
  13.     if (loader != null) {  
  14.         try {  
  15.             loader.backgroundProcess();  
  16.         } catch (Exception e) {  
  17.             log.warn(sm.getString("containerBase.backgroundProcess.loader", loader), e);                  
  18.         }  
  19.     }  
  20.     if (manager != null) {  
  21.         try {  
  22.             manager.backgroundProcess();  
  23.         } catch (Exception e) {  
  24.             log.warn(sm.getString("containerBase.backgroundProcess.manager", manager), e);                  
  25.         }  
  26.     }  
  27.     Realm realm = getRealmInternal();  
  28.     if (realm != null) {  
  29.         try {  
  30.             realm.backgroundProcess();  
  31.         } catch (Exception e) {  
  32.             log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);                  
  33.         }  
  34.     }  
  35.     Valve current = pipeline.getFirst();  
  36.     while (current != null) {  
  37.         try {  
  38.             current.backgroundProcess();  
  39.         } catch (Exception e) {  
  40.             log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);                  
  41.         }  
  42.         current = current.getNext();  
  43.     }  
  44.     fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);  
  45. }  

与自动加载类相关的代码在loader的backgroundProcess方法的调用时。每一个StandardContext会关联一个loader变量,该变量的初始化代码在org.apache.catalina.core.StandardContext类的startInternal方法中的这段代码:

Java代码   收藏代码
  1. if (getLoader() == null) {  
  2.     WebappLoader webappLoader = new WebappLoader(getParentClassLoader());  
  3.     webappLoader.setDelegate(getDelegate());  
  4.     setLoader(webappLoader);  
  5. }  

所以上面的loader.backgroundProcess()方法的调用将会执行org.apache.catalina.loader.WebappLoader类的backgroundProcess方法:

Java代码   收藏代码
  1. public void backgroundProcess() {  
  2.     if (reloadable && modified()) {  
  3.         try {  
  4.             Thread.currentThread().setContextClassLoader  
  5.                 (WebappLoader.class.getClassLoader());  
  6.             if (container instanceof StandardContext) {  
  7.                 ((StandardContext) container).reload();  
  8.             }  
  9.         } finally {  
  10.             if (container.getLoader() != null) {  
  11.                 Thread.currentThread().setContextClassLoader  
  12.                     (container.getLoader().getClassLoader());  
  13.             }  
  14.         }  
  15.     } else {  
  16.         closeJARs(false);  
  17.     }  
  18. }  

其中reloadable变量的值就是本文开始提到的配置文件的Context节点的reloadable属性的值,当它为true并且modified()方法返回也是true时就会执行StandardContext的reload方法:

Java代码   收藏代码
  1. public synchronized void reload() {  
  2.   
  3.     // Validate our current component state  
  4.     if (!getState().isAvailable())  
  5.         throw new IllegalStateException  
  6.             (sm.getString("standardContext.notStarted", getName()));  
  7.   
  8.     if(log.isInfoEnabled())  
  9.         log.info(sm.getString("standardContext.reloadingStarted",  
  10.                 getName()));  
  11.   
  12.     // Stop accepting requests temporarily.  
  13.     setPaused(true);  
  14.   
  15.     try {  
  16.         stop();  
  17.     } catch (LifecycleException e) {  
  18.         log.error(  
  19.             sm.getString("standardContext.stoppingContext", getName()), e);  
  20.     }  
  21.   
  22.     try {  
  23.         start();  
  24.     } catch (LifecycleException e) {  
  25.         log.error(  
  26.             sm.getString("standardContext.startingContext", getName()), e);  
  27.     }  
  28.   
  29.     setPaused(false);  
  30.   
  31.     if(log.isInfoEnabled())  
  32.         log.info(sm.getString("standardContext.reloadingCompleted",  
  33.                 getName()));  
  34.   
  35. }  

reload方法中将先执行stop方法将原有的该web应用停掉,再调用start方法启动该Context,start方法的分析前文已经说过,stop方法可以参照start方法一样分析,不再赘述。

 

这里重点要说的是上面提到的监控文件变动的方法modified,只有它返回true才会导致应用自动加载。看下该方法的实现: 

Java代码   收藏代码
  1. public boolean modified() {  
  2.     return classLoader != null ? classLoader.modified() : false ;  
  3. }  

可以看到这里面实际调用的是WebappLoader的实例变量classLoader的modified方法来判断的,下文就详细分析这个modified方法的实现。

 

先简要说一下Tomcat中的加载器。在Tomcat7中每一个web应用对应一个Context节点,这个节点在JVM中就对应一个org.apache.catalina.core.StandardContext对象,而每一个StandardContext对象内部都有一个加载器实例变量(即其父类org.apache.catalina.core.ContainerBase的loader实例变量),前面已经看到这个loader变量实际上是org.apache.catalina.loader.WebappLoader对象。而每一个WebappLoader对象内部关联了一个classLoader变量(就这这个类的定义中,可以看到该变量的类型是org.apache.catalina.loader.WebappClassLoader)。

 

在Tomcat7的源码中给出了6个web应用:


所以在Tomcat启动完成之后理论上应该有6个StandardContext对象,6个WebappLoader对象,6个WebappClassLoader对象。用jvisualvm观察实际情况也证实了上面的判断:


StandardContext实例数

 


WebappLoader实例数



WebappClassLoader实例数

 

上面讲过了WebappLoader的初始化代码,接下来讲一下WebappClassLoader的对象初始化代码。同样还是在StandardContext类的startInternal方法中,有这么两段代码: 

Java代码   收藏代码
  1. if (getLoader() == null) {  
  2.     WebappLoader webappLoader = new WebappLoader(getParentClassLoader());  
  3.     webappLoader.setDelegate(getDelegate());  
  4.     setLoader(webappLoader);  
  5. }  

这一段上面已经说过是WebappLoader的初始化代码。

 

Java代码   收藏代码
  1. try {  
  2.   
  3.     if (ok) {  
  4.           
  5.         // Start our subordinate components, if any  
  6.         if ((loader != null) && (loader instanceof Lifecycle))  
  7.             ((Lifecycle) loader).start();  

这一段与WebappLoader的对象相关,执行的就是WebappLoader类的start方法,因为WebappLoader继承自LifecycleBase类,所以调用它的start方法最终将会执行该类自定义的startInternal方法,看下startInternal方法中的这段代码: 

Java代码   收藏代码
  1. classLoader = createClassLoader();  
  2. classLoader.setResources(container.getResources());  
  3. classLoader.setDelegate(this.delegate);  
  4. classLoader.setSearchExternalFirst(searchExternalFirst);  
  5. if (container instanceof StandardContext) {  
  6.     classLoader.setAntiJARLocking(  
  7.             ((StandardContext) container).getAntiJARLocking());  
  8.     classLoader.setClearReferencesStatic(  
  9.             ((StandardContext) container).getClearReferencesStatic());  
  10.     classLoader.setClearReferencesStopThreads(  
  11.             ((StandardContext) container).getClearReferencesStopThreads());  
  12.     classLoader.setClearReferencesStopTimerThreads(  
  13.             ((StandardContext) container).getClearReferencesStopTimerThreads());  
  14.     classLoader.setClearReferencesHttpClientKeepAliveThread(  
  15.             ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());  
  16. }  
  17.   
  18. for (int i = 0; i < repositories.length; i++) {  
  19.     classLoader.addRepository(repositories[i]);  
  20. }  
  21.   
  22. // Configure our repositories  
  23. setRepositories();  
  24. setClassPath();  
  25.   
  26. setPermissions();  
  27.   
  28. ((Lifecycle) classLoader).start();  

一开始调用了createClassLoader方法:

Java代码   收藏代码
  1. /** 
  2.  * Create associated classLoader. 
  3.  */  
  4. private WebappClassLoader createClassLoader()  
  5.     throws Exception {  
  6.   
  7.     Class<?> clazz = Class.forName(loaderClass);  
  8.     WebappClassLoader classLoader = null;  
  9.   
  10.     if (parentClassLoader == null) {  
  11.         parentClassLoader = container.getParentClassLoader();  
  12.     }  
  13.     Class<?>[] argTypes = { ClassLoader.class };  
  14.     Object[] args = { parentClassLoader };  
  15.     Constructor<?> constr = clazz.getConstructor(argTypes);  
  16.     classLoader = (WebappClassLoader) constr.newInstance(args);  
  17.   
  18.     return classLoader;  
  19.   
  20. }  

可以看出这里通过反射实例化了一个WebappClassLoader对象。

 

回到文中上面提的问题,看下WebappClassLoader的modified方法代码: 

Java代码   收藏代码
  1. /** 
  2.  * Have one or more classes or resources been modified so that a reload 
  3.  * is appropriate? 
  4.  */  
  5. public boolean modified() {  
  6.   
  7.     if (log.isDebugEnabled())  
  8.         log.debug("modified()");  
  9.   
  10.     // Checking for modified loaded resources  
  11.     int length = paths.length;  
  12.   
  13.     // A rare race condition can occur in the updates of the two arrays  
  14.     // It's totally ok if the latest class added is not checked (it will  
  15.     // be checked the next time  
  16.     int length2 = lastModifiedDates.length;  
  17.     if (length > length2)  
  18.         length = length2;  
  19.   
  20.     for (int i = 0; i < length; i++) {  
  21.         try {  
  22.             long lastModified =  
  23.                 ((ResourceAttributes) resources.getAttributes(paths[i]))  
  24.                 .getLastModified();  
  25.             if (lastModified != lastModifiedDates[i]) {  
  26.                 if( log.isDebugEnabled() )  
  27.                     log.debug("  Resource '" + paths[i]  
  28.                               + "' was modified; Date is now: "  
  29.                               + new java.util.Date(lastModified) + " Was: "  
  30.                               + new java.util.Date(lastModifiedDates[i]));  
  31.                 return (true);  
  32.             }  
  33.         } catch (NamingException e) {  
  34.             log.error("    Resource '" + paths[i] + "' is missing");  
  35.             return (true);  
  36.         }  
  37.     }  
  38.   
  39.     length = jarNames.length;  
  40.   
  41.     // Check if JARs have been added or removed  
  42.     if (getJarPath() != null) {  
  43.   
  44.         try {  
  45.             NamingEnumeration<Binding> enumeration =  
  46.                 resources.listBindings(getJarPath());  
  47.             int i = 0;  
  48.             while (enumeration.hasMoreElements() && (i < length)) {  
  49.                 NameClassPair ncPair = enumeration.nextElement();  
  50.                 String name = ncPair.getName();  
  51.                 // Ignore non JARs present in the lib folder  
  52.                 if (!name.endsWith(".jar"))  
  53.                     continue;  
  54.                 if (!name.equals(jarNames[i])) {  
  55.                     // Missing JAR  
  56.                     log.info("    Additional JARs have been added : '"  
  57.                              + name + "'");  
  58.                     return (true);  
  59.                 }  
  60.                 i++;  
  61.             }  
  62.             if (enumeration.hasMoreElements()) {  
  63.                 while (enumeration.hasMoreElements()) {  
  64.                     NameClassPair ncPair = enumeration.nextElement();  
  65.                     String name = ncPair.getName();  
  66.                     // Additional non-JAR files are allowed  
  67.                     if (name.endsWith(".jar")) {  
  68.                         // There was more JARs  
  69.                         log.info("    Additional JARs have been added");  
  70.                         return (true);  
  71.                     }  
  72.                 }  
  73.             } else if (i < jarNames.length) {  
  74.                 // There was less JARs  
  75.                 log.info("    Additional JARs have been added");  
  76.                 return (true);  
  77.             }  
  78.         } catch (NamingException e) {  
  79.             if (log.isDebugEnabled())  
  80.                 log.debug("    Failed tracking modifications of '"  
  81.                     + getJarPath() + "'");  
  82.         } catch (ClassCastException e) {  
  83.             log.error("    Failed tracking modifications of '"  
  84.                       + getJarPath() + "' : " + e.getMessage());  
  85.         }  
  86.   
  87.     }  
  88.   
  89.     // No classes have been modified  
  90.     return (false);  
  91.   
  92. }  

这段代码从总体上看共分成两部分,第一部分检查web应用中的class文件是否有变动,根据class文件的最近修改时间来比较,如果有不同则直接返回true,如果class文件被删除也返回true。第二部分检查web应用中的jar文件是否有变动,如果有同样返回true。稍有编程经验的人对于以上比较代码都容易理解,但对这些变量的值,特别是里面比较时经常用到WebappClassLoader类的实例变量的值是在什么地方赋值的会比较困惑,这里就这点做一下说明。

 

以class文件变动的比较为例,比较的关键代码是: 

Java代码   收藏代码
  1. long lastModified =  
  2.                     ((ResourceAttributes) resources.getAttributes(paths[i]))  
  3.                     .getLastModified();  
  4.                 if (lastModified != lastModifiedDates[i]) {  

即从WebappClassLoader的实例变量resources中取出文件当前的最近修改时间,与WebappClassLoader原来缓存的该文件的最近修改时间做比较。

 

关于resources.getAttributes方法,看下resources的声明类型javax.naming.directory.DirContext可知实际这里面执行的是通常的JNDI查询一个属性的方法(如果对JNDI不熟悉请看一下JNDI的相关文档大致了解一下,这里不再做单独介绍),所以有必要把resources变量究竟是何对象拎出来说一下。

在上面看WebappLoader的startInternal方法的源码里createClassLoader()方法调用并赋值给classLoader下一行: 

Java代码   收藏代码
  1. classLoader.setResources(container.getResources());  

这里设置的resources就是上面用到的resources变量,可以看到它实际是WebappLoader所关联容器的实例变量resources。按前面的描述所关联的容器及StandardContext,再来看看StandardContext中resources是怎么赋值的。

 

还是在StandardContext的startInternal方法中,开头部分有这段代码: 

Java代码   收藏代码
  1. // Add missing components as necessary  
  2. if (webappResources == null) {   // (1) Required by Loader  
  3.     if (log.isDebugEnabled())  
  4.         log.debug("Configuring default Resources");  
  5.     try {  
  6.         if ((getDocBase() != null) && (getDocBase().endsWith(".war")) &&  
  7.                 (!(new File(getBasePath())).isDirectory()))  
  8.             setResources(new WARDirContext());  
  9.         else  
  10.             setResources(new FileDirContext());  
  11.     } catch (IllegalArgumentException e) {  
  12.         log.error("Error initializing resources: " + e.getMessage());  
  13.         ok = false;  
  14.     }  
  15. }  
  16. if (ok) {  
  17.     if (!resourcesStart()) {  
  18.         log.error( "Error in resourceStart()");  
  19.         ok = false;  
  20.     }  
  21. }  

因为默认的应用是不是war包发布,而是以目录形式发布的所以会执行setResources(new FileDirContext())方法。这里稍微曲折的地方是setResources里实际只是给StandardContext的webappResources变量赋值,而StandardContext的resources变量赋为null,在上面源码中的最后resourcesStart方法的调用中才会给resources赋值。看下resourcesStart方法:

Java代码   收藏代码
  1. public boolean resourcesStart() {  
  2.   
  3.     boolean ok = true;  
  4.   
  5.     Hashtable<String, String> env = new Hashtable<String, String>();  
  6.     if (getParent() != null)  
  7.         env.put(ProxyDirContext.HOST, getParent().getName());  
  8.     env.put(ProxyDirContext.CONTEXT, getName());  
  9.   
  10.     try {  
  11.         ProxyDirContext proxyDirContext =  
  12.             new ProxyDirContext(env, webappResources);  
  13.         if (webappResources instanceof FileDirContext) {  
  14.             filesystemBased = true;  
  15.             ((FileDirContext) webappResources).setAllowLinking  
  16.                 (isAllowLinking());  
  17.         }  
  18.         if (webappResources instanceof BaseDirContext) {  
  19.             ((BaseDirContext) webappResources).setDocBase(getBasePath());  
  20.             ((BaseDirContext) webappResources).setCached  
  21.                 (isCachingAllowed());  
  22.             ((BaseDirContext) webappResources).setCacheTTL(getCacheTTL());  
  23.             ((BaseDirContext) webappResources).setCacheMaxSize  
  24.                 (getCacheMaxSize());  
  25.             ((BaseDirContext) webappResources).allocate();  
  26.             // Alias support  
  27.             ((BaseDirContext) webappResources).setAliases(getAliases());  
  28.               
  29.             if (effectiveMajorVersion >=3 && addWebinfClassesResources) {  
  30.                 try {  
  31.                     DirContext webInfCtx =  
  32.                         (DirContext) webappResources.lookup(  
  33.                                 "/WEB-INF/classes");  
  34.                     // Do the lookup to make sure it exists  
  35.                     webInfCtx.lookup("META-INF/resources");  
  36.                     ((BaseDirContext) webappResources).addAltDirContext(  
  37.                             webInfCtx);  
  38.                 } catch (NamingException e) {  
  39.                     // Doesn't exist - ignore and carry on  
  40.                 }  
  41.             }  
  42.         }  
  43.         // Register the cache in JMX  
  44.         if (isCachingAllowed()) {  
  45.             String contextName = getName();  
  46.             if (!contextName.startsWith("/")) {  
  47.                 contextName = "/" + contextName;  
  48.             }  
  49.             ObjectName resourcesName =   
  50.                 new ObjectName(this.getDomain() + ":type=Cache,host="   
  51.                                + getHostname() + ",context=" + contextName);  
  52.             Registry.getRegistry(nullnull).registerComponent  
  53.                 (proxyDirContext.getCache(), resourcesName, null);  
  54.         }  
  55.         this.resources = proxyDirContext;  
  56.     } catch (Throwable t) {  
  57.         ExceptionUtils.handleThrowable(t);  
  58.         log.error(sm.getString("standardContext.resourcesStart"), t);  
  59.         ok = false;  
  60.     }  
  61.   
  62.     return (ok);  
  63.   
  64. }  

可以看出resources赋的是proxyDirContext对象,而proxyDirContext是一个代理对象,代理的就是webappResources,按上面的描述即org.apache.naming.resources.FileDirContext。

 

org.apache.naming.resources.FileDirContext继承自抽象父类org.apache.naming.resources.BaseDirContext,而BaseDirContext又实现了javax.naming.directory.DirContext接口。所以JNDI操作中的lookup、bind、getAttributes、rebind、search等方法都已经在这两个类中实现了。当然里面还有JNDI规范之外的方法如list等。

这里就看下前面看到的getAttributes方法的调用,在BaseDirContext类中所有的getAttributes方法最终都会调用抽象方法doGetAttributes来返回查询属性的结果,这个方法在FileDirContext的定义如下:

Java代码   收藏代码
  1. protected Attributes doGetAttributes(String name, String[] attrIds)  
  2.     throws NamingException {  
  3.   
  4.     // Building attribute list  
  5.     File file = file(name);  
  6.   
  7.     if (file == null)  
  8.         return null;  
  9.   
  10.     return new FileResourceAttributes(file);  
  11.   
  12. }  

可以看到内部执行了file方法:

Java代码   收藏代码
  1. /** 
  2.  * Return a File object representing the specified normalized 
  3.  * context-relative path if it exists and is readable.  Otherwise, 
  4.  * return <code>null</code>. 
  5.  * 
  6.  * @param name Normalized context-relative path (with leading '/') 
  7.  */  
  8. protected File file(String name) {  
  9.   
  10.     File file = new File(base, name);  
  11.     if (file.exists() && file.canRead()) {  
  12.   
  13.         if (allowLinking)  
  14.             return file;  
  15.           
  16.         // Check that this file belongs to our root path  
  17.         String canPath = null;  
  18.         try {  
  19.             canPath = file.getCanonicalPath();  
  20.         } catch (IOException e) {  
  21.             // Ignore  
  22.         }  
  23.         if (canPath == null)  
  24.             return null;  
  25.   
  26.         // Check to see if going outside of the web application root  
  27.         if (!canPath.startsWith(absoluteBase)) {  
  28.             return null;  
  29.         }  
  30.   
  31.         // Case sensitivity check - this is now always done  
  32.         String fileAbsPath = file.getAbsolutePath();  
  33.         if (fileAbsPath.endsWith("."))  
  34.             fileAbsPath = fileAbsPath + "/";  
  35.         String absPath = normalize(fileAbsPath);  
  36.         canPath = normalize(canPath);  
  37.         if ((absoluteBase.length() < absPath.length())  
  38.             && (absoluteBase.length() < canPath.length())) {  
  39.             absPath = absPath.substring(absoluteBase.length() + 1);  
  40.             if (absPath == null)  
  41.                 return null;  
  42.             if (absPath.equals(""))  
  43.                 absPath = "/";  
  44.             canPath = canPath.substring(absoluteBase.length() + 1);  
  45.             if (canPath.equals(""))  
  46.                 canPath = "/";  
  47.             if (!canPath.equals(absPath))  
  48.                 return null;  
  49.         }  
  50.   
  51.     } else {  
  52.         return null;  
  53.     }  
  54.     return file;  
  55.   
  56. }  

了解java的文件操作的人这段代码就很容易理解了,实际就是根据传入的文件名查找目录下是否存在该文件,如果存在则返回包装了的文件属性对象FileResourceAttributes。FileResourceAttributes类实际是对java.io.File类做了一层包装,如getLastModified方法实际调用的是File类的lastModified方法返回。

 

Java代码   收藏代码
  1. long lastModified =  
  2.                     ((ResourceAttributes) resources.getAttributes(paths[i]))  
  3.                     .getLastModified();  
  4.                 if (lastModified != lastModifiedDates[i]) {  

以上分析了上面这段代码中((ResourceAttributes) resources.getAttributes(paths[i])).getLastModified()这部分,但两个内置变量paths和lastModifiedDates值究竟什么时候赋的呢?

这个简要说一下WebappClassLoader这个自定义类加载器的用法,在Tomcat中所有web应用内WEB-INF\classes目录下的class文件都是用这个类加载器来加载的,一般的自定义加载器都是覆写ClassLoader的findClass方法,这里也不例外。WebappClassLoader覆盖的是URLClassLoader类的findClass方法,而在这个方法内部最终会调用findResourceInternal(String name, String path)方法:


该方法代码段较长,为不偏离主题,摘出本文描述相关的代码段:

Java代码   收藏代码
  1. // Register the full path for modification checking  
  2. // Note: Only syncing on a 'constant' object is needed  
  3. synchronized (allPermission) {  
  4.   
  5.     int j;  
  6.   
  7.     long[] result2 =  
  8.         new long[lastModifiedDates.length + 1];  
  9.     for (j = 0; j < lastModifiedDates.length; j++) {  
  10.         result2[j] = lastModifiedDates[j];  
  11.     }  
  12.     result2[lastModifiedDates.length] = entry.lastModified;  
  13.     lastModifiedDates = result2;  
  14.   
  15.     String[] result = new String[paths.length + 1];  
  16.     for (j = 0; j < paths.length; j++) {  
  17.         result[j] = paths[j];  
  18.     }  
  19.     result[paths.length] = fullPath;  
  20.     paths = result;  
  21.   
  22. }  

这里可以看到在加载一个新的class文件时会给WebappClassLoader的实例变量lastModifiedDates和paths数组添加元素。这里就解答了上面提到的文件变更比较代码的疑问。要说明的是在tomcat启动后web应用中所有的class文件并不是全部加载的,而是配置在web.xml中描述的需要与应用一起加载的才会立即加载,否则只有到该类首次使用时才会由类加载器加载。

 

关于Tomcat的自定义类加载器是一个很有意思的话题,可说的地方很多,后面会专文另述。而关于jar包文件变动的比较代码同class文件比较的类似,同样是取出当前web应用的WEB-INF\lib目录下的所有jar文件,与WebappClassLoader内部缓存的jarNames数组做比较,如果文件名不同或新加或删除了jar文件都返回true。

但这里jarNames变量的初始赋值代码在WebappClassLoader类的addJar方法中的开头部分:

Java代码   收藏代码
  1. if ((jarPath != null) && (jar.startsWith(jarPath))) {  
  2.   
  3.     String jarName = jar.substring(jarPath.length());  
  4.     while (jarName.startsWith("/"))  
  5.         jarName = jarName.substring(1);  
  6.   
  7.     String[] result = new String[jarNames.length + 1];  
  8.     for (i = 0; i < jarNames.length; i++) {  
  9.         result[i] = jarNames[i];  
  10.     }  
  11.     result[jarNames.length] = jarName;  
  12.     jarNames = result;  
  13.   
  14. }  

而addJar方法是在WebappLoader类的startInternal方法中,上面已经给出与这个相关的代码,里面的这段代码部分:

Java代码   收藏代码
  1. // Configure our repositories  
  2. setRepositories();  
  3. setClassPath();  

在setRepositories的方法最后部分:

Java代码   收藏代码
  1. try {  
  2.     JarFile jarFile = new JarFile(destFile);  
  3.     classLoader.addJar(filename, jarFile, destFile);  
  4. catch (Exception ex) {  
  5.     // Catch the exception if there is an empty jar file  
  6.     // Should ignore and continue loading other jar files  
  7.     // in the dir  
  8. }  
  9.   
  10. loaderRepositories.add( filename );  

即在tomcat启动时的加载web应用的过程里就会加载该应用的lib目录下的所有jar文件,同时给WebappClassLoader的实例变量jarNames添加数组元素。


addJar方法的调用路径

 

在看jar包加载的代码时会不断碰到resources对象list、getAttributes等方法的调用,记住这里实际上调用的是上面提到的FileDirContext的相关方法,也即对于文件的查询访问方法就清楚了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值