Tomcat启动类加载机制总结
1 背景知识:
(1) Tomcat和CataLina是什么关系?
Tomcat的前身为Catalina,Catalina又是一个轻量级的Servlet容器。
在互联网兴起之初,当时的Sun公司(后面被Oracle收购)已然看到了这次机遇,于是设计出了Applet来对Web应用的支持。不过事实却并不是预期那么得好,Sun悲催地发现Applet并没有给业界带来多大的影响。经过反思,Sun就想既然机遇出现了,市场前景也非常不错,总不能白白放弃了呀,怎么办呢?于是又投入精力去搞一套规范出来,这时Servlet诞生了!
一个Servlet主要做下面三件事情:
- 创建并填充Request对象,包括:URI、参数、method、请求头信息、请求体信息等
- 创建Response对象
- 执行业务逻辑,将结果通过Response的输出流输出到客户端
Servlet没有main方法,所以,如果要执行,则需要在一个容器里面才能执行,这个容器就是为了支持Servlet的功能而存在,Tomcat其实就是一个Servlet容器的实现。
(3) Tomcat总体架构
从组件的角度看
-
Server: 表示服务器,它提供了一种优雅的方式来启动和停止整个系统,不必单独启停连接器和容器;它是Tomcat构成的顶级构成元素,所有一切均包含在Server中;
-
Service: 表示服务,Server可以运行多个服务。比如一个Tomcat里面可运行订单服务、支付服务、用户服务等等;Server的实现类StandardServer可以包含一个到多个Services, Service的实现类为StandardService调用了容器(Container)接口,其实是调用了Servlet Engine(引擎),而且StandardService类中也指明了该Service归属的Server;
-
Container: 表示容器,可以看做Servlet容器;引擎(Engine)、主机(Host)、上下文(Context)和Wraper均继承自Container接口,所以它们都是容器。
- Engine -- 引擎
- Host -- 主机
- Context -- 上下文
- Wrapper -- 包装器
-
Connector: 表示连接器, 它将Service和Container连接起来,首先它需要注册到一个Service,它的作用就是把来自客户端的请求转发到Container(容器),这就是它为什么称作连接器, 它支持的协议如下:
- 支持AJP协议
- 支持Http协议
- 支持Https协议
-
Service内部还有各种支撑组件,下面简单罗列一下这些组件
- Manager -- 管理器,用于管理会话Session
- Logger -- 日志器,用于管理日志
- Loader -- 加载器,和类加载有关,只会开放给Context所使用
- Pipeline -- 管道组件,配合Valve实现过滤器功能
- Valve -- 阀门组件,配合Pipeline实现过滤器功能
- Realm -- 认证授权组件
从源码的设计角度看
-
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编译运行所必需的。
从一个完整请求的角度来看
假设来自客户的请求为: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
2 Tomcat启动过程
直接看时序图:
我们可以看到,Tomcat启动就是从main方法开始。我们下载Tomcat的二进制源码文件,通过ant进行build生成源文件。我们把生成的源文件bootstrap.jar和cataline.jar进行解压可以看到源代码。
Tomcat的main方法在org.apache.catalina.startup.Bootstrap 里
首先我们来看bootstap的main方法:
1 public static void main(String[] args) { 2 synchronized(daemonLock) { 3 if (daemon == null) { 4 Bootstrap bootstrap = new Bootstrap(); 5 6 try { 7 bootstrap.init(); 8 } catch (Throwable var5) { 9 handleThrowable(var5); 10 var5.printStackTrace(); 11 return; 12 } 13 14 daemon = bootstrap; 15 } else { 16 Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); 17 } 18 } 19 20 try { 21 String command = "start"; 22 if (args.length > 0) { 23 command = args[args.length - 1]; 24 } 25 26 if (command.equals("startd")) { 27 args[args.length - 1] = "start"; 28 daemon.load(args); 29 daemon.start(); 30 } else if (command.equals("stopd")) { 31 args[args.length - 1] = "stop"; 32 daemon.stop(); 33 } else if (command.equals("start")) { 34 daemon.setAwait(true); 35 daemon.load(args); 36 daemon.start(); 37 if (null == daemon.getServer()) { 38 System.exit(1); 39 } 40 } else if (command.equals("stop")) { 41 daemon.stopServer(args); 42 } else if (command.equals("configtest")) { 43 daemon.load(args); 44 if (null == daemon.getServer()) { 45 System.exit(1); 46 } 47 48 System.exit(0); 49 } else { 50 log.warn("Bootstrap: command \"" + command + "\" does not exist."); 51 } 52 } catch (Throwable var7) { 53 Throwable t = var7; 54 if (var7 instanceof InvocationTargetException && var7.getCause() != null) { 55 t = var7.getCause(); 56 } 57 58 handleThrowable(t); 59 t.printStackTrace(); 60 System.exit(1); 61 }
核心流程就是Bootstrap 对象,调用它的 init 方法初始化,然后根据启动参数,分别调用 Bootstrap 对象的不同方法。
那么Init()方法做了什么呢?
public void init() throws Exception {
//初始化classloader,这就是本文的重点 this.initClassLoaders();
//设置当前线程的contextClassLoader为catalinaLoader Thread.currentThread().setContextClassLoader(this.catalinaLoader); SecurityClassLoad.securityClassLoad(this.catalinaLoader); if (log.isDebugEnabled()) { log.debug("Loading startup class"); } //通过cataLinaLoader加载CataLina,并初始化startupInstance对象 Class<?> startupClass = this.catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.getConstructor().newInstance(); if (log.isDebugEnabled()) { log.debug("Setting startup class properties"); } //通过反射调用setParentClassLoader方法 String methodName = "setParentClassLoader"; Class<?>[] paramTypes = new Class[]{Class.forName("java.lang.ClassLoader")}; Object[] paramValues = new Object[]{this.sharedLoader}; Method method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); this.catalinaDaemon = startupInstance; }
我们首先看一下Tomcat到底初始化了哪些classLoader:
private Object catalinaDaemon = null; ClassLoader commonLoader = null; ClassLoader catalinaLoader = null; ClassLoader sharedLoader = null; public Bootstrap() { } private void initClassLoaders() { try { //初始化commomLoader this.commonLoader = this.createClassLoader("common", (ClassLoader)null); if (this.commonLoader == null) { this.commonLoader = this.getClass().getClassLoader(); } // catalinaLoader初始化, 父classloader是commonLoader this.catalinaLoader = this.createClassLoader("server", this.commonLoader); // sharedLoader初始化,父classloader是commonLoader this.sharedLoader = this.createClassLoader("shared", this.commonLoader); } catch (Throwable var2) { handleThrowable(var2); log.error("Class loader creation threw exception", var2); System.exit(1); } }
如何创建classLoader的?
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {
// 从 catalina.property文件里找 common.loader, shared.loader, server.loader 对应的值 String value = CatalinaProperties.getProperty(name + ".loader"); if (value != null && !value.equals("")) { value = this.replace(value);
//构造repositories列表 List<Repository> repositories = new ArrayList(); String[] repositoryPaths = getPaths(value); String[] var6 = repositoryPaths; int var7 = repositoryPaths.length; for(int var8 = 0; var8 < var7; ++var8) { String repository = var6[var8]; try { new URL(repository); repositories.add(new Repository(repository, RepositoryType.URL)); } catch (MalformedURLException var11) { 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)); } } } //再将Repository 列表传入ClassLoaderFactory.createClassLoader 方法 return ClassLoaderFactory.createClassLoader(repositories, parent); } else { return parent; } }
我们再看一下ClassLoaderFactory.createClassLoader 方法做了什么:
public static ClassLoader createClassLoader(List<ClassLoaderFactory.Repository> repositories, final ClassLoader parent) throws Exception { if (log.isDebugEnabled()) { log.debug("Creating new class loader"); } Set<URL> set = new LinkedHashSet(); if (repositories != null) { Iterator var3 = repositories.iterator(); label93: while(true) { while(true) { if (!var3.hasNext()) { break label93; } ClassLoaderFactory.Repository repository = (ClassLoaderFactory.Repository)var3.next(); if (repository.getType() == ClassLoaderFactory.RepositoryType.URL) { URL url = buildClassLoaderUrl(repository.getLocation()); if (log.isDebugEnabled()) { log.debug(" Including URL " + url); } set.add(url); } else { File directory; URL url; if (repository.getType() == ClassLoaderFactory.RepositoryType.DIR) { directory = new File(repository.getLocation()); directory = directory.getCanonicalFile(); if (validateFile(directory, ClassLoaderFactory.RepositoryType.DIR)) { url = buildClassLoaderUrl(directory); if (log.isDebugEnabled()) { log.debug(" Including directory " + url); } set.add(url); } } else if (repository.getType() == ClassLoaderFactory.RepositoryType.JAR) { directory = new File(repository.getLocation()); directory = directory.getCanonicalFile(); if (validateFile(directory, ClassLoaderFactory.RepositoryType.JAR)) { url = buildClassLoaderUrl(directory); if (log.isDebugEnabled()) { log.debug(" Including jar file " + url); } set.add(url); } } else if (repository.getType() == ClassLoaderFactory.RepositoryType.GLOB) { directory = new File(repository.getLocation()); directory = directory.getCanonicalFile(); if (validateFile(directory, ClassLoaderFactory.RepositoryType.GLOB)) { if (log.isDebugEnabled()) { log.debug(" Including directory glob " + directory.getAbsolutePath()); } String[] filenames = directory.list(); if (filenames != null) { String[] var7 = filenames; int var8 = filenames.length; for(int var9 = 0; var9 < var8; ++var9) { String s = var7[var9]; String filename = s.toLowerCase(Locale.ENGLISH); if (filename.endsWith(".jar")) { File file = new File(directory, s); file = file.getCanonicalFile(); if (validateFile(file, ClassLoaderFactory.RepositoryType.JAR)) { if (log.isDebugEnabled()) { log.debug(" Including glob jar file " + file.getAbsolutePath()); } URL url = buildClassLoaderUrl(file); set.add(url); } } } } } } } } } } final URL[] array = (URL[])set.toArray(new URL[0]); if (log.isDebugEnabled()) { for(int i = 0; i < array.length; ++i) { log.debug(" location " + i + " is " + array[i]); } } return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() { public URLClassLoader run() { return parent == null ? new URLClassLoader(array) : new URLClassLoader(array, parent); } });
去掉主要逻辑,此方法的Repository 列表就是这个URLClassLoader 可以加在的类的路径。我们直接看返回值,返回的是
new URLClassLoader(array)
也就是返回相应的类加载器:
public class URLClassLoader extends SecureClassLoader implements Closeable { ... public URLClassLoader(URL[] urls, ClassLoader parent) { super(parent); // this is to make the stack depth consistent with 1.1 SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } this.acc = AccessController.getContext(); ucp = new URLClassPath(urls, acc); } ... }
public URLClassLoader(URL[] urls) {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
ucp = new URLClassPath(urls, acc);
}
public class SecureClassLoader extends ClassLoader {...}
此时我们就分析到最底端了。我们回过头看三个类加载器,其实可以在catalina.property文件里找到相应的配置
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" server.loader= shared.loader=
java.util.Object
,它的实现可能有一定的危险性或者隐藏的bug。而我们知道Java自带的核心类里面也有java.util.Object
,如果JVM启动的时候先行加载的是我们自己编写的java.util.Object
,那么就有可能出现安全问题!
所以上述类是这么加载的
- Java自带的核心类 -- 由启动类加载器加载
- Java支持的可扩展类 -- 由扩展类加载器加载
- 我们自己编写的类 -- 默认由应用程序类加载器或其子类加载。
但是双亲委派模型也有一定的问题,就是在java核心类中,有SPI(Service Provider Interface)接口,它需要第三方实现类进行支持。如果使用双亲委派模型,那么第三方实现类也需要放在Java核心类里面才可以,不然的话第三方实现类将不能被加载使用。这显然是不合理的。为此有了上下文加载器
public void setContextClassLoader(ClassLoader cl) public ClassLoader getContextClassLoader()

有点想设计模式里面的装饰器思想,Tomcat类加载器是在原有的基础上增加了功能。
- Catalina类加载器,负责加载Tomcat专用的类,而这些被加载的类在Web应用中将不可见
- Shared类加载器,负责加载Tomcat下所有的Web应用程序都复用的类,而这些被加载的类在Tomcat中将不可见
- WebApp类加载器,负责加载具体的某个Web应用程序所使用到的类,而这些被加载的类在Tomcat和其他的Web应用程序都将不可见
- Jsp类加载器,每个jsp页面一个类加载器,不同的jsp页面有不同的类加载器,方便实现jsp页面的热插拔
为什么要分别设计类加载器呢?其实就是为了隔离。
我们先来看一个例子
package solutions; import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class ClassLoaderDemo { public static void main(String[] args) throws Exception { MyClassLoader loader = new MyClassLoader(); //使用MyClassLoader加载cl.test.A Class aClazz = loader.loadClass("solutions.A"); Object a = aClazz.newInstance(); Method getB = aClazz.getMethod("getB", null); Object b = getB.invoke(a, null); System.out.println(a.getClass().getClassLoader()); System.out.println(b.getClass().getClassLoader()); } } class MyClassLoader extends URLClassLoader{ //该路径是一个非classpath路径 public static File file = new File("D:\\myproject\\demo\\testClass"); //MyClassLoader从上面的非classpath路径加载类 public MyClassLoader() throws Exception { super(new URL[] {file.toURL()}, null, null); } }
package solutions; public class A { B b = new B(); public B getB() { return b; } } class B { }
根据输出结果可以知道:
如果一个类由某个类加载器加载,那么因为使用该类(本例中A)而加载的类
(本例中B)也会使用加载同一个类加载器
Servlet
启动的,我们在定义自己的Servlet
时可能会使用其他的三方类库,比如MyBatis。结合着上面的例子可以知道,如果我们在加载Servlet
时使用的是一个自定义的ClassLoader
类实例,那么该Servlet
中引用的三方类库,如:MyBatis也会由该ClassLoader
加载。显然是不符合Tomcat的要求的。所以怎么解决隔离的要求呢?ClassLoader
实例,那么这样就做到了不同Web App的隔离。
接下来我们来看Tomcat是如何加载web app目录里的web app的。
(4)StandardContext实例化
package org.apache.catalina.startup; public void start() { if (log.isDebugEnabled()) { log.debug(sm.getString("hostConfig.start")); } try { ObjectName hostON = this.host.getObjectName(); this.oname = new ObjectName(hostON.getDomain() + ":type=Deployer,host=" + this.host.getName()); Registry.getRegistry((Object)null, (Object)null).registerComponent(this, this.oname, this.getClass().getName()); } catch (Exception var2) { log.warn(sm.getString("hostConfig.jmx.register", new Object[]{this.oname}), var2); } if (!this.host.getAppBaseFile().isDirectory()) { log.error(sm.getString("hostConfig.appBase", new Object[]{this.host.getName(), this.host.getAppBaseFile().getPath()})); this.host.setDeployOnStartup(false); this.host.setAutoDeploy(false); } if (this.host.getDeployOnStartup()) { this.deployApps(); } }
protected void deployApps() {
File appBase = this.host.getAppBaseFile();
File configBase = this.host.getConfigBaseFile();
String[] filteredAppPaths = this.filterAppPaths(appBase.list());
//Deploy XML descriptors from configBase
this.deployDescriptors(configBase, configBase.list());
//Deploy wars
this.deployWARs(appBase, filteredAppPaths);
//Deploy expanded folders
//我们主要看如何部署webapps里面的应用
this.deployDirectories(appBase, filteredAppPaths);
}
protected void deployDirectories(File appBase, String[] files) { if (files != null) { ExecutorService es = this.host.getStartStopExecutor(); List<Future<?>> results = new ArrayList(); String[] var5 = files; int var6 = files.length; //对于webapps下面的所有目录,进行依次部署,一般一个目录表示一个单独的web app for(int var7 = 0; var7 < var6; ++var7) { String file = var5[var7]; if (!file.equalsIgnoreCase("META-INF") && !file.equalsIgnoreCase("WEB-INF")) { File dir = new File(appBase, file); if (dir.isDirectory()) { ContextName cn = new ContextName(file, false); if (!this.isServiced(cn.getName()) && !this.deploymentExists(cn.getName())) { //并行部署多个web app,DeployDirectory构造函数第一个参数为HostConfig,这里传的是this
results.add(es.submit(new HostConfig.DeployDirectory(this, cn, dir))); } } } } Iterator var12 = results.iterator(); while(var12.hasNext()) { Future result = (Future)var12.next(); try { result.get(); } catch (Exception var11) { log.error(sm.getString("hostConfig.deployDir.threaded.error"), var11); } } } }
我们再来看看DeployDirectory的定义:
private static class DeployDirectory implements Runnable { private HostConfig config; private ContextName cn; private File dir; public DeployDirectory(HostConfig config, ContextName cn, File dir) { this.config = config; this.cn = cn; this.dir = dir; } public void run() { //这里调用HostConfig的deployDirectory进行单个web app this.config.deployDirectory(this.cn, this.dir); } }
我们来看deployDirectioy()方法:
protected void deployDirectory(ContextName cn, File dir) { long startTime = 0L; if (log.isInfoEnabled()) {
startTime = System.currentTimeMillis(); log.info(sm.getString("hostConfig.deployDir", new Object[]{dir.getAbsolutePath()})); } Context context = null; File xml = new File(dir, "META-INF/context.xml"); File xmlCopy = new File(this.host.getConfigBaseFile(), cn.getBaseName() + ".xml"); boolean copyThisXml = this.isCopyXML(); boolean deployThisXML = this.isDeployThisXML(dir, cn); HostConfig.DeployedApplication deployedApp; try { if (deployThisXML && xml.exists()) { synchronized(this.digesterLock) { try {
//最主要的代码就是这里,这里会创建一个StandardContext类实例,也就是每个web app
//都会对应一个StandardContext实例 context = (Context)this.digester.parse(xml); } catch (Exception var26) { log.error(sm.getString("hostConfig.deployDescriptor.error", new Object[]{xml}), var26); context = new FailedContext(); } finally { this.digester.reset(); if (context == null) { context = new FailedContext(); } } } if (!copyThisXml && context instanceof StandardContext) { copyThisXml = ((StandardContext)context).getCopyXML(); } if (copyThisXml) { Files.copy(xml.toPath(), xmlCopy.toPath()); ((Context)context).setConfigFile(xmlCopy.toURI().toURL()); } else { ((Context)context).setConfigFile(xml.toURI().toURL()); } } else if (!deployThisXML && xml.exists()) { log.error(sm.getString("hostConfig.deployDescriptor.blocked", new Object[]{cn.getPath(), xml, xmlCopy})); context = new FailedContext(); } else {
//contextClass = org.apache.catalina.core.StandardContext
//同样是实例化一个StandardContext的实例
context = (Context)Class.forName(this.contextClass).getConstructor().newInstance(); } ... }
也就是说,每个web app都会对应一个StandardContext实例。
接下来我们可以看下StandardContext与类加载机制的StandardContext.startInternal()方法。
package org.apache.catalina.core; protected synchronized void startInternal() throws LifecycleException { if (log.isDebugEnabled()) { log.debug("Starting " + this.getBaseName()); } //1.发布正在启动的JMX通知,这样可以通过NotificationListener来监听Web应用的启动。 if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.state.starting", this.getObjectName(), this.sequenceNumber.getAndIncrement()); this.broadcaster.sendNotification(notification); } this.setConfigured(false); boolean ok = true;//2.启动当前维护的JNDI资源。(哼,不懂JNDI) if (this.namingResources != null) { this.namingResources.start(); } //3.初始化临时工作目录,即设置的workDir,默认为$CATALINA-BASE/work/<Engine名称>/<Host名称>/<Context名称>
。 this.postWorkDirectory();
//4.初始化当前Context使用的WebResouceRoot并启动。
//WebResouceRoot维护了Web应用所以的资源集合(Class文件、Jar包以及其他资源文件),主要用于类加载器和按照路径查找资源文件。
if (this.getResources() == null) { if (log.isDebugEnabled()) { log.debug("Configuring default Resources"); } try { this.setResources(new StandardRoot(this)); } catch (IllegalArgumentException var21) { log.error(sm.getString("standardContext.resourcesInit"), var21); ok = false; } } if (ok) { this.resourcesStart(); } //5.创建Web应用类加载器webappLoader,webappLoader继承自LifecycleMBeanBase,
//每个StandardContext对象都持有一个WebappLoader对象,也就是自己的
//类加载器,所有该StandardContext加载的三方类和其他StandardContext
加载的三方类是隔离的
//getParentClassLoader返回的parentClassLoader是其父类加载器
//是由CopyParentClassLoaderRule.begin中配置的,通过digester
//注入的,实现share加载
if (this.getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(); webappLoader.setDelegate(this.getDelegate()); this.setLoader(webappLoader); } //6如果没有设置Cookie处理器,默认为Rfc6265CookieProcessor。 if (this.cookieProcessor == null) { this.cookieProcessor = new Rfc6265CookieProcessor(); } //7设置字符集映射,用于根据Locale获取字符集编码。 this.getCharsetMapper(); boolean dependencyCheck = true; //8web应用的依赖检测 try { dependencyCheck = ExtensionValidator.validateApplication(this.getResources(), this); } catch (IOException var20) { log.error(sm.getString("standardContext.extensionValidationError"), var20); dependencyCheck = false; } if (!dependencyCheck) { ok = false; } //9 NamingContextListener注册 String useNamingProperty = System.getProperty("catalina.useNaming"); if (useNamingProperty != null && useNamingProperty.equals("false")) { this.useNaming = false; } if (ok && this.isUseNaming() && this.getNamingContextListener() == null) { NamingContextListener ncl = new NamingContextListener(); ncl.setName(this.getNamingContextName()); ncl.setExceptionOnFailedWrite(this.getJndiExceptionOnFailedWrite()); this.addLifecycleListener(ncl); this.setNamingContextListener(ncl); } if (log.isDebugEnabled()) { log.debug("Processing standard container startup"); } ClassLoader oldCCL = this.bindThread(); //10启动Web应用类加载器,此时真正创建出ParallelWebappClassLoader实例 try { if (ok) { Loader loader = this.getLoader(); if (loader instanceof Lifecycle) { ((Lifecycle)loader).start(); } this.setClassLoaderProperty("clearReferencesRmiTargets", this.getClearReferencesRmiTargets()); this.setClassLoaderProperty("clearReferencesStopThreads", this.getClearReferencesStopThreads()); this.setClassLoaderProperty("clearReferencesStopTimerThreads", this.getClearReferencesStopTimerThreads()); this.setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread", this.getClearReferencesHttpClientKeepAliveThread()); this.setClassLoaderProperty("clearReferencesObjectStreamClassCaches", this.getClearReferencesObjectStreamClassCaches()); this.setClassLoaderProperty("clearReferencesObjectStreamClassCaches", this.getClearReferencesObjectStreamClassCaches()); this.setClassLoaderProperty("clearReferencesThreadLocals", this.getClearReferencesThreadLocals()); this.unbindThread(oldCCL); oldCCL = this.bindThread(); this.logger = null; this.getLogger();
//11启动安全组件 Realm realm = this.getRealmInternal(); if (null != realm) { if (realm instanceof Lifecycle) { ((Lifecycle)realm).start(); } CredentialHandler safeHandler = new CredentialHandler() { public boolean matches(String inputCredentials, String storedCredentials) { return StandardContext.this.getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials); } public String mutate(String inputCredentials) { return StandardContext.this.getRealmInternal().getCredentialHandler().mutate(inputCredentials); } }; this.context.setAttribute("org.apache.catalina.CredentialHandler", safeHandler); } //12发布configure_start事件,ContextConfig监听该实践以完成Servlet的创建
this.fireLifecycleEvent("configure_start", (Object)null);
Container[] var31 = this.findChildren();
int var8 = var31.length;
//13.启动Context子节点Wrapper。
for(int var9 = 0; var9 < var8; ++var9) {
Container child = var31[var9];
if (!child.getState().isAvailable()) {
child.start();
}
}
//14启动Context的pipeline。
if (this.pipeline instanceof Lifecycle) {
((Lifecycle)this.pipeline).start();
}
//15创建会话管理器
Manager contextManager = null;
Manager manager = this.getManager();
if (manager == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.cluster.noManager", new Object[]{this.getCluster() != null, this.distributable}));
}
if (this.getCluster() != null && this.distributable) {
try {
contextManager = this.getCluster().createManager(this.getName());
} catch (Exception var19) {
log.error(sm.getString("standardContext.cluster.managerError"), var19);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}
//16将Context的Web资源集合添加到ServletContext。
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager", new Object[]{contextManager.getClass().getName()}));
}
this.setManager((Manager)contextManager);
}
if (manager != null && this.getCluster() != null && this.distributable) {
this.getCluster().registerManager(manager);
}
}
if (!this.getConfigured()) {
log.error(sm.getString("standardContext.configurationFail"));
ok = false;
}
//17创建实例管理器instanceManager,用于创建对象实例,如Servlet、Filter等。
if (ok) {
this.getServletContext().setAttribute("org.apache.catalina.resources", this.getResources());
if (this.getInstanceManager() == null) {
this.setInstanceManager(this.createInstanceManager());
}
//18将Jar包扫描器添加到ServletContext
this.getServletContext().setAttribute(InstanceManager.class.getName(), this.getInstanceManager());
InstanceManagerBindings.bind(this.getLoader().getClassLoader(), this.getInstanceManager());
this.getServletContext().setAttribute(JarScanner.class.getName(), this.getJarScanner());
this.getServletContext().setAttribute("org.apache.catalina.webappVersion", this.getWebappVersion());
}
//19合并参数
this.mergeParameters();
Iterator var27 = this.initializers.entrySet().iterator();
while(var27.hasNext()) {
Entry entry = (Entry)var27.next();
//20启动添加到Context的ServletContainerInitializer。
try {
((ServletContainerInitializer)entry.getKey()).onStartup((Set)entry.getValue(), this.getServletContext());
} catch (ServletException var22) {
log.error(sm.getString("standardContext.sciFail"), var22);
ok = false;
break;
}
}
//21实例化应用类监听器ApplicationListener。
if (ok && !this.listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
if (ok) {
this.checkConstraintsForUncoveredMethods(this.findConstraints());
}
//22启动会话管理器
try {
Manager manager = this.getManager();
if (manager instanceof Lifecycle) {
((Lifecycle)manager).start();
}
} catch (Exception var18) {
log.error(sm.getString("standardContext.managerFail"), var18);
ok = false;
}
//23实例化FilterConfig、Filter并调用Filter.init()
if (ok && !this.filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
//24对于loadOnStartup大于等于0的Wrapper,调用Wrapper.load(),该方法负责实例化Servlet,并调用Servlet.init()进行初始化
if (ok && !this.loadOnStartup(this.findChildren())) {
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
//25启动后台定时处理程序,只有backgroundProcessorDelay>0才启动,用于监控守护文件的变更。
super.threadStart();
} finally {
this.unbindThread(oldCCL);
}
if (ok) {
if (log.isDebugEnabled()) {
log.debug("Starting completed");
}
} else {
log.error(sm.getString("standardContext.startFailed", new Object[]{this.getName()}));
}
//26发布正在运行的JMX通知。
this.startTime = System.currentTimeMillis();
Notification notification;
if (ok && this.getObjectName() != null) {
notification = new Notification("j2ee.state.running", this.getObjectName(), this.sequenceNumber.getAndIncrement());
this.broadcaster.sendNotification(notification);
}
//27释放资源,如关闭jar文件
this.getResources().gc();
if (!ok) {//28设置Context状态
this.setState(LifecycleState.FAILED);
if (this.getObjectName() != null) {
notification = new Notification("j2ee.object.failed", this.getObjectName(), this.sequenceNumber.getAndIncrement());
this.broadcaster.sendNotification(notification);
}
} else {
this.setState(LifecycleState.STARTING);
}
//3.初始化临时工作目录,即设置的workDir,默认为$CATALINA-BASE/work/<Engine名称>/<Host名称>/<Context名称>
。
private void postWorkDirectory() {
String workDir = this.getWorkDir();
String engineName;
if (workDir == null || workDir.length() == 0) {
String hostName = null;
engineName = null;
String hostWorkDir = null;
Container parentHost = this.getParent();
if (parentHost != null) {
hostName = parentHost.getName();
if (parentHost instanceof StandardHost) {
hostWorkDir = ((StandardHost)parentHost).getWorkDir();
}
Container parentEngine = parentHost.getParent();
if (parentEngine != null) {
engineName = parentEngine.getName();
}
}
if (hostName == null || hostName.length() < 1) {
hostName = "_";
}
if (engineName == null || engineName.length() < 1) {
engineName = "_";
}
String temp = this.getBaseName();
if (temp.startsWith("/")) {
temp = temp.substring(1);
}
temp = temp.replace('/', '_');
temp = temp.replace('\\', '_');
if (temp.length() < 1) {
temp = "ROOT";
}
if (hostWorkDir != null) {
workDir = hostWorkDir + File.separator + temp;
} else {
workDir = "work" + File.separator + engineName + File.separator + hostName + File.separator + temp;
}
this.setWorkDir(workDir);
}
File dir = new File(workDir);
if (!dir.isAbsolute()) {
engineName = null;
try {
engineName = this.getCatalinaBase().getCanonicalPath();
dir = new File(engineName, workDir);
} catch (IOException var7) {
log.warn(sm.getString("standardContext.workCreateException", new Object[]{workDir, engineName, this.getName()}), var7);
}
}
if (!dir.mkdirs() && !dir.isDirectory()) {
log.warn(sm.getString("standardContext.workCreateFail", new Object[]{dir, this.getName()}));
}
if (this.context == null) {
this.getServletContext();
}
this.context.setAttribute("javax.servlet.context.tempdir", dir);
this.context.setAttributeReadOnly("javax.servlet.context.tempdir");
}
//5同时webappLoader提供了backgroundProcess方法,用于Context后台处理,
//当检测到Web应用的类文件、Jar包发生变化时,重新加载Context。
public void backgroundProcess() {
if (reloadable && modified()) {
try {
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader());
if (context != null) {
context.reload();
}
} finally {
if (context != null && context.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
}
}
//7.设置字符集映射,用于根据Locale获取字符集编码。
public CharsetMapper getCharsetMapper() {
// Create a mapper the first time it is requested
if (this.charsetMapper == null) {
try {
Class<?> clazz = Class.forName(charsetMapperClass);
this.charsetMapper = (CharsetMapper) clazz.getConstructor().newInstance();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
this.charsetMapper = new CharsetMapper();
}
}
return this.charsetMapper;
}
//19合并参数
private void mergeParameters() {
Map<String, String> mergedParams = new HashMap();
String[] names = this.findParameters();
String[] var3 = names;
int var4 = names.length;
int var5;
for(var5 = 0; var5 < var4; ++var5) {
String s = var3[var5];
mergedParams.put(s, this.findParameter(s));
}
ApplicationParameter[] params = this.findApplicationParameters();
ApplicationParameter[] var9 = params;
var5 = params.length;
for(int var12 = 0; var12 < var5; ++var12) {
ApplicationParameter param = var9[var12];
if (param.getOverride()) {
if (mergedParams.get(param.getName()) == null) {
mergedParams.put(param.getName(), param.getValue());
}
} else {
mergedParams.put(param.getName(), param.getValue());
}
}
ServletContext sc = this.getServletContext();
Iterator var11 = mergedParams.entrySet().iterator();
while(var11.hasNext()) {
Entry<String, String> entry = (Entry)var11.next();
sc.setInitParameter((String)entry.getKey(), (String)entry.getValue());
}
}
(5)servlet加载:
要介绍Servlet
加载首先要看下StandardWrapper
类,StandardWrapper
是Servlet的封装,在web.xml中配置的每个servlet-class都会对应一个StandardWrapper
,StandardWrapper.servletClass
(String类型,还未加载)对应其servlet-class
具体配置。
不管是在启动时加载Servlet
还是在第一个请求到来时加载Servlet
都会调用StandardWrapper.load
方法。
在介绍StandardWrapper.load
方法之前,我们首先看下InstanceManager
,每个StandardContext
都会持有一个InstanceManager
实例,StandardContext.InstanceManager
会在StandardContext.startInternal
中实例化,默认的InstanceManager
实现是DefaultInstanceManager
,DefaultInstanceManager
会持有一个ClassLoader
实例,该ClassLoader
实例其实就是StandardContext
持有的WebappLoader.classLoader。
public DefaultInstanceManager(Context context, Map<String, Map<String, String>> injectionMap, org.apache.catalina.Context catalinaContext, ClassLoader containerClassLoader) {
//获取standardContext持有的webapploader.classLoader
this.classLoader = catalinaContext.getLoader().getClassLoader(); this.privileged = catalinaContext.getPrivileged(); this.containerClassLoader = containerClassLoader;//是加载StandardContext类的类加载器 this.ignoreAnnotations = catalinaContext.getIgnoreAnnotations(); Log log = catalinaContext.getLogger(); Set<String> classNames = new HashSet(); loadProperties(classNames, "org/apache/catalina/core/RestrictedServlets.properties", "defaultInstanceManager.restrictedServletsResource", log); loadProperties(classNames, "org/apache/catalina/core/RestrictedListeners.properties", "defaultInstanceManager.restrictedListenersResource", log); loadProperties(classNames, "org/apache/catalina/core/RestrictedFilters.properties", "defaultInstanceManager.restrictedFiltersResource", log); this.restrictedClasses = Collections.unmodifiableSet(classNames); this.context = context; this.injectionMap = injectionMap; this.postConstructMethods = catalinaContext.findPostConstructMethods(); this.preDestroyMethods = catalinaContext.findPreDestroyMethods(); }
我们看一下StandardWrapper.load方法:
public synchronized void load() throws ServletException { this.instance = this.loadServlet(); if (!this.instanceInitialized) { this.initServlet(this.instance); } if (this.isJspServlet) { StringBuilder oname = new StringBuilder(this.getDomain()); oname.append(":type=JspMonitor"); oname.append(this.getWebModuleKeyProperties()); oname.append(",name="); oname.append(this.getName()); oname.append(this.getJ2EEKeyProperties()); try { this.jspMonitorON = new ObjectName(oname.toString()); Registry.getRegistry((Object)null, (Object)null).registerComponent(this.instance, this.jspMonitorON, (String)null); } catch (Exception var3) { this.log.warn(sm.getString("standardWrapper.jspMonitorError", new Object[]{this.instance})); } } } public synchronized Servlet loadServlet() throws ServletException { if (!this.singleThreadModel && this.instance != null) { return this.instance; } else { PrintStream out = System.out; if (this.swallowOutput) { SystemLogHandler.startCapture(); } boolean var12 = false; Servlet servlet; try { var12 = true; long t1 = System.currentTimeMillis(); if (this.servletClass == null) { this.unavailable((UnavailableException)null); throw new ServletException(sm.getString("standardWrapper.notClass", new Object[]{this.getName()})); } //获取StandardContext持有的InstanceManager对象实例 InstanceManager instanceManager = ((StandardContext)this.getParent()).getInstanceManager(); //加载servlet try { servlet = (Servlet)instanceManager.newInstance(this.servletClass); } catch (ClassCastException var13) { this.unavailable((UnavailableException)null); throw new ServletException(sm.getString("standardWrapper.notServlet", new Object[]{this.servletClass}), var13); } catch (Throwable var14) { .....}
下面看DefaultInstanceManager.newInstance是如何实例化类的:
public Object newInstance(String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, SecurityException { //首先根据类名加载Class对象 Class<?> clazz = this.loadClassMaybePrivileged(className, this.classLoader); return this.newInstance(clazz.getConstructor().newInstance(), clazz); } protected Class<?> loadClassMaybePrivileged(String className, ClassLoader classLoader) throws ClassNotFoundException { Class clazz; if (SecurityUtil.isPackageProtectionEnabled()) { try { clazz = (Class)AccessController.doPrivileged(new DefaultInstanceManager.PrivilegedLoadClass(className, classLoader)); } catch (PrivilegedActionException var6) { Throwable t = var6.getCause(); if (t instanceof ClassNotFoundException) { throw (ClassNotFoundException)t; } throw new RuntimeException(t); } } else { //实际调用loadClass方法 clazz = this.loadClass(className, classLoader); } this.checkAccess(clazz); return clazz; } protected Class<?> loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException { //如果是Tomcat内部类,则只使用containerClassLoader尝试加载 //containerClassLoader是构造函数中传入的加载StandardContext类的加载器 //这是和其他StandardContext共用的加载器 if (className.startsWith("org.apache.catalina")) { return this.containerClassLoader.loadClass(className); } else { try { //如果不是Tomcat内部类,同样先使用containerClassLoader进行加载 //所以Servlet中引用的三方类会先使用share版本 Class<?> clazz = this.containerClassLoader.loadClass(className); if (ContainerServlet.class.isAssignableFrom(clazz)) { return clazz; } } catch (Throwable var4) { ExceptionUtils.handleThrowable(var4); } //如果不是上述情景,则使用该StandardContext自己的类加载器进行加载 return classLoader.loadClass(className); } }
StandardContext
都持有一个自己的ClassLoader
实例。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?