tomcat(8)载入器

【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 stateif (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 protocolURLStreamHandlerFactory streamHandlerFactory =new DirContextURLStreamHandlerFactory();try {URL.setURLStreamHandlerFactory(streamHandlerFactory);} catch (Throwable t) {// Ignore the error here.}// Construct a class loader based on our current repositories listtry {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 repositoriessetRepositories(); // 设置仓库setClassPath(); // 设置类路径setPermissions(); // 设置访问权限if (classLoader instanceof Lifecycle)((Lifecycle) classLoader).start(); // Binding the Webapp class loader to the directory contextDirContextURLStreamHandler.bind((ClassLoader) classLoader, this.container.getResources());} catch (Throwable t) {throw new LifecycleException("start: ", t);}// Validate that all required packages are actually availablevalidatePackages();// Start our background thread if we are reloadableif (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 directoryFile 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 existsString 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 loaderclassLoader.addRepository(classesPath + "/", classRepository);}// Setting up the JAR repository (/WEB-INF/lib), if it existsString libPath = "/WEB-INF/lib";classLoader.setJarPath(libPath);DirContext libDir = null;// Looking up directory /WEB-INF/lib in the contexttry {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 contexttry {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 informationif (!(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 chainClassLoader 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));elsecontinue;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 attributeservletContext.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 contextServletContext servletContext =((Context) container).getServletContext();// Assigning permissions for the work directoryFile 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 stateif (!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 temporarilysetPaused(true);// Binding threadClassLoader oldCCL = bindThread();// Shut down our session managerif ((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 servletsContainer 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 listenerslistenerStop();// Clear all application-originated servlet context attributesif (context != null)context.clearAttributes();// Shut down filtersfilterStop();if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.STOP_EVENT));}// Binding threadunbindThread(oldCCL);// Shut down our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).stop();} catch (LifecycleException e) {log(sm.getString("standardContext.stoppingLoader"), e);}}// Binding threadoldCCL = bindThread();// Restart our application class loaderif ((loader != null) && (loader instanceof Lifecycle)) {try {((Lifecycle) loader).start();} catch (LifecycleException e) {log(sm.getString("standardContext.startingLoader"), e);}}// Binding threadunbindThread(oldCCL);// Create and register the associated naming context, if internal// naming is usedboolean ok = true;if (isUseNaming()) {// StartnamingContextListener.lifecycleEvent(new LifecycleEvent(this, Lifecycle.START_EVENT));}// Binding threadoldCCL = bindThread();// Restart our application event listeners and filtersif (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 attributespostResources();postWelcomeFiles();// Restart our currently defined servletsfor (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 servletsloadOnStartup(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 threadunbindThread(oldCCL);// Start accepting requests againif (ok) {log(sm.getString("standardContext.reloadingCompleted"));} else {setAvailable(false);log(sm.getString("standardContext.reloadingFailed"));}setPaused(false);// Notify our interested LifecycleListenerslifecycle.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 mappercontext.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 loaderLoader loader = new WebappLoader();// associate the loader with the Contextcontext.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 WebappLoaderWebappClassLoader 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 ModernE:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>
Attention)这里还差了一个打印info, 在this line 下面一行: repository: /WEB-INF/classes/ ;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/331221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

micrometer_具有InlfuxDB的Spring Boot和Micrometer第3部分:Servlet和JDBC

micrometer在上一个博客中&#xff0c;我们使用由InfluxDB支持的Micrometer设置了React式应用程序。 在本教程中&#xff0c;我们将使用传统的带有JDBC的基于Servlet的阻塞Spring堆栈。 我选择的数据库是postgresql。 我将使用与先前博客文章相同的脚本。 因此&#xff0c;我…

漫画算法:辗转相除法是什么鬼

转载自 玻璃猫 程序员小灰 大四毕业前夕&#xff0c;计算机学院的小灰又一次顶着炎炎烈日&#xff0c; 去某IT公司面试研发工程师岗位…… 半小时后&#xff0c;公司会议室&#xff0c;面试开始…… 小灰奋笔疾书&#xff0c;五分钟后…… 小灰的思路十分简单。他使用暴力…

tomcat(9)Session管理

【0】README0.0&#xff09;本文部分描述转自“深入剖析tomcat”&#xff0c;旨在学习“tomcat-Session管理” 的基础知识&#xff1b;0.1&#xff09;Catalina通过一个称为Session 管理器的组件来管理建立的Session对象&#xff0c;该组件由org.apache.catalina.Manager接口来…

micrometer_具有InlfuxDB的Spring Boot和Micrometer第2部分:添加InfluxDB

micrometer自从我们添加了基本应用程序以来&#xff0c;是时候启动InfluxDB实例了。 我们将按照之前的教程进行操作&#xff0c;并添加一个docker实例。 docker run –rm -p 8086&#xff1a;8086 –name influxdb-本地influxdb 是时候在我们的pom上添加微米InfluxDB依赖项了…

漫画:什么是volatile关键字?(整合版)

转载自 永远爱大家的 程序员小灰 ————— 第二天 ————— ———————————— Java内存模型简称JMM&#xff08;Java Memory Model&#xff09;&#xff0c;是Java虚拟机所定义的一种抽象规范&#xff0c;用来屏蔽不同硬件和操作系统的内存访问差异&#xff0c;让j…

tomcat(supplement)HttpConnector.initialize() 和 start() 方法 以及 StandardContext.start()方法的分析

【0】README 0.0&#xff09;本文中源代码的背景&#xff0c;参见 tomcat(9)session管理 0.1&#xff09;本文主要以图片的形式分析他们大致的调用过程&#xff1b; 0.2&#xff09;HttpConnector org.apache.catalina.connector.http.HttpConnector; 而StandardContext o…

restful rest_HATEOAS的RESTful服务。 超媒体:REST的秘密要素

restful rest在这篇文章中&#xff0c;我们将介绍有关HATEOAS的RESTful服务的综合文章。 超媒体是REST的秘密成分。 1.简介 在本教程的前一部分中&#xff0c;我们花了一些时间来刷新有关REST体系结构样式的基本原理的知识。 业界对REST状态的批判性眼光揭示了一个令人失望的…

漫画:什么是单例设计模式

转载自 永远爱大家的 程序员小灰 ————— 第二天 ————— 单例模式第一版&#xff1a; 1234567891011public class Singleton {private Singleton() {} //私有构造函数private static Singleton instance null; //单例对象//静态工厂方法public static Singleton ge…

如何在工作繁重、睡眠较少的情况下保持旺盛精力?

作者&#xff1a;陈炬 链接&#xff1a;https://www.zhihu.com/question/23177623/answer/47785761 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 本人也在创业&#xff0c;结合《精力管理》一书&#xff0c;说说我…

mockito接口没法赋值_Mockito:无法实例化@InjectMocks字段:类型是接口

mockito接口没法赋值使用Mockito进行Java类的模拟和存根的任何人&#xff0c;可能都熟悉InjectMocks -annotation。 在要测试的类上使用此批注&#xff0c;Mockito将尝试通过构造函数注入&#xff0c;setter注入或属性注入来注入模拟。 魔术成功了&#xff0c;它无声地失败了&a…

tomcat(10)安全性

【0】README0.0&#xff09;本文部分描述转自“深入剖析tomcat”&#xff0c;旨在学习 tomcat(10)安全性 的基本知识&#xff1b;0.1&#xff09;servlet技术支持通过配置部署描述器&#xff08;web.xml&#xff09;文件来对这些内容进行访问控制&#xff1b;&#xff08;干货—…

SonarQube 8.3.x中的Maven项目的测试覆盖率报告

几年前&#xff0c;我写了一篇博客文章&#xff0c;介绍如何在SonarQube中生成测试报告&#xff0c;该报告独立于单元测试和集成测试的测试报告中。 从SonarQube 6.2开始&#xff0c;测试报告不再在这些类别中分开&#xff08;请参阅SonarQube的博客文章 &#xff09;。 SonarQ…

单例模式懒汉、饿汉和登记

转载自 JAVA设计模式之单例模式本文继续介绍23种设计模式系列之单例模式。 概念&#xff1a;  java中单例模式是一种常见的设计模式&#xff0c;单例模式的写法有好几种&#xff0c;这里主要介绍三种&#xff1a;懒汉式单例、饿汉式单例、登记式单例。  单例模式有以下特点…

量角器中Selenium定位器的完整指南(示例)

在测试网站的功能时&#xff0c;特别是Web元素&#xff08;例如单选按钮&#xff0c;文本框&#xff0c;下拉列表等&#xff09;&#xff0c;您需要确保能够访问这些元素。 Selenium定位器正是出于这个目的&#xff0c;通过使用此命令&#xff0c;我们可以识别这些Web元素DOM&a…

MySQL的自然联结+外部联结(左外连接,右外连接)+内部联结

【0】README0.1&#xff09;本文旨在review MySQL的自然联结外部联结&#xff08;左外连接&#xff0c;右外连接&#xff09;内部联结 的相关知识&#xff1b;【1】自然联结1&#xff09;自然联结定义&#xff1a;无论何时对表进行联结&#xff0c;应该至少有一个列出现不止一个…

MySQL 添加列+修改列+删除列

【0】REAMDE 0.1&#xff09;本文部分文字描述转自 http://blog.163.com/zhangjie_0303/blog/static/99082706201191911653778/ 0.2&#xff09;本文旨在review mysql 对列的相关操作&#xff1a;如添加&#xff0c;修改&#xff0c;删除以及重命名表名等操作&#xff1b; 【1】…

compose应用_带有PostgreSQLDocker Compose for Spring Boot应用程序

compose应用在此博客文章中&#xff0c;您将学习如何使用PostgreSQL配置Spring Boot应用程序以与Docker Compose一起运行。 这篇博客文章涵盖&#xff1a; Spring Boot应用程序Dockerfile配置&#xff0c;在依赖项和资源之间进行了清晰的分离 用于通过PostgreSQL运行应用程序…

单例模式面试题

转载自 单例模式面试题&#xff08;特点、理解&#xff09; (1)单例模式特点&#xff08;什么是单例模式&#xff09;&#xff1f;  a.单例类只能有一个实例。  b.单例类必须自己创建自己的唯一实例。  c.单例类必须给所有其他对象提供这一实例。 (2)单例模式的作用&#x…

MySQL的source命令不加分号和delimiter的使用

【0】README 0.1&#xff09;本文旨在 review source 命令&#xff0c; 这一直是我的痛&#xff0c;为什么一直导入 sql 文件不成功&#xff0c;一直没有写 blog 吧他 记录下来&#xff08;事实上&#xff0c;也间接证明我就是个小白&#xff09;&#xff1b; 0.2&#xff09…

selenium自动化测试_维持Selenium测试自动化的完美方法

selenium自动化测试毫无疑问&#xff0c; 自动浏览器测试已改变了软件开发的工作方式。 如果不是Selenium&#xff0c;我们将无法像我们一样使用各种各样的无错误的Web应用程序。 但是有时&#xff0c;甚至IT部门也误解了自动化一词。 大多数人认为计算机将为他们完成所有测试…