【0】README
0.0)本文部分描述转自“深入剖析tomcat”,旨在学习 tomcat(8)载入器 的基础知识;
0.1)一个标准web 应用程序中的载入器:简单来说就是 tomcat中的载入器;
0.2)servlet容器需要实现一个自定义的载入器,而不能简单地使用系统的类载入器的原因:(干货——为什么servlet容器要实现一个自定义的载入器)
0.2.1)原因1:因为servlet容器不应该完全信任它正在运行的servlet类;
0.2.2)原因2:如果使用系统类的载入器载入某个servlet类所使用的全部类,那么servlet就能够访问所有的类,包括当前运行的java 虚拟机中环境变量CLASSPATH指明的路径下的所有的类和库,这是非常危险的;因为
servlet应该只允许载入 WEB-INF/classes目录及其子目录下的类,和部署到 WEB-INF/lib 目录下的类(类库);0.2.3)原因3:为了提供自动重载的功能,即当 WEB-INF/classes 目录或 WEB-INF/lib目录下的类发生变化时,web 应用程序会重新载入这些类。在tomcat的载入器的实现中,
类载入器使用一个额外的线程来不断检查servlet 类和其它类的文件的时间戳。
0.3)在Catalina中: 载入器是 org.apache.catalina.Loader接口的实例; 若要支持自动重载功能, 则载入器必须实现 org.apache.catalina.loader.Reloader
接口;
0.4)intro to 两个术语
0.4.1)仓库(repository):仓库表示类载入器会在哪里搜索要载入的类;
0.4.2)资源(resource):而资源指的是一个类载入器中的 DirContext对象,它的文件根路径指的就是 上下文的文件根路径;
0.5)for complete source code, please visit https://github.com/pacosonTang/HowTomcatWorks/tree/master/chapter8/chapter8
【1】 java的类载入器
0)intro to 类载入器: 每次创建java类的实例时,都必须先将类载入到内存中, java虚拟机使用类载入器来载入需要的类。一般case下, 类载入器会在一些java 核心类库,以及环境变量 classpath 中指明的目录中 搜索相关类。如果在这些位置都找不到要载入的类,就会抛出
java.lang.ClassNotFoundException 异常;
1)从J2SE 1.2 开始, jvm 使用了3种类载入器来载入所需要的类:分别是引导类载入器(bootstrap class loader), 扩展类载入器(extension class loader) 和 系统类载入器(system class
loader)。而 引导类载入器是 扩展类载入器的父亲, 扩展类载入器是 系统类载入器的父亲。(干货——jvm 使用了3种类载入器来载入所需要的类)
2)3种载入器的详细描述:(干货——3种载入器的
spec intro)
2.1)引导类载入器: 用于引导启动 jvm。当调用 javax.exe 是, 就会启动引导类载入器。引导类载入器是使用本地代码来实现的, 因为它用来载入运行 jvm 所需要的类, 以及所有的 java 核心类。如 java.lang 包 和
java.io 包下的类。启动类载入器会在 rt.jar 和 i18n.jar 等java 包中搜索要载入的类。
2.2) 扩展类载入器: 负责载入标准扩展目录中的类。sum 公司的 jvm 的标准扩展目录是 /jdk/jre/lib/ext/;
2.3)系统类载入器:是默认的类载入器, 他会搜索在环境变量 CLASSPATH 中指明的路径和 JAR 文件;
3)jvm 使用的是哪种类载入器呢?
3.1)答案在于 类载入器的代理模型。
3.2)载入一个类 的steps(每当需要载入一个类 的时候):(干货中的干货——载入一个类
的steps)
step1)首先调用 系统类载入器,但并不会立即载入这个类;
step2)相反,他会将载入类的任务交给其父类载入器——扩展类载入器;
step3)而扩展类载入器也会将载入任务交给其父类载入器——引导类载入器;
3.3)因此,引导类载入器会首先执行载入某个类的任务。接下来有3种cases:
case1)如果引导类载入器找不到需要载入的类,那么扩展类载入器会尝试 载入该类;
case2)如果扩展类载入器也找不到该类,就轮到系统类载入器继续执行载入任务;
case3)如果系统类载入器也找不到这个类,抛出 java.lang.ClassNotFoundException 异常;
3.4)为什么要这么做? 代理模型的重要用途就是为了 解决 类载入过程中的安全问题;(干货——代理模型的重要用途)
3.5)看个荔枝: 当程序的某个地方调用了 自定义的 java.lang.Object 类时, 系统类载入器会将载入工作 委托给 扩展类载入器,继而会被交给 引导类载入器。 引导类载入器搜索其 核心库, 找到标准的 java.lang.Object
类, 并将之实例化。 结果是, 自定义的 java.lang.Object 类并没有被载入(这正是我们想要的)。
4)关于 java 中类载入机制的一件重要事情是, 可以通过继承抽象类 java.lang.ClassLoader 类 编写自己的类载入器。而 tomcat 要使用自定义类载入器的原因有3条(reasons):(干货——tomcat
要使用自定义类载入器的原因)
r1)为了在载入类中指定某些规则;
r2)为了缓存已经载入的类;
r3)为了实现类的预载入,方便使用;
【2】Loader接口
0)载入web 应用程序中需要的servlet类及其相关类需要遵循的一些rules:(干货——web
app中用到的servlet需要遵循的一些rules)
r1)应用程序中的 servlet 只能引用部署在 WEB-INF/classes 目录及其子目录下的类;
r2)但是,servlet类不能访问其他路径中的类,即使这些类包含在运行当前的Tomcat的 jvm 的 classpath 环境变量中;
r3)此外, servlet类只能访问 WEB-INF/lib 目录下的库,其他目录中的类库不能访问;
1)Tomcat载入器指的是web 应用程序载入器,而不仅仅指 类载入器:(干货——tomcat载入器不仅仅是类载入器)
1.1)载入器必须实现 org.apache.catalina.Loader接口;
1.2)载入器的实现中,会使用一个 自定义类载入器: 它是 org.apache.catalina.loader.WebappClassLoader类的一个实例;(Loader接口的getClassLoader() 方法来获取)
2)Loader接口定义的对仓库集合的操作:(Tomcat中的仓库就是WEB-INFO/classes
目录和 WEB-INF/lib 目录)
2.1)intro to 仓库:一个web app的仓库指的是,其 WEB-INFO/classes 目录和 WEB-INF/lib 目录,这两个目录作为仓库添加到 载入器中;
2.2)addRepository方法和 findRepositories() 方法:添加一个新仓库和返回所有仓库集合(数组对象);
3)Tomcat的载入器通常会与一个 Context级别的servlet容器相关联;
4)自动重载:如果Context 容器中的一个或多个类被修改了, 载入器也可以支持对类的自动重载;
4.1)Loader接口使用modified() 方法来支持类的自动重载:如果仓库中的一个或多个类被修改了,那么modified() 方法会返回true,才能提供自动重载的支持;
4.2)载入器类本身并不会自动重载: 它会调用 Context接口(容器)的reload() 方法来实现;
4.3)setReloadable() 和 getReloadable() 方法:用来指明是否支持载入器的自动重载;
4.4)自动重载的default case:默认情况下是 禁用了自动重载的功能的,要想启动Context容器的自动重载功能,需要再 server.xml 文件中添加一个 Context元素,如下所示:(干货——默认case下,Context容器的自动重载功能是closed,通过如下方式启用自动重载功能)
<Context path="/myApp" docBase="myApp" debug="0" reloadable="true" />
4.5)载入器的实现会指明是否要委托给一个父类载入器;(Loader接口中声明了 getDelegate()方法 和 setDelegate()方法)
4.6)org.apache.catalina.Loader的声明代码如下:
package org.apache.catalina; import java.beans.PropertyChangeListener; public interface Loader { public ClassLoader getClassLoader(); public Container getContainer(); public void setContainer(Container container); public DefaultContext getDefaultContext(); public void setDefaultContext(DefaultContext defaultContext); public boolean getDelegate(); public void setDelegate(boolean delegate); public String getInfo(); public boolean getReloadable(); public void setReloadable(boolean reloadable); public void addPropertyChangeListener(PropertyChangeListener listener); public void addRepository(String repository); public String[] findRepositories(); public boolean modified(); public void removePropertyChangeListener(PropertyChangeListener listener); }
5)Catalina提供了 org.apache.catalina.loader.WebappLoader类作为 Loader接口的实现: WebappLoader 对象中使用
org.apache.catalina.loader.WebappClassLoader 类的实例作为其类载入器,该类继承自 java.net.URLClassLoader类;(干货——WebappClassLoader类很重要,已经提及过两次了)
6)Loader接口及其实现类的URL类图如下:
Attention)当与某个载入器相关联的容器(如 Context)需要使用某个 servlet类时,即当该类的某个方法被调用时, 容器会先调用载入器的 getClassLoader() 方法来获取类载入器的实例。然后,容器会调用类 载入器的
loadClass() 方法来载入这个servlet类;
【3】Reloader接口
1)intro to Reloader接口:为了支持类的自动重载功能,类载入器实现需要实现 org.apache.catalina.loader.Reloader接口;
2)最重要的方法:modified方法,其作用是,如果web app 中的某个servlet 或相关类被修改了,modified方法会返回true;(干货——Reloader接口的最重要的方法modified方法)
【4】 WebappLoader类(web 应用程序载入器, 负责载入web 应用程序中所使用到的类)
1)如上述的UML类图所示,WebappLoader类实现 Runnable接口,当调用WebappLoader类的start方法时,会完成以下几项重要工作(works):
w1)创建一个类载入器;
w2)设置仓库;
w3)设置类路径;
w4)设置访问权限;
w5)启动一个新线程来支持自动重载;
(下面的内容将分别对以上works 进行 intro)
public void start() throws LifecycleException { // org.apache.catalina.loader.WebappLoader.start(),为了看其 outline,我没有对该method做删减 // Validate and update our current component state if (started) throw new LifecycleException (sm.getString("webappLoader.alreadyStarted")); if (debug >= 1) log(sm.getString("webappLoader.starting")); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; if (container.getResources() == null) return; // Register a stream handler factory for the JNDI protocol URLStreamHandlerFactory streamHandlerFactory = new DirContextURLStreamHandlerFactory(); try { URL.setURLStreamHandlerFactory(streamHandlerFactory); } catch (Throwable t) { // Ignore the error here. } // Construct a class loader based on our current repositories list try { classLoader = createClassLoader(); // 创建一个类载入器,下面是设置类载入器 classLoader.setResources(container.getResources()); classLoader.setDebug(this.debug); classLoader.setDelegate(this.delegate); for (int i = 0; i < repositories.length; i++) { classLoader.addRepository(repositories[i]); } // Configure our repositories setRepositories(); // 设置仓库 setClassPath(); // 设置类路径 setPermissions(); // 设置访问权限 if (classLoader instanceof Lifecycle) ((Lifecycle) classLoader).start(); // Binding the Webapp class loader to the directory context DirContextURLStreamHandler.bind ((ClassLoader) classLoader, this.container.getResources()); } catch (Throwable t) { throw new LifecycleException("start: ", t); } // Validate that all required packages are actually available validatePackages(); // Start our background thread if we are reloadable if (reloadable) { log(sm.getString("webappLoader.reloading")); try { threadStart(); // 启动一个新线程来支持自动重载 } catch (IllegalStateException e) { throw new LifecycleException(e); } } }
【4.1】创建类载入器
1)WebappLoader类提供了 getLoaderClass() 方法 和 setLoaderClass() 方法来获取或改变 其私有变量的 loaderClass 的值;
1.1)该私有变量保存了一个字符串类型的值,指明了类载入器所要载入的类的名字;(干货——私有变量loaderClass指明了类载入器所要载入的类的名字)
1.2)默认情况下: 变量loadClass的值是 org.apache.catalina.loader.WebappClassLoader ;
1.3)也可以通过继承 WebappClassLoader 类的方式实现自己的类载入器,然后调用 setLoaderClass() 方法强制WebappLoader实例使用 自定义类载入器。否则的话,在它启动时,WebappLoader 类会调用其 私有方法 createClassLoader() 方法来创建 默认 的
类载入器;
Attention)
A1)可以不使用 WebappClassLoader 类的实例,而使用其他类的实例作为类载入器;
A2)createClassLoader()方法的返回值是: WebappLoader类型的, 因此,如果自定义类型没有继承自 WebappClassLoader l类,createClassLoader方法就会抛出一个异常;
private WebappClassLoader createClassLoader() throws Exception { // org.apache.catalina.loader.WebappLoader.createClassLoader() Class clazz = Class.forName(loaderClass); WebappClassLoader classLoader = null; // highlight line. if (parentClassLoader == null) { // Will cause a ClassCast is the class does not extend WCL, but // this is on purpose (the exception will be caught and rethrown) classLoader = (WebappClassLoader) clazz.newInstance(); } else { Class[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); } return classLoader; }
【4.2】设置仓库
private void setRepositories() { // org.apache.catalina.loader.WebappLoader.setRepositories() if (!(container instanceof Context)) return; ServletContext servletContext = ((Context) container).getServletContext(); if (servletContext == null) return; // Loading the work directory File workDir = (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR); if (workDir == null) return; log(sm.getString("webappLoader.deploy", workDir.getAbsolutePath())); DirContext resources = container.getResources(); // Setting up the class repository (/WEB-INF/classes), if it exists String classesPath = "/WEB-INF/classes"; DirContext classes = null; try { Object object = resources.lookup(classesPath); if (object instanceof DirContext) { classes = (DirContext) object; } } catch(NamingException e) { // Silent catch: it's valid that no /WEB-INF/classes collection // exists } if (classes != null) { File classRepository = null; String absoluteClassesPath = servletContext.getRealPath(classesPath); if (absoluteClassesPath != null) { classRepository = new File(absoluteClassesPath); } else { classRepository = new File(workDir, classesPath); classRepository.mkdirs(); copyDir(classes, classRepository); } log(sm.getString("webappLoader.classDeploy", classesPath, classRepository.getAbsolutePath())); // Adding the repository to the class loader classLoader.addRepository(classesPath + "/", classRepository); } // Setting up the JAR repository (/WEB-INF/lib), if it exists String libPath = "/WEB-INF/lib"; classLoader.setJarPath(libPath); DirContext libDir = null; // Looking up directory /WEB-INF/lib in the context try { Object object = resources.lookup(libPath); if (object instanceof DirContext) libDir = (DirContext) object; } catch(NamingException e) { // Silent catch: it's valid that no /WEB-INF/lib collection // exists } if (libDir != null) { boolean copyJars = false; String absoluteLibPath = servletContext.getRealPath(libPath); File destDir = null; if (absoluteLibPath != null) { destDir = new File(absoluteLibPath); } else { copyJars = true; destDir = new File(workDir, libPath); destDir.mkdirs(); } // Looking up directory /WEB-INF/lib in the context try { NamingEnumeration enum = resources.listBindings(libPath); while (enum.hasMoreElements()) { Binding binding = (Binding) enum.nextElement(); String filename = libPath + "/" + binding.getName(); if (!filename.endsWith(".jar")) continue; // Copy JAR in the work directory, always (the JAR file // would get locked otherwise, which would make it // impossible to update it or remove it at runtime) File destFile = new File(destDir, binding.getName()); log(sm.getString("webappLoader.jarDeploy", filename, destFile.getAbsolutePath())); Resource jarResource = (Resource) binding.getObject(); if (copyJars) { if (!copy(jarResource.streamContent(), new FileOutputStream(destFile))) continue; } JarFile jarFile = new JarFile(destFile); classLoader.addJar(filename, jarFile, destFile); } } catch (NamingException e) { // Silent catch: it's valid that no /WEB-INF/lib directory // exists } catch (IOException e) { e.printStackTrace(); } } }
【4.3】设置类路径
1)设置类路径的任务是:通过在 start()方法中调用 setClassPath方法完成的。该方法会在servlet上下文中 为 Jasper JSP 编译器设置一个字符串形式的属性来指明类路径信息;
private void setClassPath() { // org.apache.catalina.loader.WebappLoader.setClassPath() // Validate our current state information if (!(container instanceof Context)) return; ServletContext servletContext = ((Context) container).getServletContext(); if (servletContext == null) return; StringBuffer classpath = new StringBuffer(); // Assemble the class path information from our class loader chain ClassLoader loader = getClassLoader(); int layers = 0; int n = 0; while ((layers < 3) && (loader != null)) { if (!(loader instanceof URLClassLoader)) break; URL repositories[] = ((URLClassLoader) loader).getURLs(); for (int i = 0; i < repositories.length; i++) { String repository = repositories[i].toString(); if (repository.startsWith("file://")) repository = repository.substring(7); else if (repository.startsWith("file:")) repository = repository.substring(5); else if (repository.startsWith("jndi:")) repository = servletContext.getRealPath(repository.substring(5)); else continue; if (repository == null) continue; if (n > 0) classpath.append(File.pathSeparator); classpath.append(repository); n++; } loader = loader.getParent(); layers++; } // Store the assembled class path as a servlet context attribute servletContext.setAttribute(Globals.CLASS_PATH_ATTR, classpath.toString()); }
【4.4】设置访问权限
1)若运行Tomcat时,使用了安全管理器:则 setPermissions() 方法会为 类载入器设置访问相关目录的权限;
private void setPermissions() { // org.apache.catalina.loader.WebappLoader.setPermission() if (System.getSecurityManager() == null) return; if (!(container instanceof Context)) return; // Tell the class loader the root of the context ServletContext servletContext = ((Context) container).getServletContext(); // Assigning permissions for the work directory File workDir = (File) servletContext.getAttribute(Globals.WORK_DIR_ATTR); if (workDir != null) { try { String workDirPath = workDir.getCanonicalPath(); classLoader.addPermission (new FilePermission(workDirPath, "read,write")); classLoader.addPermission (new FilePermission(workDirPath + File.separator + "-", "read,write,delete")); } catch (IOException e) { // Ignore } } try { URL rootURL = servletContext.getResource("/"); classLoader.addPermission(rootURL); String contextRoot = servletContext.getRealPath("/"); if (contextRoot != null) { try { contextRoot = (new File(contextRoot)).getCanonicalPath(); classLoader.addPermission(contextRoot); } catch (IOException e) { // Ignore } } URL classesURL = servletContext.getResource("/WEB-INF/classes/"); classLoader.addPermission(classesURL); URL libURL = servletContext.getResource("/WEB-INF/lib/"); classLoader.addPermission(libURL); if (contextRoot != null) { if (libURL != null) { File rootDir = new File(contextRoot); File libDir = new File(rootDir, "WEB-INF/lib/"); try { String path = libDir.getCanonicalPath(); classLoader.addPermission(path); } catch (IOException e) { } } } else { if (workDir != null) { if (libURL != null) { File libDir = new File(workDir, "WEB-INF/lib/"); try { String path = libDir.getCanonicalPath(); classLoader.addPermission(path); } catch (IOException e) { } } if (classesURL != null) { File classesDir = new File(workDir, "WEB-INF/classes/"); try { String path = classesDir.getCanonicalPath(); classLoader.addPermission(path); } catch (IOException e) { } } } } } catch (MalformedURLException e) { } }
【4.5】开启新线程执行类的重新载入
1)WebappLoader类支持自动重载功能:如果仓库中的类被重新编译了,那么这个类会自动重新载入,无需重启tomcat;
2)为了实现这个功能:WebappLoader类使用一个线程周期性地检查每个资源的时间戳。间隔时间由变量 checkInterval 指定,单位为妙。默认case下, checkInterval的值为15,即每隔15秒会检查一次是否有文件需要自动重新载入。 getCheckInterval方法和setCheckInterval方法
用于获取和设置间隔时间;
3)tomcat4中,WebappLoader类实现 java.lang.Runnable 接口来支持自动重载,源代码如下:
对以上代码的分析(Analysis)(run方法中while循环会执行以下operations):
o1)使线程休眠一段时间,时长由变量checkInterval指定,以秒为单位;
o2)调用WebappLoader 实例的类载入器的modified方法检查已经载入的类是否被修改,若没有类修改,则重新执行循环;
o3)若某个已经载入的类被修改了,则调用私有方法 notifyContext(),通知与 WebappLoader实例关联的 Context容器重新载入相关类;
Attention)紧接上面的调用流程图,last step 是调用Context容器的reload() 方法,其源代码如下(本文应用程序的Context容器的实现示例是StandardContext):
public synchronized void reload() { //org.apache.catalina.core.StandardContext.reload() // Validate our current component state if (!started) throw new IllegalStateException (sm.getString("containerBase.notStarted", logName())); // Make sure reloading is enabled // if (!reloadable) // throw new IllegalStateException // (sm.getString("standardContext.notReloadable")); log(sm.getString("standardContext.reloadingStarted")); // Stop accepting requests temporarily setPaused(true); // Binding thread ClassLoader oldCCL = bindThread(); // Shut down our session manager if ((manager != null) && (manager instanceof Lifecycle)) { try { ((Lifecycle) manager).stop(); } catch (LifecycleException e) { log(sm.getString("standardContext.stoppingManager"), e); } } // Shut down the current version of all active servlets Container children[] = findChildren(); for (int i = 0; i < children.length; i++) { Wrapper wrapper = (Wrapper) children[i]; if (wrapper instanceof Lifecycle) { try { ((Lifecycle) wrapper).stop(); } catch (LifecycleException e) { log(sm.getString("standardContext.stoppingWrapper", wrapper.getName()), e); } } } // Shut down application event listeners listenerStop(); // Clear all application-originated servlet context attributes if (context != null) context.clearAttributes(); // Shut down filters filterStop(); if (isUseNaming()) { // Start namingContextListener.lifecycleEvent (new LifecycleEvent(this, Lifecycle.STOP_EVENT)); } // Binding thread unbindThread(oldCCL); // Shut down our application class loader if ((loader != null) && (loader instanceof Lifecycle)) { try { ((Lifecycle) loader).stop(); } catch (LifecycleException e) { log(sm.getString("standardContext.stoppingLoader"), e); } } // Binding thread oldCCL = bindThread(); // Restart our application class loader if ((loader != null) && (loader instanceof Lifecycle)) { try { ((Lifecycle) loader).start(); } catch (LifecycleException e) { log(sm.getString("standardContext.startingLoader"), e); } } // Binding thread unbindThread(oldCCL); // Create and register the associated naming context, if internal // naming is used boolean ok = true; if (isUseNaming()) { // Start namingContextListener.lifecycleEvent (new LifecycleEvent(this, Lifecycle.START_EVENT)); } // Binding thread oldCCL = bindThread(); // Restart our application event listeners and filters if (ok) { if (!listenerStart()) { log(sm.getString("standardContext.listenerStartFailed")); ok = false; } } if (ok) { if (!filterStart()) { log(sm.getString("standardContext.filterStartFailed")); ok = false; } } // Restore the "Welcome Files" and "Resources" context attributes postResources(); postWelcomeFiles(); // Restart our currently defined servlets for (int i = 0; i < children.length; i++) { if (!ok) break; Wrapper wrapper = (Wrapper) children[i]; if (wrapper instanceof Lifecycle) { try { ((Lifecycle) wrapper).start(); } catch (LifecycleException e) { log(sm.getString("standardContext.startingWrapper", wrapper.getName()), e); ok = false; } } } // Reinitialize all load on startup servlets loadOnStartup(children); // Restart our session manager (AFTER naming context recreated/bound) if ((manager != null) && (manager instanceof Lifecycle)) { try { ((Lifecycle) manager).start(); } catch (LifecycleException e) { log(sm.getString("standardContext.startingManager"), e); } } // Unbinding thread unbindThread(oldCCL); // Start accepting requests again if (ok) { log(sm.getString("standardContext.reloadingCompleted")); } else { setAvailable(false); log(sm.getString("standardContext.reloadingFailed")); } setPaused(false); // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(Context.RELOAD_EVENT, null); }
【5】WebappClassLoader类
1)web 应用程序中负责载入类的类载入器是: org.apache.catalina.loader.WebappLoader类的实例;
2)考虑到安全性:WebappClassLoader 类不允许载入指定的某些类,这些类的名字存储在一个字符串数组变量triggers中,当前只有一个元素:
private static final String[] triggers = { "javax.servlet.Servlet" };
3)还有,某些特殊的包及其子包下的类也是不允许载入的,也不会将载入类的任务委托给系统类载入器去执行:
private static final String[] packageTriggers = { "javax", "org.xml.sax", "org.w3c.dom", "org.apache.xerces", "org.apache.xalan", };
(下面说明WebappClassLoader类是如何完成待加载类的缓存和载入任务的。)
【5.1】类缓存
1)为了达到更好的性能,会缓存已经载入的类:缓存可以在本地执行,即可以由WebappClassLoader 实例来管理它所加载并缓存的类。
1.1)java.lang.ClassLoader类会维护一个Vector对象,保存已经载入的类,防止这些类在不使用时当做垃圾而回收;
2)资源:每个由 WebappClassLoader 载入的类(无论是在WEB-INF/classes 目录下还是从某个JAR 文件内作为类文件部署), 都视为资源;资源是 org.apache.catalina.loader.ResourceEntry
类的实例;(干货——资源的定义——每个由 WebappClassLoader 载入的类都是资源)
3)资源类ResourceEntry类的源代码定义为:
public class ResourceEntry { // org.apache.catalina.loader.ResourceEntry /** * The "last modified" time of the origin file at the time this class * was loaded, in milliseconds since the epoch. */ public long lastModified = -1; /** * Binary content of the resource. */ public byte[] binaryContent = null; /** * Loaded class. */ public Class loadedClass = null; /** * URL source from where the object was loaded. */ public URL source = null; /** * URL of the codebase from where the object was loaded. */ public URL codeBase = null; /** * Manifest (if the resource was loaded from a JAR). */ public Manifest manifest = null; /** * Certificates (if the resource was loaded from a JAR). */ public Certificate[] certificates = null; }
4)所有已经缓存的类会存储在一个名为resourceEntries 的 HashMap 类型的变量中,其key值就是载入的资源名称。那些载入失败的类会被存储到另一个名为 notFoundResources 的 HashMap 类型的变量中;
【5.2】载入类(当载入类时,WebappClassLoader类要遵守如下rules)
r1)因为所有已经载入的类都会缓存起来,所以载入类时要先检查本地缓存;
r2)若本地缓存中没有,则检查上一层缓存,即调用 java.lang.ClassLoader 类的findLoadedClass() 方法;
r3)若两个缓存中都没有,则使用系统的类载入器进行加载,防止 web 应用程序中的类覆盖J2EE 的类;
r4)若启用了 SecurityManager,则检查是否允许载入该类。若该类是禁止载入的类,抛出 ClassNotFoundException异常;
r5)若打开标志位 delegate,或者待载入的类是属于包触发器中的包名,则调用父载入器来载入相关类。如果父载入器是null,则使用系统的类载入器;
r6)从当前仓库中载入相关的类;
r7)若当前仓库中没有需要的类,且标志位delegate关闭,则使用父类载入器。若父类载入器为 null, 则使用系统的类载入器进行加载;
r8)若仍未找到需要的类,则抛出 ClassNotFoundException 异常;
【5.3】应用程序
1)应用程序目的是为了说明:如何使用与某个Context容器相关联的WebappLoader实例;(干货——本应用程序的目的)
2)而Context接口的标准实现:org.apache.catalina.core.StandardContext;(干货——你也看到了,从本文起,tomcat的Context容器的标准实现变为了StandardContext,而不是原来diy出的
SimpleContext)
3)我们需要知道的是:StandardContext类是如何与监听它触发的事件(如START_EVENT and STOP_EVENT)的监听器协同工作的;(干货——需要理清StandardContext类的工作原理)
4)监听器必须实现接口: org.apache.catalina.lifecycle.LifecycleListener接口;而监听器的一个实例是 SimpleContextConfig类;
5)应用程序源代码:
public final class Bootstrap { public static void main(String[] args) { //invoke: http://localhost:8080/Modern or http://localhost:8080/Primitive // 为了通知StandardContext 实例到哪里查找应用程序目录,需要设置一个名为"catalina.base"的系统属性,其值为"user.dir"属性的值; System.setProperty("catalina.base", System.getProperty("user.dir")); Connector connector = new HttpConnector(); //实例化默认连接器 Wrapper wrapper1 = new SimpleWrapper(); // 为两个servlet类创建两个Wrapper实例; wrapper1.setName("Primitive"); wrapper1.setServletClass("PrimitiveServlet"); Wrapper wrapper2 = new SimpleWrapper(); wrapper2.setName("Modern"); wrapper2.setServletClass("ModernServlet"); // 创建StandardContext 的一个实例,设置应用程序路径和上下文的文档根路径 Context context = new StandardContext(); // StandardContext's start method adds a default mapper context.setPath("/myApp"); context.setDocBase("myApp"); // 上面的代码在功能上等同于下面的在server.xml 文件中的配置 // <Context path="/myApp" docBase="myApp" /> context.addChild(wrapper1); // 将两个Wrapper实例添加到Context容器中 context.addChild(wrapper2); // 为它们设置访问路径的映射关系,这样Context 容器就能够定位到他们 // context.addServletMapping(pattern, name); context.addServletMapping("/Primitive", "Primitive"); context.addServletMapping("/Modern", "Modern"); // add ContextConfig. This listener is important because it configures // StandardContext (sets configured to true), otherwise StandardContext // won't start // 下一步,实例化一个监听器,并通过 Context容器注册它. LifecycleListener listener = new SimpleContextConfig(); ((Lifecycle) context).addLifecycleListener(listener); // 接着,它会实例化WebappLoader类,并将其关联到Context容器. // here is our loader Loader loader = new WebappLoader(); // associate the loader with the Context context.setLoader(loader); // 然后,将Context容器与默认连接器相关联,调用默认连接器的initialize() and start()方法, // 再调用 Context容器的 start() 方法,这样servlet容器准备就绪,可以处理servlet请求了. connector.setContainer(context); try { connector.initialize(); ((Lifecycle) connector).start(); ((Lifecycle) context).start(); // attention: 从这一行开始(包括这一行),进行spec analysis. // 接下来的几行代码仅仅显示出资源的docBase属性值和类载入器中所有的仓库的名字. // now we want to know some details about WebappLoader WebappClassLoader classLoader = (WebappClassLoader) loader.getClassLoader(); System.out.println("Resources' docBase: " + ((ProxyDirContext)classLoader.getResources()).getDocBase()); String[] repositories = classLoader.findRepositories(); for (int i=0; i<repositories.length; i++) { System.out.println(" repository: " + repositories[i]); } // 最后,用户输入任意键后,程序退出. // make the application wait until we press a key. System.in.read(); ((Lifecycle) context).stop(); } catch (Exception e) { e.printStackTrace(); } } }
Conclusion)
C1)web 应用程序中的载入器,或一个简单的载入器,都是 Catalina中最重要的组件;(干货——载入器是Catalina中最重要的组件)
C2)载入器负责载入应用程序所需要的类,因此会使用一个内部类载入器:这个内部类载入器是一个自定义类,tomcat使用这个自定义的类载入器对 web应用程序上下文中要载入的类进行一些约束;
C3)此外,自定义类载入器可以支持对载入类的缓存和对一个或多个被修改的类的自动重载;
6)打印结果
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-resources.jar;lib/naming-common.jar;lib/commons-collectio ns.jar;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomcat.chapter8.startup.Bootstrap HttpConnector Opening server socket on all host IP addresses HttpConnector[8080] Starting background thread WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp Starting Wrapper Primitive Starting Wrapper Modern StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom StandardManager[/myApp]: Seeding of random number generator has been completed Resources' docBase: E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\myApp // this line. Stopping wrapper Primitive Stopping wrapper Modern E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>
Attention)这里还差了一个打印info, 在this line 下面一行: repository: /WEB-INF/classes/ ;