假设来自客户的请求为: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
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
if (log.isDebugEnabled()) {
log.debug("Creating new class loader");
}
Set
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
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=
其中 shared.loader, server.loader 是没有值的,createClassLoader 方法里如果没有值的话,就返回传入的 parent ClassLoader,也就是说,commonLoader,catalinaLoader,sharedLoader 其实是一个对象。
初始化完三个ClassLoader对象后,init() 方法就使用 catalinaClassLoader 加载了org.apache.catalina.startup.Catalina 类,并创建了一个对象,然后通过反射调用这个对象的 setParentClassLoader 方法,传入的参数是 sharedClassLoader。最后吧这个 Catania 对象复制给 catalinaDaemon 属性。
3 深入理解Tomcat类加载机制:
(1)什么是类加载器?
Java是一门面向对象的语言,而对象又必然依托于类。类要运行,必须首先被加载到内存,这就需要类加载器。类加载器又有多种,比如加载java自带的核心类,加载java支持的可扩展类,加载我们程序员自己编写的类。那么为什么要设计多个类加载器呢?主要是因为安全问题。如果自己编写的类也用一个加载器进行加载的话,那么它的实现可能有一定的危险性和隐藏的安全性。
假如我们自己编写一个类java.util.Object,它的实现可能有一定的危险性或者隐藏的bug。而我们知道Java自带的核心类里面也有java.util.Object,如果JVM启动的时候先行加载的是我们自己编写的java.util.Object,那么就有可能出现安全问题!
为了保证java最基本的、最核心的功能不会被破坏,java采用双亲委派模型。
(2)双亲委派模型:
双亲委派模式对类加载器定义了层级,每个类加载器都有一个父类加载器。在一个类需要加载的时候,首先委派给父类加载器来加载,而父类加载器又委派给祖父类加载器来加载,以此类推。如果父类及上面的类加载器都加载不了,那么才会由当前类加载器来加载,并将被加载的类缓存起来。
所以上述类是这么加载的
Java自带的核心类 -- 由启动类加载器加载
Java支持的可扩展类 -- 由扩展类加载器加载
我们自己编写的类 -- 默认由应用程序类加载器或其子类加载。
但是双亲委派模型也有一定的问题,就是在java核心类中,有SPI(Service Provider Interface)接口,它需要第三方实现类进行支持。如果使用双亲委派模型,那么第三方实现类也需要放在Java核心类里面才可以,不然的话第三方实现类将不能被加载使用。这显然是不合理的。为此有了上下文加载器
public void setContextClassLoader(ClassLoader cl)
public ClassLoader getContextClassLoader()
我们可以通过在SPI类里面调用getContextClassLoader来获取第三方实现类的类加载器。由第三方实现类通过调用setContextClassLoader来传入自己实现的类加载器, 这样就变相地解决了双亲委派模式遇到的问题。
(3)Tomcat的类加载器
Tomcat的类加载器和双亲委派模型有所差别,主要是因为在于一个Tomcat容器允许同时运行多个Web程序,每个Web程序依赖的类又必须是相互隔离的。因此,如果Tomcat使用双亲委派模式来加载类的话,将导致Web程序依赖的类变为共享的。我们看一下tomcat的类加载器:
有点想设计模式里面的装饰器思想,Tomcat类加载器是在原有的基础上增加了功能。
Common类加载器,负责加载Tomcat和Web应用都复用的类
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)也会使用加载同一个类加载器
其实在Tomcat中,基本上一个Web App所有的操作都是由一个Servlet启动的,我们在定义自己的Servlet时可能会使用其他的三方类库,比如MyBatis。结合着上面的例子可以知道,如果我们在加载Servlet时使用的是一个自定义的ClassLoader类实例,那么该Servlet中引用的三方类库,如:MyBatis也会由该ClassLoader加载。显然是不符合Tomcat的要求的。所以怎么解决隔离的要求呢?
我们为每个Web App都专门实例化一个ClassLoader实例,那么这样就做到了不同Web App的隔离。
因为我们知道Java中一个类是由该类名称以及该类的defining loader定义的。
接下来我们来看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
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/
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,
//在其启动后会去创建Web应用类加载器(ParallelWebappClassLoader)。
//每个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/
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
(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
//获取standardContext持有的webapploader.classLoaderthis.classLoader = catalinaContext.getLoader().getClassLoader();
this.privileged = catalinaContext.getPrivileged();
this.containerClassLoader = containerClassLoader;//是加载StandardContext类的类加载器
this.ignoreAnnotations = catalinaContext.getIgnoreAnnotations();
Log log = catalinaContext.getLogger();
Set
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);
}
}
总结:本文主要介绍tomcat整体架构并深入分析了Tomcat类加载机制和类隔离原理,即每个StandardContext都持有一个自己的ClassLoader实例。
参考文档:
1 Tomcat - 启动过程:类加载机制详解 | Java 全栈知识体系 (pdai.tech)
2 https://www.jianshu.com/p/eba6d4227ddf
3 https://www.jianshu.com/p/bb943f64e4ba