Tomcat的架构设计和启动过程详解
事实却并不是预期那么得好Sun悲催地发现Applet并没有给业界带来多大的影响。经过反思Sun就想既然机遇出现了市场前景也非常不错总不能白白放弃了呀怎么办呢于是又投入精力去搞一套规范出来这时Servlet诞生了一个Servlet主要做下面三件事情创建并填充Request对象包括URI、参数、method、请求头信息、请求体信息等创建Response对象执行业务逻辑将结果通过Response的输出流输出到客户端Servlet没有main方法所以如果要执行则需要在一个容器里面才能执行这个容器就是为了支持Servlet的功能而存在Tomcat其实就是一个Servlet容器的实现。核心架构设Tomcat 的架构设计以 模块化、分层、解耦 为核心遵循 Java Servlet 规范同时支持高性能、高扩展的 Web 服务。其整体架构可概括为 “连接器Connector- 容器Container” 双层模型并通过 Lifecycle 生命周期管理机制 和 责任链模式Pipeline-Valve 实现组件协同。Tomcat的架构呈“套娃式”嵌套Server → Service → (Connector Engine) → Host → Context → Wrapper核心架构组成Server代表整个 Tomcat 实例是顶级容器管理多个 Service。Service将一个或多个 Connector 与一个 Engine 绑定构成独立服务单元。Manager管理器用于管理会话SessionLogger日志器用于管理日志Loader加载器和类加载有关只会开放给Context所使用Pipeline管道组件配合Valve实现过滤器功能Valve阀门组件配合Pipeline实现过滤器功能Realm认证授权组件Connector连接器负责处理外部 HTTP/AJP 请求实现网络通信与协议解析。Container容器负责加载和管理 Servlet处理业务逻辑包含四级嵌套容器Engine处理所有请求每个 Service 仅有一个。Host虚拟主机对应一个域名或 IP。ContextWeb 应用上下文对应一个 WAR 包或目录。Wrapper最底层容器封装单个 Servlet。从web.xml配置和模块对应角度上述模块的理解不是孤立的它可以直接映射为Tomcat的web.xml配置让我们联系起来看xmlServer port8005 shutdownSHUTDOWN Listener classNameorg.apache.catalina.startup.VersionLoggerListener / Listener classNameorg.apache.catalina.core.AprLifecycleListener SSLEngineon / Listener classNameorg.apache.catalina.core.JreMemoryLeakPreventionListener / Listener classNameorg.apache.catalina.mbeans.GlobalResourcesLifecycleListener / Listener classNameorg.apache.catalina.core.ThreadLocalLeakPreventionListener / GlobalNamingResources Resource nameUserDatabase authContainer typeorg.apache.catalina.UserDatabase descriptionUser database that can be updated and saved factoryorg.apache.catalina.users.MemoryUserDatabaseFactory pathnameconf/tomcat-users.xml / /GlobalNamingResources Service nameCatalina Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 / Engine nameCatalina defaultHostlocalhost Realm classNameorg.apache.catalina.realm.LockOutRealm Realm classNameorg.apache.catalina.realm.UserDatabaseRealm resourceNameUserDatabase/ /Realm Host namelocalhost appBasewebapps unpackWARstrue autoDeploytrue Valve classNameorg.apache.catalina.valves.AccessLogValve directorylogs prefixlocalhost_access_log suffix.txt pattern%h %l %u %t quot;%rquot; %s %b / /Host /Engine /Service /Server从一个完整请求的角度来看通过一个完整的HTTP请求我们还需要把它贯穿起来假设来自客户的请求为http://localhost:8080/test/index.jsp 请求被发送到本机端口8080被在那里侦听的Coyote HTTP/1.1 Connector,然后Connector把该请求交给它所在的Service的Engine来处理并等待Engine的回应Engine获得请求localhost:8080/test/index.jsp匹配它所有虚拟主机HostEngine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理因为该Host被定义为该Engine的默认主机)localhost Host获得请求/test/index.jsp匹配它所拥有的所有ContextHost匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为的Context去处理)path/test的Context获得请求/index.jsp在它的mapping table中寻找对应的servletContext匹配到URL PATTERN为*.jsp的servlet对应于JspServlet类构造HttpServletRequest对象和HttpServletResponse对象作为参数调用JspServlet的doGet或doPost方法Context把执行完了之后的HttpServletResponse对象返回给HostHost把HttpServletResponse对象返回给EngineEngine把HttpServletResponse对象返回给ConnectorConnector把HttpServletResponse对象返回给客户browser从源码的设计角度看从功能的角度将Tomcat源代码分成5个子模块分别是:Jsper模: 这个子模块负责jsp页面的解析、jsp属性的验证同时也负责将jsp页面动态转换为java代码并编译成class文件。在Tomcat源代码中凡是属于org.apache.jasper包及其子包中的源代码都属于这个子模块;Servlet和Jsp模块: 这个子模块的源代码属于javax.servlet包及其子包如我们非常熟悉的javax.servlet.Servlet接口、javax.servet.http.HttpServlet类及javax.servlet.jsp.HttpJspPage就位于这个子模块中;Catalina模块: 这个子模块包含了所有以org.apache.catalina开头的java源代码。该子模块的任务是规范了Tomcat的总体架构定义了Server、Service、Host、Connector、Context、Session及Cluster等关键组件及这些组件的实现这个子模块大量运用了Composite设计模式。同时也规范了Catalina的启动及停止等事件的执行流程。从代码阅读的角度看这个子模块应该是我们阅读和学习的重点。Connector模块: 如果说上面三个子模块实现了Tomcat应用服务器的话那么这个子模块就是Web服务器的实现。所谓连接器(Connector)就是一个连接客户和应用服务器的桥梁它接收用户的请求并把用户请求包装成标准的Http请求(包含协议名称请求头Head请求方法是Get还是Post等等)。同时这个子模块还按照标准的Http协议负责给客户端发送响应页面比如在请求页面未发现时connector就会给客户端浏览器发送标准的Http 404错误响应页面。Resource模块: 这个子模块包含一些资源文件如Server.xml及Web.xml配置文件。严格说来这个子模块不包含java源代码但是它还是Tomcat编译运行所必需的。从后续深入理解的角度我们看完上述组件结构后后续应该重点从哪些角度深入理解Tomcat呢基于组件的架构我们知道组成Tomcat的是各种各样的组件每个组件各司其职组件与组件之间有明确的职责划分同时组件与组件之间又通过一定的联系相互通信。Tomcat整体就是一个个组件的堆砌基于JMX我们在后续阅读Tomcat源码的时候会发现代码里充斥着大量的类似于下面的代码。highlighter- JavaRegistry.getRegistry(null, null).invoke(mbeans, init, false); Registry.getRegistry(null, null).invoke(mbeans, start, false);而这实际上就是通过JMX来管理相应对象的代码。这儿我们不会详细讲述什么是JMX我们只是简单地说明一下JMX的概念参考JMX百度百科。JMXJava Management Extensions即Java管理扩展是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议灵活的开发无缝集成的系统、网络和服务管理应用。基于生命周期如果我们查阅各个组件的源代码会发现绝大多数组件实现了Lifecycle接口这也就是我们所说的基于生命周期。生命周期的各个阶段的触发又是基于事件的方式。启动过程详解总体流程我们看下整体的初始化和启动的流程在理解的时候可以直接和Tomcat架构设计中组件关联上启动过程代码浅析看了下网上关于Tomcat的文章很多直接关注在纯代码的分析这种是很难的我建议你一定要把代码加载进来自己看一下然后这里我把它转化为核心的几个问题来帮助你理解。Bootstrap主入口Tomcat源码就从它的main方法开始。Tomcat的main方法在org.apache.catalina.startup.Bootstrap 里。让我们带着这个为看下Catalina的初始化的java/** * 初始化守护进程 * * throws Exception Fatal initialization error */ public void init() throws Exception { // 初始化classloader包括catalinaLoader下文将具体分析 initClassLoaders(); // 设置当前的线程的contextClassLoader为catalinaLoader Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); // 通过catalinaLoader加载Catalina并初始化startupInstance 对象 if (log.isDebugEnabled()) log.debug(Loading startup class); Class? startupClass catalinaLoader.loadClass(org.apache.catalina.startup.Catalina); Object startupInstance startupClass.getConstructor().newInstance(); // 通过反射调用了setParentClassLoader 方法 if (log.isDebugEnabled()) log.debug(Setting startup class properties); String methodName setParentClassLoader; Class? paramTypes[] new Class[1]; paramTypes[0] Class.forName(java.lang.ClassLoader); Object paramValues[] new Object[1]; paramValues[0] sharedLoader; Method method startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); catalinaDaemon startupInstance; }通过上面几行关键代码的注释我们就可以看出Catalina是如何初始化的。这里还留下一个问题tomcat为什么要初始化不同的classloader呢我们将在下文进行详解。Bootstrap如何初始化Catalina的我们用Sequence Diagram插件来看main方法的时序图但是可以发现它并没有帮我们画出Bootstrap初始化Catalina的过程这和上面的组件初始化不符合让我们带着这个为看下Catalina的初始化的java/** * 初始化守护进程 * * throws Exception Fatal initialization error */ public void init() throws Exception { // 初始化classloader包括catalinaLoader下文将具体分析 initClassLoaders(); // 设置当前的线程的contextClassLoader为catalinaLoader Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); // 通过catalinaLoader加载Catalina并初始化startupInstance 对象 if (log.isDebugEnabled()) log.debug(Loading startup class); Class? startupClass catalinaLoader.loadClass(org.apache.catalina.startup.Catalina); Object startupInstance startupClass.getConstructor().newInstance(); // 通过反射调用了setParentClassLoader 方法 if (log.isDebugEnabled()) log.debug(Setting startup class properties); String methodName setParentClassLoader; Class? paramTypes[] new Class[1]; paramTypes[0] Class.forName(java.lang.ClassLoader); Object paramValues[] new Object[1]; paramValues[0] sharedLoader; Method method startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); catalinaDaemon startupInstance; }通过上面几行关键代码的注释我们就可以看出Catalina是如何初始化的。这里还留下一个问题tomcat为什么要初始化不同的classloader呢我们将在下文进行详解。启动过程类加载机制详解Tomcat初始化了哪些classloader在Bootstrap中我们可以看到有如下三个classloaderjavaClassLoader commonLoader null; ClassLoader catalinaLoader null; ClassLoader sharedLoader null;如何初始化的呢javaprivate void initClassLoaders() { try { // commonLoader初始化 commonLoader createClassLoader(common, null); if (commonLoader null) { // no config file, default to this loader - we might be in a single env. commonLoader this.getClass().getClassLoader(); } // catalinaLoader初始化, 父classloader是commonLoader catalinaLoader createClassLoader(server, commonLoader); // sharedLoader初始化 sharedLoader createClassLoader(shared, commonLoader); } catch (Throwable t) { handleThrowable(t); log.error(Class loader creation threw exception, t); System.exit(1); } }可以看出catalinaLoader 和 sharedLoader 的 parentClassLoader 是 commonLoader。如何创建classLoader的不妨再看下如何创建的javaprivate ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { String value CatalinaProperties.getProperty(name .loader); if ((value null) || (value.equals())) return parent; value replace(value); ListRepository repositories new ArrayList(); String[] repositoryPaths getPaths(value); for (String repository : repositoryPaths) { // Check for a JAR URL repository try { SuppressWarnings(unused) URL url new URL(repository); repositories.add(new Repository(repository, RepositoryType.URL)); continue; } catch (MalformedURLException e) { // Ignore } // Local repository if (repository.endsWith(*.jar)) { repository repository.substring (0, repository.length() - *.jar.length()); repositories.add(new Repository(repository, RepositoryType.GLOB)); } else if (repository.endsWith(.jar)) { repositories.add(new Repository(repository, RepositoryType.JAR)); } else { repositories.add(new Repository(repository, RepositoryType.DIR)); } } return ClassLoaderFactory.createClassLoader(repositories, parent); }方法的逻辑也比较简单就是从 catalina.property文件里找 common.loader, shared.loader, server.loader 对应的值然后构造成Repository 列表再将Repository 列表传入ClassLoaderFactory.createClassLoader 方法ClassLoaderFactory.createClassLoader 返回的是 URLClassLoader而Repository 列表就是这个URLClassLoader 可以加在的类的路径。 在catalina.property文件里javacommon.loader${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar server.loader shared.loader其中 shared.loader, server.loader 是没有值的createClassLoader 方法里如果没有值的话就返回传入的 parent ClassLoader也就是说commonLoader,catalinaLoader,sharedLoader 其实是一个对象。在Tomcat之前的版本里这三个是不同的URLClassLoader对象。javaClass? startupClass catalinaLoader.loadClass(org.apache.catalina.startup.Catalina); Object startupInstance startupClass.getConstructor().newInstance();初始化完三个ClassLoader对象后init() 方法就使用 catalinaClassLoader 加载了org.apache.catalina.startup.Catalina 类并创建了一个对象然后通过反射调用这个对象的 setParentClassLoader 方法传入的参数是 sharedClassLoader。最后吧这个 Catania 对象复制给 catalinaDaemon 属性。深入理解可以复习下类加载机制的基础解密类加载机制深入理解JVM如何加载你的代码什么是类加载机制Java是一门面向对象的语言而对象又必然依托于类。类要运行必须首先被加载到内存。我们可以简单地把类分为几类Java自带的核心类Java支持的可扩展类我们自己编写的类为什么要设计多个类加载器如果所有的类都使用一个类加载器来加载会出现什么问题呢假如我们自己编写一个类java.util.Object它的实现可能有一定的危险性或者隐藏的bug。而我们知道Java自带的核心类里面也有java.util.Object如果JVM启动的时候先行加载的是我们自己编写的java.util.Object那么就有可能出现安全问题所以Sun后被Oracle收购采用了另外一种方式来保证最基本的、也是最核心的功能不会被破坏。你猜的没错那就是双亲委派模式什么是双亲委派模型双亲委派模型解决了类错乱加载的问题也设计得非常精妙。双亲委派模式对类加载器定义了层级每个类加载器都有一个父类加载器。在一个类需要加载的时候首先委派给父类加载器来加载而父类加载器又委派给祖父类加载器来加载以此类推。如果父类及上面的类加载器都加载不了那么由当前类加载器来加载并将被加载的类缓存起来。所以上述类是这么加载的Java自带的核心类 -- 由启动类加载器加载Java支持的可扩展类 -- 由扩展类加载器加载我们自己编写的类 -- 默认由应用程序类加载器或其子类加载但它也不是万能的在有些场景也会遇到它解决不了的问题比如如下场景。双亲委派模型问题是如何解决的在Java核心类里面有SPIService Provider Interface它由Sun编写规范第三方来负责实现。SPI需要用到第三方实现类。如果使用双亲委派模型那么第三方实现类也需要放在Java核心类里面才可以不然的话第三方实现类将不能被加载使用。但是这显然是不合理的怎么办呢ContextClassLoader上下文类加载器就来解围了。在java.lang.Thread里面有两个方法get/set上下文类加载器javapublic void setContextClassLoader(ClassLoader cl) public ClassLoader getContextClassLoader()我们可以通过在SPI类里面调用getContextClassLoader来获取第三方实现类的类加载器。由第三方实现类通过调用setContextClassLoader来传入自己实现的类加载器, 这样就变相地解决了双亲委派模式遇到的问题。为什么Tomcat的类加载器也不是双亲委派模型我们知道Java默认的类加载机制是通过双亲委派模型来实现的而Tomcat实现的方式又和双亲委派模型有所区别。原因在于一个Tomcat容器允许同时运行多个Web程序每个Web程序依赖的类又必须是相互隔离的。因此如果Tomcat使用双亲委派模式来加载类的话将导致Web程序依赖的类变为共享的。举个例子假如我们有两个Web程序一个依赖A库的1.0版本另一个依赖A库的2.0版本他们都使用了类xxx.xx.Clazz其实现的逻辑因类库版本的不同而结构完全不同。那么这两个Web程序的其中一个必然因为加载的Clazz不是所使用的Clazz而出现问题而这对于开发来说是非常致命的Tomcat类加载机制是怎么样的呢既然Tomcat的类加载机器不同于双亲委派模式那么它又是一种怎样的模式呢我们在这里一定要看下官网提供的类加载的文档结合经典的类加载机制我们完整的看下Tomcat类加载图我们在这张图中看到很多类加载器除了Jdk自带的类加载器我们尤其关心Tomcat自身持有的类加载器。仔细一点我们很容易发现Catalina类加载器和Shared类加载器他们并不是父子关系而是兄弟关系。为啥这样设计我们得分析一下每个类加载器的用途才能知晓。Common类加载器负责加载Tomcat和Web应用都复用的类Catalina类加载器负责加载Tomcat专用的类而这些被加载的类在Web应用中将不可见Shared类加载器负责加载Tomcat下所有的Web应用程序都复用的类而这些被加载的类在Tomcat中将不可见WebApp类加载器负责加载具体的某个Web应用程序所使用到的类而这些被加载的类在Tomcat和其他的Web应用程序都将不可见Jsp类加载器每个jsp页面一个类加载器不同的jsp页面有不同的类加载器方便实现jsp页面的热插拔同样的我们可以看到通过ContextClassLoader上下文类加载器的setContextClassLoader来传入自己实现的类加载器javapublic void init() throws Exception { initClassLoaders(); // 看这里 Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); ...WebApp类加载器到这儿我们隐隐感觉到少分析了点什么没错就是WebApp类加载器。整个启动过程分析下来我们仍然没有看到这个类加载器。它又是在哪儿出现的呢我们知道WebApp类加载器是Web应用私有的而每个Web应用其实算是一个Context那么我们通过Context的实现类应该可以发现。在Tomcat中Context的默认实现为StandardContext我们看看这个类的startInternal()方法在这儿我们发现了我们感兴趣的WebApp类加载器。javaprotected synchronized void startInternal() throws LifecycleException { if (getLoader() null) { WebappLoader webappLoader new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); } }入口代码非常简单就是webappLoader不存在的时候创建一个并调用setLoader方法。我们接着分析setLoaderjavapublic void setLoader(Loader loader) { Lock writeLock loaderLock.writeLock(); writeLock.lock(); Loader oldLoader null; try { // Change components if necessary oldLoader this.loader; if (oldLoader loader) return; this.loader loader; // Stop the old component if necessary if (getState().isAvailable() (oldLoader ! null) (oldLoader instanceof Lifecycle)) { try { ((Lifecycle) oldLoader).stop(); } catch (LifecycleException e) { log.error(StandardContext.setLoader: stop: , e); } } // Start the new component if necessary if (loader ! null) loader.setContext(this); if (getState().isAvailable() (loader ! null) (loader instanceof Lifecycle)) { try { ((Lifecycle) loader).start(); } catch (LifecycleException e) { log.error(StandardContext.setLoader: start: , e); } } } finally { writeLock.unlock(); } // Report this property change to interested listeners support.firePropertyChange(loader, oldLoader, loader); }这儿我们感兴趣的就两行代码java((Lifecycle) oldLoader).stop(); // 旧的加载器停止 ((Lifecycle) loader).start(); // 新的加载器启动启动过程Catalina的加载Catalina的引入通过前面我们知道了Tomcat的类加载机制和整体的组件加载流程我们也知道通过Bootstrap初始化的catalinaClassLoader加载了Catalina那么进而引入了一个问题就是Catalina是如何加载的呢加载了什么呢先回顾下整个流程和我们分析的阶段看下Bootstrap中Load的过程java/** * 加载守护进程 */ private void load(String[] arguments) throws Exception { // Call the load() method String methodName load; Object param[]; Class? paramTypes[]; if (argumentsnull || arguments.length0) { paramTypes null; param null; } else { paramTypes new Class[1]; paramTypes[0] arguments.getClass(); param new Object[1]; param[0] arguments; } Method method catalinaDaemon.getClass().getMethod(methodName, paramTypes); if (log.isDebugEnabled()) { log.debug(Calling startup class method); } method.invoke(catalinaDaemon, param);// 本质上就是调用catalina的load方法 }Catalina的加载上一步我们知道catalina load的触发因为有参数所以是load(String[])方法。我们进而看下这个load方法做了什么load(String[])本质上还是调用了load方法java/* * Load using arguments */ public void load(String args[]) { try { if (arguments(args)) { // 处理命令行的参数 load(); } } catch (Exception e) { e.printStackTrace(System.out); } }load加载过程本质上是初始化Server的实例java/** * Start a new server instance. */ public void load() { // 如果已经加载则退出 if (loaded) { return; } loaded true; long t1 System.nanoTime(); // 已经弃用 initDirs(); // Before digester - it may be needed initNaming(); // 解析 server.xml parseServerXml(true); Server s getServer(); if (s null) { return; } getServer().setCatalina(this); getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); // Stream redirection initStreams(); // 启动Server try { getServer().init(); } catch (LifecycleException e) { if (Boolean.getBoolean(org.apache.catalina.startup.EXIT_ON_INIT_FAILURE)) { throw new java.lang.Error(e); } else { log.error(sm.getString(catalina.initError), e); } } if(log.isInfoEnabled()) { log.info(sm.getString(catalina.init, Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1)))); } }总体流程如下initDirs已经弃用了Tomcat10会删除这个方法。java/** * deprecated unused. Will be removed in Tomcat 10 onwards. */ Deprecated protected void initDirs() { }initNaming设置额外的系统变量javaprotected void initNaming() { // Setting additional variables if (!useNaming) { log.info(sm.getString(catalina.noNaming)); System.setProperty(catalina.useNaming, false); } else { System.setProperty(catalina.useNaming, true); String value org.apache.naming; String oldValue System.getProperty(javax.naming.Context.URL_PKG_PREFIXES); if (oldValue ! null) { value value : oldValue; } System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value); if( log.isDebugEnabled() ) { log.debug(Setting naming prefix value); } value System.getProperty (javax.naming.Context.INITIAL_CONTEXT_FACTORY); if (value null) { System.setProperty (javax.naming.Context.INITIAL_CONTEXT_FACTORY, org.apache.naming.java.javaURLContextFactory); } else { log.debug(INITIAL_CONTEXT_FACTORY already set value ); } } }Server.xml的解析分三大块下面的代码还是很清晰的:javaprotected void parseServerXml(boolean start) { // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile())); File file configFile(); if (useGeneratedCode !Digester.isGeneratedCodeLoaderSet()) { // Load loader String loaderClassName generatedCodePackage .DigesterGeneratedCodeLoader; try { Digester.GeneratedCodeLoader loader (Digester.GeneratedCodeLoader) Catalina.class.getClassLoader().loadClass(loaderClassName).newInstance(); Digester.setGeneratedCodeLoader(loader); } catch (Exception e) { if (log.isDebugEnabled()) { log.info(sm.getString(catalina.noLoader, loaderClassName), e); } else { log.info(sm.getString(catalina.noLoader, loaderClassName)); } // No loader so dont use generated code useGeneratedCode false; } } // 初始化server.xml的位置 File serverXmlLocation null; String xmlClassName null; if (generateCode || useGeneratedCode) { xmlClassName start ? generatedCodePackage .ServerXml : generatedCodePackage .ServerXmlStop; } if (generateCode) { if (generatedCodeLocationParameter ! null) { generatedCodeLocation new File(generatedCodeLocationParameter); if (!generatedCodeLocation.isAbsolute()) { generatedCodeLocation new File(Bootstrap.getCatalinaHomeFile(), generatedCodeLocationParameter); } } else { generatedCodeLocation new File(Bootstrap.getCatalinaHomeFile(), work); } serverXmlLocation new File(generatedCodeLocation, generatedCodePackage); if (!serverXmlLocation.isDirectory() !serverXmlLocation.mkdirs()) { log.warn(sm.getString(catalina.generatedCodeLocationError, generatedCodeLocation.getAbsolutePath())); // Disable code generation generateCode false; } } // 用 SAXParser 来解析 xml解析完了之后xml 里定义的各种标签就有对应的实现类对象了 ServerXml serverXml null; if (useGeneratedCode) { serverXml (ServerXml) Digester.loadGeneratedClass(xmlClassName); } if (serverXml ! null) { serverXml.load(this); } else { try (ConfigurationSource.Resource resource ConfigFileLoader.getSource().getServerXml()) { // Create and execute our Digester Digester digester start ? createStartDigester() : createStopDigester(); InputStream inputStream resource.getInputStream(); InputSource inputSource new InputSource(resource.getURI().toURL().toString()); inputSource.setByteStream(inputStream); digester.push(this); if (generateCode) { digester.startGeneratingCode(); generateClassHeader(digester, start); } digester.parse(inputSource); if (generateCode) { generateClassFooter(digester); try (FileWriter writer new FileWriter(new File(serverXmlLocation, start ? ServerXml.java : ServerXmlStop.java))) { writer.write(digester.getGeneratedCode().toString()); } digester.endGeneratingCode(); Digester.addGeneratedClass(xmlClassName); } } catch (Exception e) { log.warn(sm.getString(catalina.configFail, file.getAbsolutePath()), e); if (file.exists() !file.canRead()) { log.warn(sm.getString(catalina.incorrectPermissions)); } } }