事实却并不是预期那么得好,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:管理器,用于管理会话Session
- Logger:日志器,用于管理日志
- Loader:加载器,和类加载有关,只会开放给Context所使用
- Pipeline:管道组件,配合Valve实现过滤器功能
- Valve:阀门组件,配合Pipeline实现过滤器功能
- Realm:认证授权组件
- Connector(连接器):负责处理外部 HTTP/AJP 请求,实现网络通信与协议解析。
- Container(容器):负责加载和管理 Servlet,处理业务逻辑,包含四级嵌套容器:
- Engine:处理所有请求,每个 Service 仅有一个。
- Host:虚拟主机,对应一个域名或 IP。
- Context:Web 应用上下文,对应一个 WAR 包或目录。
- Wrapper:最底层容器,封装单个 Servlet。
从web.xml配置和模块对应角度
上述模块的理解不是孤立的,它可以直接映射为Tomcat的web.xml配置,让我们联系起来看
xml
<Server port="8005" shutdown="SHUTDOWN"> <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> <GlobalNamingResources> <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" /> </GlobalNamingResources> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Engine name="Catalina" defaultHost="localhost"> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %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,匹配它所有虚拟主机Host
- Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
- localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context
- Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为""的Context去处理)
- path="/test"的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet
- Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类,构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
- Context把执行完了之后的HttpServletResponse对象返回给Host
- Host把HttpServletResponse对象返回给Engine
- Engine把HttpServletResponse对象返回给Connector
- Connector把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- Java
Registry.getRegistry(null, null).invoke(mbeans, "init", false); Registry.getRegistry(null, null).invoke(mbeans, "start", false);
而这实际上就是通过JMX来管理相应对象的代码。这儿我们不会详细讲述什么是JMX,我们只是简单地说明一下JMX的概念,参考JMX百度百科。
JMX(Java 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中我们可以看到有如下三个classloader
java
ClassLoader commonLoader = null; ClassLoader catalinaLoader = null; ClassLoader sharedLoader = null;
如何初始化的呢?
java
private 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的?
不妨再看下如何创建的?
java
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; value = replace(value); List<Repository> 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文件里
java
common.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对象。
java
Class<?> 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核心类里面有SPI(Service Provider Interface),它由Sun编写规范,第三方来负责实现。SPI需要用到第三方实现类。如果使用双亲委派模型,那么第三方实现类也需要放在Java核心类里面才可以,不然的话第三方实现类将不能被加载使用。但是这显然是不合理的!怎么办呢?
ContextClassLoader(上下文类加载器)就来解围了。
在java.lang.Thread里面有两个方法,get/set上下文类加载器
java
public 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来传入自己实现的类加载器
java
public void init() throws Exception { initClassLoaders(); // 看这里 Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); ...WebApp类加载器
到这儿,我们隐隐感觉到少分析了点什么!没错,就是WebApp类加载器。整个启动过程分析下来,我们仍然没有看到这个类加载器。它又是在哪儿出现的呢?
我们知道WebApp类加载器是Web应用私有的,而每个Web应用其实算是一个Context,那么我们通过Context的实现类应该可以发现。在Tomcat中,Context的默认实现为StandardContext,我们看看这个类的startInternal()方法,在这儿我们发现了我们感兴趣的WebApp类加载器。
java
protected synchronized void startInternal() throws LifecycleException { if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); } }入口代码非常简单,就是webappLoader不存在的时候创建一个,并调用setLoader方法。我们接着分析setLoader
java
public 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 (arguments==null || arguments.length==0) { 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
设置额外的系统变量
java
protected 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的解析
分三大块,下面的代码还是很清晰的:
java
protected 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 don't 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")); } } }