这篇随笔的重点关注启动Tomcat时会用到的两个类,分别是Catalina类 和 Bootstrap类,它们都位于org.apache.catalina.startup包下,Catalina类用于启动或关闭Server对象,并负责解析Tomcat文件:server.xml文件。Bootstrap类是一个入口点,负责创建Catalina实例,并调用其process()方法,理论上这两个类可以合并为一个,但是为了支持Tomcat的多种运行模式,而提供了多种启动类,例如,上面说到的Bootstrap类是作为一个独立的应用程序运行Tomcat的,而另一个类org.apahce.catalina.starup.BootstrapService可以使Tomcat作为一个Windows NT服务来运行。
为了使用户使用方便,Tomcat附带了批处理文件 和 shell 脚本,可以方便的启动或者关闭servlet容器,在这些批处理 文件 和 shell 脚本的帮助下, 用户无须为了执行Bootstrap类 而记下 java.exe程序的选项,相反,用户只需要运行相应的批处理文件或者 shell脚本即可,
先介绍一下Catalina类
Catalina类
org.apache.catalina.startup.Catalina类是启动类,它包含了一个Digester对象,用于解析位于%CATALIN_HOME%conf目录下的server.xml文件。理解了添加到Digester对象中的规则之后,就可以自行配置Tomcat了。
Catalina类还封装了一个Server对象,该对象有一个Service对象,正如之前学习的那样,Service对象包含一个Servlet容器 和 一个 或者多个连接器,可以使用Catalina类来启动或者关闭Server对象,
可以通过实例化Catalina类,并调用其process方法来运行Tomcat,但是在调用该方法时,需要传入适当的参数,第一个参数是start 表示要启动Tomcat,或 stop 表示要向Tomcat发送一条关闭命令,还有其他的参数可选,包括-help、-config、-debug和-nothing
注意:当使用nothing参数时,表示将不对JNDI 命名提供支持,更多关于Tomcat中 对JNDI命名的支持 看org.apahce.naming包中的类吧
一般情况下,及时Catalina类提供了main方法作为程序的入口点,也需要使用Bootstrap类来实例化Catalina类,并调用其process方法,下面给出procee的实现
/** * 实例主程序。 * * @param args Command line arguments */ public void process(String args[]) { setCatalinaHome(); setCatalinaBase(); try { if (arguments(args)) execute(); } catch (Exception e) { e.printStackTrace(System.out); } }
process 方法设置了两个系统属性,分别是 catalina.home 和 catalina.base,默认值均与user.dir值相同,
注意:user.dir属性的值指明了用户的工作目录,即,会从哪个目录下调用java命令,更多系统属性的列表,可以参见java.lang.System类 getProperties方法的介绍
然后procee 方法会调用arguments方法,并传入参数列表,arguments方法处理命令行参数,如果Catalina对象能够继续处理的话,arguments方法会返回true,
/** * 处理指定的命令行参数,如果应该继续处理,则返回true;否则返回false。 * * @param args * Command line arguments to process */ protected boolean arguments(String args[]) { boolean isConfig = false; if (args.length < 1) { usage(); return (false); } for (int i = 0; i < args.length; i++) { if (isConfig) { configFile = args[i]; isConfig = false; } else if (args[i].equals("-config")) { isConfig = true; } else if (args[i].equals("-debug")) { debug = true; } else if (args[i].equals("-nonaming")) { useNaming = false; } else if (args[i].equals("-help")) { usage(); return (false); } else if (args[i].equals("start")) { starting = true; } else if (args[i].equals("stop")) { stopping = true; } else { usage(); return (false); } } return (true); }
process方法会检查arguments方法的返回值,如果返回值为true,则调用execute方法
1 /** 2 * 执行 命令行配置后 处理。 3 * 4 * 5 */ 6 protected void execute() throws Exception { 7 8 // 如果命令启动则启动 9 if (starting) 10 start(); 11 // 如果命令停止则停止 12 else if (stopping) 13 stop(); 14 15 }
execute方法会调用start方法 启动Tomcat 或者 调用stop方法来关闭Tomcat。
注意:在Tomcat 5 以及之后,没有execute方法,会在procee方法中调用 start方法 或者 stop方法
Start方法
start方法 会创建一个Digester实例来解析server.xml文件(Tomcat 配置文件),在解析 server.xml文件之前,start方法会调用Digester对象的push方法,传入当前Catalin对象作为参数,这样Catalina对象就成为了 Digester对象的内部栈中的第一个对象,解析Server.xml文件之后,会使变量server引用一个Server对象,默认是org.apache.catalina.core.StandardServer类型的对象,然后start方法会调用Server对象的initialize方法 和 start方法,接着,Catalina对象的start方法会调用Server对象的await反方循环等待,直到接收到正确的关闭命令,当await方法返回时,Catalina对象的start方法会调用Server对象的stop方法,从而关闭Server对象和其他的组件,此外 start方法 还会使用关闭钩子,确保用户突然退出应用程序时会执行Server对象的stop方法。
1 /** 2 * 启动一个新的Server实例 3 */ 4 protected void start() { 5 6 // 创建一个解析XML文件的Digester 7 Digester digester = createStartDigester(); 8 // 包含 服务器配置文件的File引用 9 File file = configFile(); 10 try { 11 InputSource is = new InputSource("file://" + file.getAbsolutePath()); 12 FileInputStream fis = new FileInputStream(file); 13 is.setByteStream(fis); 14 // 将该Catalina对象压入Digester对象的内部栈中,成为内部栈中的第一个元素 15 digester.push(this); 16 digester.parse(is); 17 fis.close(); 18 } catch (Exception e) { 19 System.out.println("Catalina.start: " + e); 20 e.printStackTrace(System.out); 21 System.exit(1); 22 } 23 24 // 设置附加变量 25 if (!useNaming) { 26 System.setProperty("catalina.useNaming", "false"); 27 } else { 28 System.setProperty("catalina.useNaming", "true"); 29 String value = "org.apache.naming"; 30 String oldValue = System.getProperty(javax.naming.Context.URL_PKG_PREFIXES); 31 if (oldValue != null) { 32 value = value + ":" + oldValue; 33 } 34 System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value); 35 value = System.getProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY); 36 if (value == null) { 37 System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY, 38 "org.apache.naming.java.javaURLContextFactory"); 39 } 40 } 41 42 // If a SecurityManager is being used, set properties for 43 // checkPackageAccess() and checkPackageDefinition 44 if (System.getSecurityManager() != null) { 45 String access = Security.getProperty("package.access"); 46 if (access != null && access.length() > 0) 47 access += ","; 48 else 49 access = "sun.,"; 50 Security.setProperty("package.access", access + "org.apache.catalina.,org.apache.jasper."); 51 String definition = Security.getProperty("package.definition"); 52 if (definition != null && definition.length() > 0) 53 definition += ","; 54 else 55 definition = "sun.,"; 56 Security.setProperty("package.definition", 57 // FIX ME package "javax." was removed to prevent HotSpot 58 // fatal internal errors 59 definition + "java.,org.apache.catalina.,org.apache.jasper."); 60 } 61 62 // Replace System.out and System.err with a custom PrintStream 63 SystemLogHandler log = new SystemLogHandler(System.out); 64 System.setOut(log); 65 System.setErr(log); 66 67 // 创建一个关闭钩子 68 Thread shutdownHook = new CatalinaShutdownHook(); 69 70 // 初始化 Server对象 71 if (server instanceof Lifecycle) { 72 try { 73 server.initialize(); 74 ((Lifecycle) server).start(); 75 try { 76 // 注册关闭钩子 77 Runtime.getRuntime().addShutdownHook(shutdownHook); 78 } catch (Throwable t) { 79 // This will fail on JDK 1.2. Ignoring, as Tomcat can run 80 // fine without the shutdown hook. 81 } 82 // 等待关闭命令 阻塞 直到 收到正确的关闭命令 83 server.await(); 84 } catch (LifecycleException e) { 85 System.out.println("Catalina.start: " + e); 86 e.printStackTrace(System.out); 87 if (e.getThrowable() != null) { 88 System.out.println("----- Root Cause -----"); 89 e.getThrowable().printStackTrace(System.out); 90 } 91 } 92 } 93 94 // 关闭服务器组件 95 if (server instanceof Lifecycle) { 96 try { 97 try { 98 // 首先删除关闭钩子,以便server.stop() 99 100 // 不会被调用两次 101 Runtime.getRuntime().removeShutdownHook(shutdownHook); 102 } catch (Throwable t) { 103 // This will fail on JDK 1.2. Ignoring, as Tomcat can run 104 // fine without the shutdown hook. 105 } 106 // 关闭服务器组件 107 ((Lifecycle) server).stop(); 108 } catch (LifecycleException e) { 109 System.out.println("Catalina.stop: " + e); 110 e.printStackTrace(System.out); 111 if (e.getThrowable() != null) { 112 System.out.println("----- Root Cause -----"); 113 e.getThrowable().printStackTrace(System.out); 114 } 115 } 116 } 117 118 }
stop方法
stop方法用来关闭Catalina 和 Server对象
1 /** 2 * 停止现有的服务器实例。 其实就是手动 向 服务器组件负责监听关闭命令的端口发送 关闭名命令 3 */ 4 protected void stop() { 5 6 // 创建一个解析服务器组件配置XML文件的Digester 7 Digester digester = createStopDigester(); 8 File file = configFile(); 9 try { 10 InputSource is = new InputSource("file://" + file.getAbsolutePath()); 11 FileInputStream fis = new FileInputStream(file); 12 is.setByteStream(fis); 13 digester.push(this); 14 digester.parse(is); 15 fis.close(); 16 } catch (Exception e) { 17 System.out.println("Catalina.stop: " + e); 18 e.printStackTrace(System.out); 19 System.exit(1); 20 } 21 22 // 向服务器组件 指定监听关闭命令端口发送关闭命令 23 try { 24 Socket socket = new Socket("127.0.0.1", server.getPort()); 25 OutputStream stream = socket.getOutputStream(); 26 String shutdown = server.getShutdown(); 27 for (int i = 0; i < shutdown.length(); i++) 28 stream.write(shutdown.charAt(i)); 29 stream.flush(); 30 stream.close(); 31 socket.close(); 32 } catch (IOException e) { 33 System.out.println("Catalina.stop: " + e); 34 e.printStackTrace(System.out); 35 // 如果发送命令时异常 则直接退出 使用关闭钩子来关闭服务器组件 36 System.exit(1); 37 } 38 39 }
启动Digester对象
Catalina类的createStartDigester方法创建了一个Digester实例,然后为其添加规则,以解析server.xml文件,server.xml文件用来配置Tomcat,位于%CATALINA_HOME%/conf目录下,添加到Digester对象中的规则 是理解Tomcat配置的关键,
/** * 创建和配置我们将用于启动的Digester */ protected Digester createStartDigester() { // 初始化Digester Digester digester = new Digester(); if (debug) digester.setDebug(999); // 不校验XML的格式内容 digester.setValidating(false); // 配置我们将要使用的操作 // 遇到Server模式时,使用Server元素的className的值来创建Server实例 digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); // 配置Server 各种属性 digester.addSetProperties("Server"); // 将Server实例 与当前Catalina对象关联上,利用setServer方法,将Server实例传入,Server对象类型为 // "org.apache.catalina.Server" digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); // 遇到Server/GlobalNamingResources模式时,创建 // org.apache.catalina.deploy.NamingResources实例 digester.addObjectCreate("Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); // 设置模式 Server/GlobalNamingResources 生成的对象各种属性 digester.addSetProperties("Server/GlobalNamingResources"); // 将其与Server对象关联起来,利用 Server对象的setGlobalNamingResources方法传入 // GlobalNamingResources类型实例 digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); // xml规则到这里 如果存在上面 Server/GlobalNamingResources元素 到这里可定会遇到结束元素标签 // 会将该值从内部栈中移除 // 遇到 Server/Listener 必须制定 Listener实现类 digester.addObjectCreate("Server/Listener", null, // MUST be specified // in the element "className"); // 配置Listener对象属性 digester.addSetProperties("Server/Listener"); // 将Listener与Server对象关联起来通过 addLifecycleListener digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // 同样 在这里 如果存在 Listener标签 也会遇到结束标签 然后将 Listener对象移除从内部栈中 // 遇到Server/Service模式 创建 Service digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); // 真滴累 不想在写了 如果熟悉 Digester的人其实不用再写了 如果不熟悉的话 可以参考之前写的随笔Digester digester.addObjectCreate("Server/Service/Listener", null, // MUST be // specified // in the // element "className"); digester.addSetProperties("Server/Service/Listener"); digester.addSetNext("Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate("Server/Service/Connector", "org.apache.catalina.connector.http.HttpConnector", "className"); digester.addSetProperties("Server/Service/Connector"); digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.Connector"); digester.addObjectCreate("Server/Service/Connector/Factory", "org.apache.catalina.net.DefaultServerSocketFactory", "className"); digester.addSetProperties("Server/Service/Connector/Factory"); digester.addSetNext("Server/Service/Connector/Factory", "setFactory", "org.apache.catalina.net.ServerSocketFactory"); digester.addObjectCreate("Server/Service/Connector/Listener", null, // MUST // be // specified // in // the // element "className"); digester.addSetProperties("Server/Service/Connector/Listener"); digester.addSetNext("Server/Service/Connector/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // 为嵌套元素添加规则集 digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); digester.addRuleSet(new EngineRuleSet("Server/Service/")); digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Default")); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/DefaultContext/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/Default")); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/DefaultContext/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(digester, parentClassLoader)); return (digester); }
上述方法,会创建一个org.apache.commons.digester.Digester类的一个实例,并且为其添加规则。
前三条规则用于解析server.xml文件的Server元素,可能你已经知道了Server元素时Server.xml文件的根元素,下面是为Server模式添加的规则;
1 // 配置我们将要使用的操作 2 // 遇到Server模式时,使用Server元素的className的值来创建Server实例 3 digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); 4 // 配置Server 各种属性 5 digester.addSetProperties("Server"); 6 // 将Server实例 与当前Catalina对象关联上,利用setServer方法,将Server实例传入,Server对象类型为 7 // "org.apache.catalina.Server" 8 digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");
第一条规则:在遇到Server元素时,Digester对象要创建 org.apahce.catalina.core.StandardServer类的一个实例,例外是,如果Server元素有一个名为 className的属性,那么className属性的值就是必须实例化类的名称。
第二条规则:指明要对Server对象的指定的属性名设置同名的java属性值,
第三条规则将:Server对象压入到Digester对象内部栈中,并与栈中的下一个对象相关联,就是上一个被添加的Catalina对象,调用其setServer方法与Server对象相关联,那Catalina实例是如何放到Digester对象的内部栈中的呢,在Start方法的开始部分,在解析xml文件之前会调用Digester对象的push方法将Catalina对象压入栈
// 将该Catalina对象压入Digester对象的内部栈中,成为内部栈中的第一个元素 digester.push(this);
现在你应该可以理解 后面规则的意思了 如果你还理解不了的话,看下前面的Digester随笔的内容。
关闭Digester对象
createStopDigester方法返回一个Digester对象来关闭Server对象
1 /** 2 * 创建和配置我们将用于关闭的Digester。 因为我们只是为了获取到Server对象的 元素配置属性,目前用到的是 监听关闭命令的端口 以及 3 * 关闭命令,所以只要解析Server元素 标签就可以了 4 * 5 */ 6 protected Digester createStopDigester() { 7 8 // Initialize the digester 9 Digester digester = new Digester(); 10 if (debug) 11 digester.setDebug(999); 12 13 // 配置关闭所需的规则 14 digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); 15 digester.addSetProperties("Server"); 16 digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); 17 18 return (digester); 19 20 }
与启动Digester对象不同,关闭Digester对象只是对XML文档的根元素感兴趣 因为只是为了为了关闭时用到的Server的 监听关闭命令端口 以及 关闭命令
Bootstrap类
有一些类提供了启动Tomcat的入口点,其中之一是org.apache.startup.Bootstrap类。当运行startup.bat文件或者startup.sh文件时,实际上就是调用了该类的main方法。main方法会创建3个类载入器,并实例化Catalina类,然后它调用Catalina的process方法。
1 package org.apache.catalina.startup; 2 3 import java.io.File; 4 import java.lang.reflect.Method; 5 6 /** 7 * 8 * <p> 9 * <b>Title:Bootstrap.java</b> 10 * </p> 11 * <p> 12 * Copyright:ChenDong 2018 13 * </p> 14 * <p> 15 * Company:仅学习时使用 16 * </p> 17 * <p> 18 * 类功能描述:Catalina 的Boostrap装载类。这个应用程序构造了一个类加载器,用于加载Catalina内部类(通过 19 * “catalina.home”下的 20 * “server”目录中找到的所有JAR文件),并开始容器的常规执行。这种迂回方法的目的是将Catalina内部类(以及它们依赖的任何其他类, 21 * 例如XML解析器)排除在系统类路径之外,因此应用程序级类不可见。 22 * </p> 23 * 24 * @author 陈东 25 * @date 2018年12月25日 下午9:42:57 26 * @version 1.0 27 */ 28 public final class Bootstrap { 29 30 // ------------------------------------------------------- Static Variables 31 32 /** 33 * 调试用于处理启动的细节级别。 34 * 35 * 36 */ 37 private static int debug = 0; 38 39 // ----------------------------------------------------------- Main Program 40 41 /** 42 * 引导程序的主程序。 43 * 44 * @param args 45 * Command line arguments to be processed 46 */ 47 public static void main(String args[]) { 48 49 // Set the debug flag appropriately 50 for (int i = 0; i < args.length; i++) { 51 if ("-debug".equals(args[i])) 52 debug = 1; 53 } 54 55 // 如果尚未设置,则从catalina.home配置catalina.base 56 if (System.getProperty("catalina.base") == null) 57 System.setProperty("catalina.base", getCatalinaHome()); 58 /** 59 * 使用多个类载入器的母的是为了防止应用程序中类(包括 servlet类 和 Web应用程序中的其他辅助类) 60 * 使用WEB-INF/classes目录 和 WEB-INF/lib目录之外的类。 61 * 部署到%CATALINA_HOME%/common/lib目录下的jar文件的类文件是可用的 62 */ 63 64 // 构造我们需要的类装入器 65 66 ClassLoader commonLoader = null; 67 ClassLoader catalinaLoader = null; 68 ClassLoader sharedLoader = null; 69 /** 70 * 对于每个类载入器都会制定一条可以访问的路径, 71 */ 72 try { 73 74 File unpacked[] = new File[1]; 75 File packed[] = new File[1]; 76 File packed2[] = new File[2]; 77 ClassLoaderFactory.setDebug(debug); 78 79 unpacked[0] = new File(getCatalinaHome(), "common" + File.separator + "classes"); 80 packed2[0] = new File(getCatalinaHome(), "common" + File.separator + "endorsed"); 81 packed2[1] = new File(getCatalinaHome(), "common" + File.separator + "lib"); 82 /** 83 * commonLoader 类载入器,载入 <code>%CATALINA_HOME%/common/lib</code>、 84 * <code>%CATALINA_HOME%/common/classes</code>、 85 * <code>%CATALINA_HOME%/common/endorsed</code>目录下的java类 86 */ 87 commonLoader = ClassLoaderFactory.createClassLoader(unpacked, packed2, null); 88 89 unpacked[0] = new File(getCatalinaHome(), "server" + File.separator + "classes"); 90 packed[0] = new File(getCatalinaHome(), "server" + File.separator + "lib"); 91 /** 92 * catalinaLoaderb 类载入 负责 载入 运行 Catalina Servlet容器 所需要类,它可以载入 93 * <code>%CATALINA_HOME%/server/classes</code>、 94 * <code>%CATALINA_HOME%/server/lib</code> 以及 commonLoader 95 * 类载入器可以载入的类 96 */ 97 catalinaLoader = ClassLoaderFactory.createClassLoader(unpacked, packed, commonLoader); 98 99 unpacked[0] = new File(getCatalinaBase(), "shared" + File.separator + "classes"); 100 packed[0] = new File(getCatalinaBase(), "shared" + File.separator + "lib"); 101 /** 102 * sharedLoader 类载入器可以载入 103 * <code>%CATALINA_HOME%/shared/classes</code>、 104 * <code>%CATALINA_HOME%/shared/lib</code> 以及 commonLoader 105 * 类载入器可以载入的类。 106 * 107 * 在Tomcat 中,每个Web应用程序中与Context 容器相关联的每个类载入器的 父类载入器都是 sharedLoader 108 * 类载入器 109 * 110 * sharedLoader 类载入器 并不能访问Catalina的内部类,或CLASSPATH环境变量指定的类路径中的类 111 */ 112 sharedLoader = ClassLoaderFactory.createClassLoader(unpacked, packed, commonLoader); 113 } catch (Throwable t) { 114 115 log("Class loader creation threw exception", t); 116 System.exit(1); 117 118 } 119 120 Thread.currentThread().setContextClassLoader(catalinaLoader); 121 122 // 加载启动类并调用它的process()方法 123 try { 124 125 SecurityClassLoad.securityClassLoad(catalinaLoader); 126 127 // 实例化一个启动类Catalina类 128 if (debug >= 1) 129 log("Loading startup class"); 130 Class startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); 131 Object startupInstance = startupClass.newInstance(); 132 133 // 设置共享扩展类加载器 134 if (debug >= 1) 135 log("Setting startup class properties"); 136 // 调用Catalina的setParentClassLoader方法 137 String methodName = "setParentClassLoader"; 138 Class paramTypes[] = new Class[1]; 139 paramTypes[0] = Class.forName("java.lang.ClassLoader"); 140 Object paramValues[] = new Object[1]; 141 paramValues[0] = sharedLoader; 142 Method method = startupInstance.getClass().getMethod(methodName, paramTypes); 143 method.invoke(startupInstance, paramValues); 144 145 // 调用Catalina的process 146 if (debug >= 1) 147 log("Calling startup class process() method"); 148 methodName = "process"; 149 paramTypes = new Class[1]; 150 paramTypes[0] = args.getClass(); 151 paramValues = new Object[1]; 152 paramValues[0] = args; 153 method = startupInstance.getClass().getMethod(methodName, paramTypes); 154 // 将参数args 传入Catalina对象的 process方法中 155 method.invoke(startupInstance, paramValues); 156 157 } catch (Exception e) { 158 System.out.println("Exception during startup processing"); 159 e.printStackTrace(System.out); 160 System.exit(2); 161 } 162 163 } 164 165 /** 166 * 获取catalina.home环境变量的值。 如果之前没有设置过{@code catalina.home}的值,就会返回 user.dir属性的值 167 */ 168 private static String getCatalinaHome() { 169 return System.getProperty("catalina.home", System.getProperty("user.dir")); 170 } 171 172 /** 173 * 获取catalina.base环境变量的值。 174 * 如果属性{@code catalina.base }的属性值为空,则返回{@code catalina.home}的值 175 */ 176 private static String getCatalinaBase() { 177 return System.getProperty("catalina.base", getCatalinaHome()); 178 } 179 180 /** 181 * 记录调试详细信息。 182 * 183 * 184 * 185 * @param message 186 * 要被记录的信息 187 */ 188 private static void log(String message) { 189 190 System.out.print("Bootstrap: "); 191 System.out.println(message); 192 193 } 194 195 /** 196 * 记录异常的调试详细信息。 197 * 198 * 199 * 200 * @param message 201 * 要被记录的信息 202 * @param exception 203 * 记录时遇到异常 204 */ 205 private static void log(String message, Throwable exception) { 206 207 log(message); 208 exception.printStackTrace(System.out); 209 210 } 211 212 }
Bootstrap 类 有四个静态方法, 分别是两个log方法、getCatalinaHome方法 和 getCatalinaBase()方法,getCatalinaHome方法的额实现如下
/** * 获取catalina.home环境变量的值。 如果之前没有设置过{@code catalina.home}的值,就会返回 user.dir属性的值 */ private static String getCatalinaHome() { return System.getProperty("catalina.home", System.getProperty("user.dir")); }
getCatalinaBase方法实现如下
/** * 获取catalina.base环境变量的值。 * 如果属性{@code catalina.base }的属性值为空,则返回{@code catalina.home}的值 */ private static String getCatalinaBase() { return System.getProperty("catalina.base", getCatalinaHome()); }
此外 main方法 还会为不同目的而创建三个类载入器
/** * 使用多个类载入器的母的是为了防止应用程序中类(包括 servlet类 和 Web应用程序中的其他辅助类) * 使用WEB-INF/classes目录 和 WEB-INF/lib目录之外的类。 * 部署到%CATALINA_HOME%/common/lib目录下的jar文件的类文件是可用的 */ // 构造我们需要的类装入器 ClassLoader commonLoader = null; ClassLoader catalinaLoader = null; ClassLoader sharedLoader = null; /** * 对于每个类载入器都会制定一条可以访问的路径, */ try { File unpacked[] = new File[1]; File packed[] = new File[1]; File packed2[] = new File[2]; ClassLoaderFactory.setDebug(debug); unpacked[0] = new File(getCatalinaHome(), "common" + File.separator + "classes"); packed2[0] = new File(getCatalinaHome(), "common" + File.separator + "endorsed"); packed2[1] = new File(getCatalinaHome(), "common" + File.separator + "lib"); /** * commonLoader 类载入器,载入 <code>%CATALINA_HOME%/common/lib</code>、 * <code>%CATALINA_HOME%/common/classes</code>、 * <code>%CATALINA_HOME%/common/endorsed</code>目录下的java类 */ commonLoader = ClassLoaderFactory.createClassLoader(unpacked, packed2, null); unpacked[0] = new File(getCatalinaHome(), "server" + File.separator + "classes"); packed[0] = new File(getCatalinaHome(), "server" + File.separator + "lib"); /** * catalinaLoaderb 类载入 负责 载入 运行 Catalina Servlet容器 所需要类,它可以载入 * <code>%CATALINA_HOME%/server/classes</code>、 * <code>%CATALINA_HOME%/server/lib</code> 以及 commonLoader * 类载入器可以载入的类 */ catalinaLoader = ClassLoaderFactory.createClassLoader(unpacked, packed, commonLoader); unpacked[0] = new File(getCatalinaBase(), "shared" + File.separator + "classes"); packed[0] = new File(getCatalinaBase(), "shared" + File.separator + "lib"); /** * sharedLoader 类载入器可以载入 * <code>%CATALINA_HOME%/shared/classes</code>、 * <code>%CATALINA_HOME%/shared/lib</code> 以及 commonLoader * 类载入器可以载入的类。 * * 在Tomcat 中,每个Web应用程序中与Context 容器相关联的每个类载入器的 父类载入器都是 sharedLoader * 类载入器 * * sharedLoader 类载入器 并不能访问Catalina的内部类,或CLASSPATH环境变量指定的类路径中的类 */ sharedLoader = ClassLoaderFactory.createClassLoader(unpacked, packed, commonLoader); } catch (Throwable t) { log("Class loader creation threw exception", t); System.exit(1); } Thread.currentThread().setContextClassLoader(catalinaLoader);
在创建了三个类载入器之后,main方法会载入Catalina类并创建它的一个实例,然后再将其赋值给 startupInstance变量
Class startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
然后它调用setParentClassLoader方法,并将sharedLoader类载入器作为参数传入
1 String methodName = "setParentClassLoader"; 2 Class paramTypes[] = new Class[1]; 3 paramTypes[0] = Class.forName("java.lang.ClassLoader"); 4 Object paramValues[] = new Object[1]; 5 paramValues[0] = sharedLoader; 6 Method method = startupInstance.getClass().getMethod(methodName, paramTypes); 7 method.invoke(startupInstance, paramValues);
最后main方法会调用Catalina对象的process方法
1 // 调用Catalina的process 2 if (debug >= 1) 3 log("Calling startup class process() method"); 4 methodName = "process"; 5 paramTypes = new Class[1]; 6 paramTypes[0] = args.getClass(); 7 paramValues = new Object[1]; 8 paramValues[0] = args; 9 method = startupInstance.getClass().getMethod(methodName, paramTypes); 10 // 将参数args 传入Catalina对象的 process方法中 11 method.invoke(startupInstance, paramValues);
明天继续搞