Tomcat 8.5.9 源码阅读(一)

Tomcat 8.5.9 源码阅读(一)
目录前言启动类 BootstrapBootstrap 成员变量Bootstrap 关键方法maininitreplacecreateClassLoadersetAwait总结前言慢慢来一点一点吃透最常用的 web 容器 Tomcat。本次使用的 Tomcat 源码版本为 8.5.9另有 10.0.6 辅以参考。本次阅读的目的为了解 Tomcat 的启动和请求处理的过程并学习其设计与实现的思路及其中用到的各种“奇淫巧技”。本文用到的源码获取地址https://gitee.com/scarypie/tomcat-source-code-learning启动类 BootstrapBootstrap 是 Tomcat 的启动类也是本文的切入口。Bootstrap 被定义为 final不可被继承其实加不加 final 都可以加上更严谨一些。另外 Tomcat 使用的是自带的日志框架名为 JULI。其实就是 JCLApache Commons Logging的一个分支其实现为 JULJava Util Logging采用硬编码方式。Bootstrap 成员变量Bootstrap 成员变量并不多其中 log、daemon、catalinaBaseFile、catalinaHomeFile、PATH_PATTERN需要在静态方法中调用所以必需以 static 修饰。另外四个为 catalinaDaemon、commonLoader、catalinaLoader、sharedLoadercatalinaDaemon 为 Object 类型另外三个为 ClassLoader。这里可以提前说一下Tomcat 的初始化和启动基本都以反射的方式调用 Catalina 类的方法实现。privatestaticfinalLoglogLogFactory.getLog(Bootstrap.class);/** * Daemon object used by main. */privatestaticBootstrapdaemonnull;privatestaticfinalFilecatalinaBaseFile;privatestaticfinalFilecatalinaHomeFile;privatestaticfinalPatternPATH_PATTERNPattern.compile((\.*?\)|(([^,])*));// -------------------------------------------------------------- Variables/** * Daemon reference. */privateObjectcatalinaDaemonnull;ClassLoadercommonLoadernull;ClassLoadercatalinaLoadernull;ClassLoadersharedLoadernull;Bootstrap 关键方法main() 方法不用说程序入口。除却它还有无参 init()、setAwait()、load()、start()init() 中会调用 initClassLoaders()而 initClassLoaders() 中会调用 createClassLoader()createClassLoader() 中会调用 replace()、getPaths()。main/** * Main method and entry point when starting Tomcat via the provided * scripts. * * param args Command line arguments to be processed */publicstaticvoidmain(Stringargs[]){// ----------------- 第一部分 -------------------------if(daemonnull){// Dont set daemon until init() has completedBootstrapbootstrapnewBootstrap();try{// 1.初始化化类加载器并初始化calatinabootstrap.init();}catch(Throwablet){handleThrowable(t);t.printStackTrace();return;}daemonbootstrap;}else{// When running as a service the call to stop will be on a new// thread so make sure the correct class loader is used to prevent// a range of class not found exceptions.// 如果daemon不为空则只需要设置当前线程上下文累加器为catalinaLoaderThread.currentThread().setContextClassLoader(daemon.catalinaLoader);}// ----------------- 第二部分 -------------------------try{Stringcommandstart;if(args.length0){// 默认commandstart如果args有参数必须把start放在最后// 如 -config xxx startcommandargs[args.length-1];}if(command.equals(startd)){args[args.length-1]start;daemon.load(args);daemon.start();}elseif(command.equals(stopd)){args[args.length-1]stop;daemon.stop();}elseif(command.equals(start)){// 以下三个方法都是反射调用calatina的方法daemon.setAwait(true);daemon.load(args);daemon.start();}elseif(command.equals(stop)){daemon.stopServer(args);}elseif(command.equals(configtest)){daemon.load(args);if(nulldaemon.getServer()){System.exit(1);}System.exit(0);}else{log.warn(Bootstrap: command \command\ does not exist.);}}catch(Throwablet){// Unwrap the Exception for clearer error reportingif(tinstanceofInvocationTargetExceptiont.getCause()!null){tt.getCause();}handleThrowable(t);t.printStackTrace();System.exit(1);}}main 方法可以分开为两个部分来看第一部分从467到485行其作用就是创建并初始化类加载器初始化成员变量 daemon、catalinaDaemon设置当前线程上下文累加器为 catalinaLoader。第二部分根据命令参数不同执行不同逻辑start、startd、stop、configtest命令分别表示启动、后台启动、停止、后台停止不等待关机完成立即返回。如果命令为 start 则会依次调用 Bootstrap 的 setAwait()、load()和start()方法。综上main() 方法逻辑很简单就是创建并初始化 Bootstrap 实例并初始化其成员变量然后调用该实例的三个方法。init/** * 初始化化类加载器并初始化calatina * Initialize daemon. * throws Exception Fatal initialization error */publicvoidinit()throwsException{// 初始化三个classLoader// commonLoader、catalinaLoader、sharedLoaderinitClassLoaders();// 设置当前线程的ContextClassLoader为catalinaLoaderThread.currentThread().setContextClassLoader(catalinaLoader);SecurityClassLoad.securityClassLoad(catalinaLoader);// Load our startup class and call its process() methodif(log.isDebugEnabled())log.debug(Loading startup class);// catalinaLoader 加载Catalina并实例化Class?startupClasscatalinaLoader.loadClass(org.apache.catalina.startup.Catalina);ObjectstartupInstancestartupClass.newInstance();// Set the shared extensions class loaderif(log.isDebugEnabled())log.debug(Setting startup class properties);// 调用 catalina 的方法setParentClassLoader// 设置catalina的parentClassLoader 为 sharedLoaderStringmethodNamesetParentClassLoader;Class?paramTypes[]newClass[1];paramTypes[0]Class.forName(java.lang.ClassLoader);ObjectparamValues[]newObject[1];paramValues[0]sharedLoader;MethodmethodstartupInstance.getClass().getMethod(methodName,paramTypes);method.invoke(startupInstance,paramValues);catalinaDaemonstartupInstance;}init() 方法也很简单一句话概括就是创建并初始化三个类加载器设置当前线程上下文类加载器为 catalinaClassLoader反射创建 Catalina 类实例并将其指派给 Bootstrap 实例的 catalinaDaemon 成员变量。replaceTomcat 安装目录的 conf 文件夹下包含有 catalina.properties 配置文件其典型配置如下common.loader${catalina.base}/lib,${catalina.home}/lib server.loader${catalina.home}/server/lib shared.loader${catalina.base}/shared/lib而 replace() 方法的作用就是将字符串中的 ${property_name} 占位符替换为实际的系统属性值如在 createClassLoader() 中调用 replace(common.loader)其返回值如下D:\Workspace\tomcat-source-code-learning\apache-tomcat-8.5.9-src/lib, D:\Workspace\tomcat-source-code-learning\apache-tomcat-8.5.9-src/lib/*.jar, D:\Workspace\tomcat-source-code-learning\apache-tomcat-8.5.9-src/lib, D:\Workspace\tomcat-source-code-learning\apache-tomcat-8.5.9-src/lib/*.jar工作原理如下1.使用正则表达式分割正则PATH_PATTERN Pattern.compile(“(”.?)|(([^,])))匹配两种模式“.*?” - 双引号包裹的路径[^,]* - 非逗号字符序列2.去除首尾空格3.处理空路径路过空字符串4.处理引号如果路径以 开头和结尾 → 去除引号如果路径包含未配对的 → 抛出异常其他情况保持不变createClassLoaderinitClassLoader() 方法会分别调用 createClassLoader(“common”, null)、createClassLoader(“server”, commonLoader)、createClassLoader(“shared”, commonLoader)createClassLoader() 第二个参数为父类加载器。createClassLoader() 从 catalina.properties 读取 name.loader 配置如 common.loader、server.loader、shared.loader如果配置为空则直接返回父类加载器。然后调用 replace() 方法替换系统属性占位符如 ${catalina.home}、${catalina.base}。再调用 getPaths() 方法将配置值解析为路径数组。为每个路径创建 Repository 对象确定类型URL: 以 http:// 或 https:// 开头的远程 JARGLOB: 以 *.jar 结尾匹配目录下所有 JAR 文件JAR: 单个 JAR 文件DIR: 目录最后调用 ClassLoaderFactory.createClassLoader() 创建实际的类加载器。三个类加载器关系加载器父加载器配置键commonLoadernullcommon.loadercatalinaLoadercommonLoaderserver.loadersharedLoadercommonLoadershared.loader综上该方法根据配置文件 catalina.properties 中的配置动态创建类加载器用于实现 Tomcat 内部类与应用程序类的隔离。这种设计将 Tomcat 内部类位于 server 目录与应用程序类隔离开避免类冲突符合 Java 的双亲委派模型和模块化隔离原则。附下 initClassLoaders() 方法代码其作用不言而喻privatevoidinitClassLoaders(){try{commonLoadercreateClassLoader(common,null);if(commonLoadernull){// no config file, default to this loader - we might be in a single env.commonLoaderthis.getClass().getClassLoader();}catalinaLoadercreateClassLoader(server,commonLoader);sharedLoadercreateClassLoader(shared,commonLoader);}catch(Throwablet){handleThrowable(t);log.error(Class loader creation threw exception,t);System.exit(1);}}至此main() 方法第一部分内容讲清楚了。setAwait该方法其实就是反射调用 Catalina 类实例 catalinaDaemon 的 setAwait() 方法而 Catalina 的 setAwait() 方法也很简单就是将其成员变量 await 设置为入参值这里默认为 true。同样daemon.load(args) 和 daemon.start() 也是分别利用反射调用 Catalina 的 load() 和 start() 方法这个待到讲 Catalina 时再说。总结Bootstrap 类作用就是创建并初始化三个类加载器为 Catalina 类实例创建及其方法调用做准备。然后调用 Catalina 的方法将执行流程引入到 Catalina 中。过程中使用自带的 CatalinaProperties、ClassLoaderFactory及其内部静类 Repository 等类replace、getPaths等方法完成了配置的读取有兴趣的可以看看。

最新新闻

日新闻

周新闻

月新闻