Tomcat启动类加载机制总结

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 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 repositories, final ClassLoader parent) throws Exception {

if (log.isDebugEnabled()) {

log.debug("Creating new class loader");

}

Set 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() {

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> 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///

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 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 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> injectionMap, org.apache.catalina.Context catalinaContext, ClassLoader containerClassLoader) {

//获取standardContext持有的webapploader.classLoaderthis.classLoader = catalinaContext.getLoader().getClassLoader();

this.privileged = catalinaContext.getPrivileged();

this.containerClassLoader = containerClassLoader;//是加载StandardContext类的类加载器

this.ignoreAnnotations = catalinaContext.getIgnoreAnnotations();

Log log = catalinaContext.getLogger();

Set 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);

}

}

总结:本文主要介绍tomcat整体架构并深入分析了Tomcat类加载机制和类隔离原理,即每个StandardContext都持有一个自己的ClassLoader实例。

参考文档:

1 Tomcat - 启动过程:类加载机制详解 | Java 全栈知识体系 (pdai.tech)

2 https://www.jianshu.com/p/eba6d4227ddf

3 https://www.jianshu.com/p/bb943f64e4ba

相关推荐