context的初始化没啥可分析的逻辑但是它所触发的初始化事件让ContextConfig做了一些事情
protected void org.apache.catalina.startup.ContextConfig.init() {
// Called from StandardContext.init()
//创建Digester
//(*1*)
Digester contextDigester = createContextDigester();
contextDigester.getParser();
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.init"));
}
context.setConfigured(false);//设置配置状态,默认设置为失败,以免被误任务成功
ok = true;
//(*2*)
contextConfig(contextDigester);
}
//(*1*)
protected Digester createContextDigester() {
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
HashMap, List> fakeAttributes = new HashMap<>();
ArrayList attrs = new ArrayList<>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
RuleSet contextRuleSet = new ContextRuleSet("", false);
//添加context规则组,这些规则组我们在分析catalina启动的时候已经分析过了,此处不再赘述
digester.addRuleSet(contextRuleSet);
RuleSet namingRuleSet = new NamingRuleSet("Context/");
//添加naming规则组,这些规则组我们在分析catalina启动的时候已经分析过了,此处不再赘述
digester.addRuleSet(namingRuleSet);
return digester;
}
//(*2*)
protected void contextConfig(Digester digester) {
String defaultContextXml = null;
// Open the default context.xml file, if it exists 如果存在默认的配置,使用它
if (context instanceof StandardContext) {
defaultContextXml = ((StandardContext)context).getDefaultContextXml();
}
// set the default if we don't have any overrides
//如果没有配置默认的,那么是用全局默认配置conf/context.xml
if (defaultContextXml == null) {
defaultContextXml = Constants.DefaultContextXml;
}
//如果还没有进行解析,那么就会重新解析,默认的全局配置-》configBase下的context.xml.default-》configBase下的配置
if (!context.getOverride()) {
File defaultContextFile = new File(defaultContextXml);
if (!defaultContextFile.isAbsolute()) {
defaultContextFile =
new File(context.getCatalinaBase(), defaultContextXml);
}
if (defaultContextFile.exists()) {
try {
URL defaultContextUrl = defaultContextFile.toURI().toURL();
//解析方式就是以digester注册的规则进行sax解析
processContextConfig(digester, defaultContextUrl);
} catch (MalformedURLException e) {
log.error(sm.getString(
"contextConfig.badUrl", defaultContextFile), e);
}
}
//读取host级别的context描述文件context.xml.default,host级别的配置会覆盖
//全局的配置
File hostContextFile = new File(getHostConfigBase(), Constants.HostContextXml);
if (hostContextFile.exists()) {
try {
URL hostContextUrl = hostContextFile.toURI().toURL();
processContextConfig(digester, hostContextUrl);
} catch (MalformedURLException e) {
log.error(sm.getString(
"contextConfig.badUrl", hostContextFile), e);
}
}
}
//读取自身配置的context文件,自身的配置文件优先级最高,会覆盖前面两个的设置
if (context.getConfigFile() != null) {
processContextConfig(digester, context.getConfigFile());
}
}
从上面的代码可以看到,ContextConfig对context的初始化事件主要是进行context描述文件的进一步处理,可能有人就会疑惑,那我们前面进行context发布的时候不是已经进行了context描述文件的读取了吗?是这样的,前面读取的配置描述文件只给Disgester添加了StandardContext的创建规则,所以不会完全进行读取。而且优先级为自身context描述文件优先级最高,再者就是host级别的,最后才是全局的context.xml
protected synchronized void beforeStart() {
try {
fixDocBase();
} catch (IOException e) {
log.error(sm.getString(
"contextConfig.fixDocBase", context.getName()), e);
}
antiLocking();
}
修复docBase,这个用来做什么的呢?就是变成指定的解压后的路径
protected void fixDocBase() throws IOException {
//host对象
Host host = (Host) context.getParent();
//获取appBase路径
File appBase = host.getAppBaseFile();
//获取docBase路径
String docBase = context.getDocBase();
if (docBase == null) {
// Trying to guess the docBase according to the path
//如果docBase为空,那么使用path进行代替
String path = context.getPath();
if (path == null) {
return;
}
//然后又来一次ContextName设置baseName,这部分逻辑前面分析过,这里就不再赘述了
ContextName cn = new ContextName(path, context.getWebappVersion());
//对于appBase目录下的,一般这个baseName就是目录名
docBase = cn.getBaseName();
}
File file = new File(docBase);
//设置成绝对路径
if (!file.isAbsolute()) {
docBase = (new File(appBase, docBase)).getPath();
} else {
docBase = file.getCanonicalPath();
}
file = new File(docBase);
String origDocBase = docBase;
ContextName cn = new ContextName(context.getPath(), context.getWebappVersion());
//对于appBase目录下的,一般这个baseName就是目录名,没有版本号
String pathName = cn.getBaseName();
boolean unpackWARs = true;
if (host instanceof StandardHost) {
unpackWARs = ((StandardHost) host).isUnpackWARs();
//使用子容器覆盖父容器的属性
if (unpackWARs && context instanceof StandardContext) {
unpackWARs = ((StandardContext) context).getUnpackWAR();
}
}
//判断这个context是否是在appBase路径内的
boolean docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);
if (docBase.toLowerCase(Locale.ENGLISH).endsWith(".war") && !file.isDirectory()) {
//通过File构建URL路径
URL war = UriUtil.buildJarUrl(new File(docBase));
if (unpackWARs) {
//解压到指定的pathName路径
docBase = ExpandWar.expand(host, war, pathName);
file = new File(docBase);
docBase = file.getCanonicalPath();
if (context instanceof StandardContext) {
//保存原始的路径,对于未解压的war包来说,原始的路径就是war的位置
((StandardContext) context).setOriginalDocBase(origDocBase);
}
} else {
//这个校验我们在前面分析过,就是用于校验路径是否为正确路径,不能存在./和../这种路径
ExpandWar.validate(host, war, pathName);
}
} else {
File docDir = new File(docBase);
File warFile = new File(docBase + ".war");
URL war = null;
//如果这个目录存在对应的war包,并且已经处在appBase路径,那么构建war包url,以便后面重新解压
if (warFile.exists() && docBaseInAppBase) {
war = UriUtil.buildJarUrl(warFile);
}
if (docDir.exists()) {
if (war != null && unpackWARs) {
// Check if WAR needs to be re-expanded (e.g. if it has
// changed). Note: HostConfig.deployWar() takes care of
// ensuring that the correct XML file is used.
// This will be a NO-OP if the WAR is unchanged.
//重新解压
ExpandWar.expand(host, war, pathName);
}
} else {
//如果目录不存在,存在war包,并且允许解压
if (war != null) {
if (unpackWARs) {
docBase = ExpandWar.expand(host, war, pathName);
file = new File(docBase);
docBase = file.getCanonicalPath();
} else {
docBase = warFile.getCanonicalPath();
//校验
ExpandWar.validate(host, war, pathName);
}
}
if (context instanceof StandardContext) {
//保存原始路径,以便恢复现场
((StandardContext) context).setOriginalDocBase(origDocBase);
}
}
}
// Re-calculate now docBase is a canonical path
//重新判断经过处理后的docBase是否还在appBase路径中
docBaseInAppBase = docBase.startsWith(appBase.getPath() + File.separatorChar);
//如果在appBase中,截取掉
if (docBaseInAppBase) {
docBase = docBase.substring(appBase.getPath().length());
docBase = docBase.replace(File.separatorChar, '/');
if (docBase.startsWith("/")) {
docBase = docBase.substring(1);
}
} else {
docBase = docBase.replace(File.separatorChar, '/');
}
//设置解压后的docBase或者原始不用解压的目录,以便后面解析web.xml能够找到
context.setDocBase(docBase);
}
具体的解压逻辑
public static String expand(Host host, URL war, String pathname)
throws IOException {
//打开对war的链接,准备读取
JarURLConnection juc = (JarURLConnection) war.openConnection();
//不允许缓存,以免造成文件锁定
juc.setUseCaches(false);
URL jarFileUrl = juc.getJarFileURL();
URLConnection jfuc = jarFileUrl.openConnection();
boolean success = false;
//将路径设置到appBase下面
File docBase = new File(host.getAppBaseFile(), pathname);
//构建/META-INF/war-tracker路径
File warTracker = new File(host.getAppBaseFile(), pathname + Constants.WarTracker);
long warLastModified = -1;
try (InputStream is = jfuc.getInputStream()) {
// Get the last modified time for the WAR
//获取war包的上次修改时间
warLastModified = jfuc.getLastModified();
}
// Check to see of the WAR has been expanded previously
//如果已经存在了
if (docBase.exists()) {
// A WAR was expanded. Tomcat will have set the last modified
// time of warTracker file to the last modified time of the WAR so
// changes to the WAR while Tomcat is stopped can be detected
//如果warTracker不存在或者它们没有发生修改,那么直接返回这个已经解压好的目录的路径即可
if (!warTracker.exists() || warTracker.lastModified() == warLastModified) {
// No (detectable) changes to the WAR
success = true;
return (docBase.getAbsolutePath());
}
//否则需要删除原来解压的文件,以便后面重新解压
// WAR must have been modified. Remove expanded directory.
log.info(sm.getString("expandWar.deleteOld", docBase));
if (!delete(docBase)) {
throw new IOException(sm.getString("expandWar.deleteFailed", docBase));
}
}
// Create the new document base directory
//创建解压目录
if(!docBase.mkdir() && !docBase.isDirectory()) {
throw new IOException(sm.getString("expandWar.createFailed", docBase));
}
// Expand the WAR into the new document base directory
String canonicalDocBasePrefix = docBase.getCanonicalPath();
if (!canonicalDocBasePrefix.endsWith(File.separator)) {
canonicalDocBasePrefix += File.separator;
}
// Creating war tracker parent (normally META-INF)
File warTrackerParent = warTracker.getParentFile();
//创建war tracker目录
if (!warTrackerParent.isDirectory() && !warTrackerParent.mkdirs()) {
throw new IOException(sm.getString("expandWar.createFailed", warTrackerParent.getAbsolutePath()));
}
try (JarFile jarFile = juc.getJarFile()) {
Enumeration jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String name = jarEntry.getName();
//解压后对应的文件名
File expandedFile = new File(docBase, name);
if (!expandedFile.getCanonicalPath().startsWith(
canonicalDocBasePrefix)) {
// Trying to expand outside the docBase
// Throw an exception to stop the deployment
throw new IllegalArgumentException(
sm.getString("expandWar.illegalPath",war, name,
expandedFile.getCanonicalPath(),
canonicalDocBasePrefix));
}
int last = name.lastIndexOf('/');
if (last >= 0) {
File parent = new File(docBase,
name.substring(0, last));
if (!parent.mkdirs() && !parent.isDirectory()) {
throw new IOException(
sm.getString("expandWar.createFailed", parent));
}
}
if (name.endsWith("/")) {
continue;
}
try (InputStream input = jarFile.getInputStream(jarEntry)) {
if (null == input) {
throw new ZipException(sm.getString("expandWar.missingJarEntry",
jarEntry.getName()));
}
// Bugzilla 33636
//解压
//(*1*)
expand(input, expandedFile);
long lastModified = jarEntry.getTime();
if ((lastModified != -1) && (lastModified != 0)) {
expandedFile.setLastModified(lastModified);
}
}
}
// Create the warTracker file and align the last modified time
// with the last modified time of the WAR
//创建war tracker,并设置修改时间为war包的时间,如果war包修改,那么就会和war tracker的不一样
warTracker.createNewFile();
warTracker.setLastModified(warLastModified);
success = true;
} catch (IOException e) {
throw e;
} finally {
if (!success) {
// If something went wrong, delete expanded dir to keep things
// clean
deleteDir(docBase);
}
}
// Return the absolute path to our new document base directory
//返回解压后的目录
return docBase.getAbsolutePath();
}
//(*1*)
private static void expand(InputStream input, File file) throws IOException {
//熟悉的流操作,不过多细说
try (BufferedOutputStream output =
new BufferedOutputStream(new FileOutputStream(file))) {
byte buffer[] = new byte[2048];
while (true) {
int n = input.read(buffer);
if (n <= 0)
break;
output.write(buffer, 0, n);
}
}
}
protected synchronized void startInternal() throws LifecycleException {
if(log.isDebugEnabled())
log.debug("Starting " + getBaseName());
// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(), sequenceNumber.getAndIncrement());
//触发通知
broadcaster.sendNotification(notification);
}
//设置配置状态为false,并触发属性变更事件
setConfigured(false);
boolean ok = true;
// Currently this is effectively a NO-OP but needs to be called to
// ensure the NamingResources follows the correct lifecycle
//启动命名资源,比如我们在context.xml配置的JNDI之类的资源
if (namingResources != null) {
namingResources.start();
}
// Post work directory
// 创建工作目录,一般为$tomcat_home/work/engine名(一般为catalina)/host名(如果是本机,那么就是localhost)
// /context的baseName(通常为path.substring(1)+"##"+版本)
//(*1*)
postWorkDirectory();
// Add missing components as necessary
//如果WebResourceRoot(一个资源的复合类,用于维护各种资源)为空,那么就new出一个来
if (getResources() == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources");
try {
//StandardRoot是WebResourceRoot子类
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
//启动注册资源到mbeanserver中,并判断是否需要添加
//WEB-INF/classas/META-INF/resources下的资源,并为其挂在到根路径下
//这样你存在WEB-INF/classas/META-INF/resources下的资源可以
//挂载到根路径下。这个webResourceRoot用于后面类加载器设置类加载路径
resourcesStart();
}
//如果没有设置类加载器,那么创建一个专属于context的类加载器
if (getLoader() == null) {
//父加载器是share加载器,但是一般地share加载器也会是common加载,
//通常catalina和share加载都是为空
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
//设置webapp加载器的加载模式,如果为true,那么首先使用父加载器进行加载
//如果为false,那么自己先加载
webappLoader.setDelegate(getDelegate());
//设置加载器,类加载器的分析往下看
setLoader(webappLoader);
}
。。。。。。省略部分代码
}
//设置工作目录
//(*1*)
private void postWorkDirectory() {
// Acquire (or calculate) the work directory path
String workDir = getWorkDir();
if (workDir == null || workDir.length() == 0) {
// Retrieve our parent (normally a host) name
String hostName = null;
String engineName = null;
String hostWorkDir = null;
//获取父容器
Container parentHost = getParent();
if (parentHost != null) {
//获取主机名
hostName = parentHost.getName();
if (parentHost instanceof StandardHost) {
//获取host工作目录,一般为 空
hostWorkDir = ((StandardHost)parentHost).getWorkDir();
}
//获取Engine容器
Container parentEngine = parentHost.getParent();
if (parentEngine != null) {
//获取Engine名
engineName = parentEngine.getName();
}
}
if ((hostName == null) || (hostName.length() < 1))
hostName = "_";
if ((engineName == null) || (engineName.length() < 1))
engineName = "_";
//通过前面设置的path与version构建baseName,这里面使用了一个contextName,但是和前面使用不是同一个构造器
//这个构造的路径是由path+##+version,如果path是/,ROOT,null那么这个path就被置为空字符串,拼接的路径
//会变成ROOT##版本
//这里假设当前context的请求路径是/tomcat/context,其版本为1.5,那么最终拼成的baseName就是tomcat#context##1.5
String temp = getBaseName();
if (temp.startsWith("/"))
temp = temp.substring(1);
temp = temp.replace('/', '_');
temp = temp.replace('\\', '_');
if (temp.length() < 1)
temp = ContextName.ROOT_NAME;
if (hostWorkDir != null ) {
//如果存在host的工作目录,那么以hostwork为前缀
workDir = hostWorkDir + File.separator + temp;
} else {
workDir = "work" + File.separator + engineName +
File.separator + hostName + File.separator + temp;//work/catalina/localhost/tomcat#context##1.5
}
//设置工作目录
setWorkDir(workDir);
}
// Create this directory if necessary
//创建目录
File dir = new File(workDir);
if (!dir.isAbsolute()) {
String catalinaHomePath = null;
try {
catalinaHomePath = getCatalinaBase().getCanonicalPath();//D:/tomcat/work/catalina/localhost/tomcat#context##1.5
dir = new File(catalinaHomePath, workDir);
} catch (IOException e) {
log.warn(sm.getString("standardContext.workCreateException",
workDir, catalinaHomePath, getName()), e);
}
}
if (!dir.mkdirs() && !dir.isDirectory()) {
log.warn(sm.getString("standardContext.workCreateFail", dir,
getName()));
}
// Set the appropriate servlet context attribute
//创建ApplicationContext,实际上获取到的是一个门面
if (context == null) {
getServletContext();
}
//将当前context的工作目录以key为javax.servlet.context.tempdir设置到ApplicationContext中
context.setAttribute(ServletContext.TEMPDIR, dir);
//将这个context的目录设置为只读属性
context.setAttributeReadOnly(ServletContext.TEMPDIR);
}
//(*1*)
public ServletContext getServletContext() {
if (context == null) {
//创建ApplicationContext,将当前context传入
context = new ApplicationContext(this);
if (altDDName != null)
context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
}
//获取门面对象,用于屏蔽一些不提供给外部的操作
return (context.getFacade());
}
从上面的代码中,我们看到tomcat为每个context设置了单独的类加载,那么这个类加载器到底是怎么设置进去的,加载顺序是如何控制的呢?
//父加载器是catalina加载器,但是一般地catalina加载器也回事common加载,
//通常catalina和share加载都是为空
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
//设置webapp加载器的加载模式,如果为true,那么首先使用父加载器进行加载
//如果为false,那么自己先加载
webappLoader.setDelegate(getDelegate());
//设置加载器,类加载器的分析往下看
setLoader(webappLoader);
我们看到context的setLoader方法
public void org.apache.catalina.core.StandardContext.setLoader(Loader loader) {
Lock writeLock = loaderLock.writeLock();
writeLock.lock();
Loader oldLoader = null;
try {
// Change components if necessary
oldLoader = this.loader;
//如果老加载器与新的加载器相等,那么直接返回
if (oldLoader == loader)
return;
this.loader = loader;
//停止旧的的加载器,主要是进行一些缓存,引用的线程进行释放。
// Stop the old component if necessary
if (getState().isAvailable() && (oldLoader != null) &&
(oldLoader instanceof Lifecycle)) {
try {
((Lifecycle) oldLoader).stop();
} catch (LifecycleException e) {
log.error("StandardContext.setLoader: stop: ", e);
}
}
// Start the new component if necessary
if (loader != null)
//关联当前context
loader.setContext(this);
if (getState().isAvailable() && (loader != null) &&
(loader instanceof Lifecycle)) {
try {
//启动类加载器
((Lifecycle) loader).start();
} catch (LifecycleException e) {
log.error("StandardContext.setLoader: start: ", e);
}
}
} finally {
writeLock.unlock();
}
// Report this property change to interested listeners
//触发属性变更事件
support.firePropertyChange("loader", oldLoader, loader);
}
我们可以看到,这个WebappLoader类没有继承JDK的Classloader类,它所拥有的方法中没有看到用于加载类的方法,带着疑问,我们继续查看WebappClassloader的start方法,无独有偶,WebappLoader继承了LifecycleMBeanBase类,这个类不再说了,就是注册MBean然后调用子类实现的startInternal方法
protected void org.apache.catalina.loader.WebappLoader.startInternal() throws LifecycleException {
if (log.isDebugEnabled())
log.debug(sm.getString("webappLoader.starting"));
//如果context没有配置WebResourceRoot,那么设置状态正在启动
if (context.getResources() == null) {
log.info("No resources for " + context);
//ContextCofig对starting不感兴趣
setState(LifecycleState.STARTING);
return;
}
// Construct a class loader based on our current repositories list
try {
//创建真正的类加载器
//(*1*)
classLoader = createClassLoader();
//设置资源,用于加载设置的类资源
classLoader.setResources(context.getResources());
//设置类加载模式
classLoader.setDelegate(this.delegate);
// Configure our repositories
//拼接context类加载器及其父加载器的类加载路径设置到ApplicationContext中,
//顺便说一声这个类加载器继承自URLClassLoader。
//(*2*)
setClassPath();
//当设置了JDK的安全管理器时,给类加载器设置资源的操作权限
//设置context的工作空间为可读可写,resouces设置的资源为只读等等。
setPermissions();
//(*3*)
//主要设置web应用的类加载器地址
((Lifecycle) classLoader).start();
//获取context的名字
String contextName = context.getName();
if (!contextName.startsWith("/")) {
contextName = "/" + contextName;
}
//注册mbean
ObjectName cloname = new ObjectName(context.getDomain() + ":type=" +
classLoader.getClass().getSimpleName() + ",host=" +
context.getParent().getName() + ",context=" + contextName);
Registry.getRegistry(null, null)
.registerComponent(classLoader, cloname, null);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
log.error( "LifecycleException ", t );
throw new LifecycleException("start: ", t);
}
//设置STARTING状态
setState(LifecycleState.STARTING);
}
//(*1*)
private WebappClassLoaderBase createClassLoader()
throws Exception {
//loaderClass = ParallelWebappClassLoader,继承自WebappClassLoaderBase
Class> clazz = Class.forName(loaderClass);
WebappClassLoaderBase classLoader = null;
//如果父加载器为空,重新获取父加载器,防御式编程
if (parentClassLoader == null) {
parentClassLoader = context.getParentClassLoader();
}
Class>[] argTypes = { ClassLoader.class };
Object[] args = { parentClassLoader };
//反射ParallelWebappClassLoader(ClassLoader parentClassLoader)构造器创建ParallelWebappClassLoader
Constructor> constr = clazz.getConstructor(argTypes);
classLoader = (WebappClassLoaderBase) constr.newInstance(args);
//返回
return classLoader;
}
//(*2*)
private void setClassPath() {
// Validate our current state information
//如果没有设置context,那么直接返回,没有context也就没有lib,classes
if (context == null)
return;
//获取ApplicationContext,如果ApplicationContext为空,返回
ServletContext servletContext = context.getServletContext();
if (servletContext == null)
return;
StringBuilder classpath = new StringBuilder();
// Assemble the class path information from our class loader chain
//获取上面创建的类加载器
ClassLoader loader = getClassLoader();
//如果加载模式为父加载器加载
if (delegate && loader != null) {
// Skip the webapp loader for now as delegation is enabled
loader = loader.getParent();
}
//那么从父加载器往上append各个类加载器的classpath,在Unix系统中使用:分割,在windows系统中用;分割
while (loader != null) {
if (!buildClassPath(classpath, loader)) {
break;
}
loader = loader.getParent();
}
//添加当前类加载器的加载路径
if (delegate) {
// Delegation was enabled, go back and add the webapp paths
loader = getClassLoader();
if (loader != null) {
buildClassPath(classpath, loader);
}
}
this.classpath = classpath.toString();
// Store the assembled class path as a servlet context attribute
//将当前context的类加载器可以加载到类的路径保存到ApplicationContext中,key为org.apache.catalina.jsp_classpath
servletContext.setAttribute(Globals.CLASS_PATH_ATTR, this.classpath);
}
//(*3*)
public void start() throws LifecycleException {
//将加载器的状态设置为STARTING_PREP
state = LifecycleState.STARTING_PREP;
//获取/WEB-INF/classes路径web资源对象
WebResource classes = resources.getResource("/WEB-INF/classes");
//将/WEB-INF/classes路径设置为类加载器路径
if (classes.isDirectory() && classes.canRead()) {
localRepositories.add(classes.getURL());
}
//获取/WEB-INF/lib下的所有jar包资源
WebResource[] jars = resources.listResources("/WEB-INF/lib");
for (WebResource jar : jars) {
if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
localRepositories.add(jar.getURL());
jarModificationTimes.put(
jar.getName(), Long.valueOf(jar.getLastModified()));
}
}
//将加载器的状态设置为STARTED
state = LifecycleState.STARTED;
}
接下来我们继续分析context的类加载器是如何加载class的
public Class> org.apache.catalina.loader.WebappClassLoaderBase.loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class> clazz = null;
// Log access to stopped class loader
//如果当前类加载的状态已经是停止状态,那么抛出错误,不允许再使用这个类加载加载类
checkStateForClassLoading(name);
// (0) Check our previously loaded local class cache
//从当前类加载的缓存中寻找已经加载过的类对象
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
//是否解析这个类,底层调用本地方法,用于验证,准备,解析一个class类
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.1) Check our previously loaded class cache
//通过JDK自带的本地方法查找jvm是否已经加载过这个类
clazz = findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return (clazz);
}
// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding Java SE classes. This implements
// SRV.10.7.2
//处理类名,比如com.java.Test -》 com/java/Test.class
String resourceName = binaryNameToPath(name, false);
//获取JDK系统加载器,这个加载器是在new当前这个类的对象时设置的
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
//通过类加载器的getResource查看是否存在这个资源,而不是使用loadClass,因为loadClass的方法
//相对于getResource来说,它消耗的性能是昂贵(想象一下,系统类加载器是双亲委派机制)。
tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
} catch (Throwable t) {
// Swallow all exceptions apart from those that must be re-thrown
ExceptionUtils.handleThrowable(t);
//虽然抛出错误,依然要设置为true是因为不排除其父类可以加载它。
tryLoadingFromJavaseLoader = true;
}
if (tryLoadingFromJavaseLoader) {
try {
//通过java类加载器加载类
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
//如果缓存中没有加载过并且java的类加载器都加载不到,那么将使用自己
//的类加载器,但是如果设置了java的安全管理器,那么检查是否有此资源的访问权限
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = "Security Violation, attempt to use " +
"Restricted Class: " + name;
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
//判断是否使用代理加载模式,也就是首先使用父加载器加载
//filter方法是对类名进行判断是否使用父加载器,比如javax开头的用父类加载
//用el等等,那么自己加载。
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested
//通过父加载器加载
if (delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader1 " + parent);
try {
//指定类加载器为父加载器,第二参数表示是否需要提前进行初始化(调用类加载器)
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (2) Search local repositories
if (log.isDebugEnabled())
log.debug(" Searching local repositories");
try {
//从WebResourceRoot中寻找资源,首先从WEB-INF/classes下找,然后从此类设置的URL仓库中
//从前面的分析(类加载器的start)中可以看到,它会从WEB-INF/lib下。
clazz = findClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from local repository");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
// (3) Delegate to parent unconditionally
//如果还是没有找到,那么从父加载器中寻找
if (!delegateLoad) {
if (log.isDebugEnabled())
log.debug(" Delegating to parent classloader at end: " + parent);
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Loading class from parent");
if (resolve)
resolveClass(clazz);
return (clazz);
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
从上面的分析,我们可以看到tomcat的应用加载器有两种模式,一种是非代理模式,另一种是代理模式,我们来看下它们之间的区别
区别就是在后面两个阶段,前面依然遵循了java的双亲委派,主要是为了安全。
到这里web类加载器的分析就结束了,我们继续StandardContext的start阶段的分析。
类加载器设置完毕后,接下来就是设置cookie处理器,初始化字符集映射(地区-》字符集),从MANIFEST.MF文件中检查依赖,设置JNDI相关的命名上下文,将刚才创建的类加载器绑定到当前线程中,启动类加载器(就是上面将的类加载的启动),启动realm的start方法
接下来又是重点,tomcat启动realm之后就触发了configure_start事件
protected synchronized void org.apache.catalina.startup.ContextConfig.configureStart() {
// Called from StandardContext.start()
webConfig();
//加载被@WebFilter,@WebServlet,,@WebListener注解的类
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
//添加security role,通常使用security-constraint标签配置
/**
*
*
* /manager/*
*
*
* admin
*
*
*/
if (ok) {
validateSecurityRoles();
}
// Configure an authenticator if we need one
//设置各类验证器,比如登录验证方式,管道阀验证等等
//
// BASIC
//
if (ok) {
authenticatorConfig();
}
// Make our application available if no problems were encountered
//如果没有出现配置问题,那么设置为配置成功
if (ok) {
context.setConfigured(true);
} else {
log.error(sm.getString("contextConfig.unavailable"));
//否则设置为配置失败
context.setConfigured(false);
}
}
上面的代码调用了webConfig()方法,这个方法就是解析我们web项目中的web.xml用的
protected void org.apache.catalina.startup.ContextConfig.webConfig() {
//构建WebXmlParser,在这个解析器的构造器中,构造了我们熟悉的Digester,这个Digester可想而知就
//设置了很多跟web.xml中标签相关的规则
WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
context.getXmlValidation(), context.getXmlBlockExternal());
Set defaults = new HashSet<>();
//获取默认web.xml-》全局的conf/web.xml,host级别的web.xml.default,并使用webXmlParser进行解析成WebXml对象
defaults.add(getDefaultWebXmlFragment(webXmlParser));
//创建一个空的WebXml对象,作为解析context自身web.xml的空篮子
WebXml webXml = createWebXml();
// Parse context level web.xml
//加载web.xml资源
InputSource contextWebXml = getContextWebXmlSource();
//解析内容到webXml对象中,如果失败,那么ok标记为false
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
ok = false;
}
。。。。。。省略
从上面可以看到,tomcat首先解析了全局的web.xml配置,然后解析host级别的web.xml配置,最后解析自身的web.xml配置,下面我们来看下web.xml是如何被解析的
public boolean org.apache.tomcat.util.descriptor.web.WebXmlParser.parseWebXml(InputSource source, WebXml dest,
boolean fragment) {
boolean ok = true;
if (source == null) {
return ok;
}
XmlErrorHandler handler = new XmlErrorHandler();
Digester digester;
WebRuleSet ruleSet;
//如果是web.xml片段,那么使用专用片段的Digester和规则
//否则使用正常的Digester和规则,片段的根标签是web-fragment
//正常的web.xml根标签是web-app
if (fragment) {
digester = webFragmentDigester;
ruleSet = webFragmentRuleSet;
} else {
digester = webDigester;
ruleSet = webRuleSet;
}
//将webXml对象设置为根对象
digester.push(dest);
//设置xml解析时的错误处理器
digester.setErrorHandler(handler);
try {
//解析内容
digester.parse(source);
if (handler.getWarnings().size() > 0 ||
handler.getErrors().size() > 0) {
ok = false;
handler.logFindings(log, source.getSystemId());
}
}
。。。。。。异常打印与设置ok标记为false
return ok;
}
从上面的代码中我们可以看到,tomcat进行web.xml的解析也是通过Digester和设置其中的规则进行解析的,所以具体的内容还是要看规则的实现,这里不会去研究所有标签的规则解析。
context-param
web上下文参数
//
digester.addCallMethod(fullPrefix + "/context-param",
"addContextParam", 2);
digester.addCallParam(fullPrefix + "/context-param/param-name", 0);
digester.addCallParam(fullPrefix + "/context-param/param-value", 1);
调用webXml对象的addContextParam方法添加参数。CallMethodRule与CallParamRule规则,我们在前面已经分析过,这里不再赘述,在begin中CallMethodRule是准备好参数数组
CallParamRule往参数数组中添加值,CallMethodRule的end将会使用这些参数调用对应的方法。
方法如下:
private final Map contextParams = new HashMap<>();
public void org.apache.tomcat.util.descriptor.web.WebXml.addContextParam(String param, String value) {
contextParams.put(param, value);
}
Filter
//创建FilterDef对象
digester.addObjectCreate(fullPrefix + "/filter",
"org.apache.tomcat.util.descriptor.web.FilterDef");
//add到webXml对象中
digester.addSetNext(fullPrefix + "/filter",
"addFilter",
"org.apache.tomcat.util.descriptor.web.FilterDef");
//给FilterDef对象设置描述信息
digester.addCallMethod(fullPrefix + "/filter/description",
"setDescription", 0);
//给FilterDef对象设置display-name
digester.addCallMethod(fullPrefix + "/filter/display-name",
"setDisplayName", 0);
//调用FilterDef对象的setFilterClass方法上设置filter的class类
digester.addCallMethod(fullPrefix + "/filter/filter-class",
"setFilterClass", 0);
//设置过滤器名
digester.addCallMethod(fullPrefix + "/filter/filter-name",
"setFilterName", 0);
//设置icon地址
digester.addCallMethod(fullPrefix + "/filter/icon/large-icon",
"setLargeIcon", 0);
//设置icon地址
digester.addCallMethod(fullPrefix + "/filter/icon/small-icon",
"setSmallIcon", 0);
//是否支持异步
digester.addCallMethod(fullPrefix + "/filter/async-supported",
"setAsyncSupported", 0);
//设置初始化参数
digester.addCallMethod(fullPrefix + "/filter/init-param",
"addInitParameter", 2);
digester.addCallParam(fullPrefix + "/filter/init-param/param-name",
0);
digester.addCallParam(fullPrefix + "/filter/init-param/param-value",
1);
//创建FilterMap对象
digester.addObjectCreate(fullPrefix + "/filter-mapping",
"org.apache.tomcat.util.descriptor.web.FilterMap");
//给webXml对象设置FilterMap(过滤器映射)
digester.addSetNext(fullPrefix + "/filter-mapping",
"addFilterMapping",
"org.apache.tomcat.util.descriptor.web.FilterMap");
//FilterMap对象设置过滤器名
digester.addCallMethod(fullPrefix + "/filter-mapping/filter-name",
"setFilterName", 0);
//设置servlet名
digester.addCallMethod(fullPrefix + "/filter-mapping/servlet-name",
"addServletName", 0);
//设置url通配符
digester.addCallMethod(fullPrefix + "/filter-mapping/url-pattern",
"addURLPattern", 0);
从上面的代码中,我们可以看到tomcat解析Filter标签,是创建一个FilterDef然后注册到webXml对象中,我们看下addFilter这个方法
private final Map filters = new LinkedHashMap<>();
public void addFilter(FilterDef filter) {
if (filters.containsKey(filter.getFilterName())) {
// Filter names must be unique within a web(-fragment).xml
throw new IllegalArgumentException(
sm.getString("webXml.duplicateFilter",
filter.getFilterName()));
}
//过滤器名-》过滤器定义对象
filters.put(filter.getFilterName(), filter);
}
过滤器的mapping配置
private final Set filterMaps = new LinkedHashSet<>();
private final Set filterMappingNames = new HashSet<>();
public void addFilterMapping(FilterMap filterMap) {
filterMaps.add(filterMap);
filterMappingNames.add(filterMap.getFilterName());
}
Listener
digester.addCallMethod(fullPrefix + "/listener/listener-class",
"addListener", 0);
对应的webXml对象的方法
private final Set listeners = new LinkedHashSet<>();
public void addListener(String className) {
listeners.add(className);
}
从上面的方法来看,tomcat对监听器的处理只是通过listeners这个泛型为String的集合进行储存,没有进行对象的包装,之所以这么做,主要是监听器就一个简单标签,没有其他的信息,所有不需要包装成对象。
Servlet
//创建ServletDefCreateRule规则
digester.addRule(fullPrefix + "/servlet",
new ServletDefCreateRule());
//给webXml对象添加Servlet
digester.addSetNext(fullPrefix + "/servlet",
"addServlet",
"org.apache.tomcat.util.descriptor.web.ServletDef");
//给Servlet对象添加初始化参数
digester.addCallMethod(fullPrefix + "/servlet/init-param",
"addInitParameter", 2);
digester.addCallParam(fullPrefix + "/servlet/init-param/param-name",
0);
digester.addCallParam(fullPrefix + "/servlet/init-param/param-value",
1);
//设置jsp文件路径
digester.addCallMethod(fullPrefix + "/servlet/jsp-file",
"setJspFile", 0);
//设置启动顺序
digester.addCallMethod(fullPrefix + "/servlet/load-on-startup",
"setLoadOnStartup", 0);
//设置servlet的类对象
digester.addCallMethod(fullPrefix + "/servlet/servlet-class",
"setServletClass", 0);
//设置servlet类的名字
digester.addCallMethod(fullPrefix + "/servlet/servlet-name",
"setServletName", 0);
//设置MultipartDef对象,用于配置诸如文件上传的最大size
digester.addObjectCreate(fullPrefix + "/servlet/multipart-config",
"org.apache.tomcat.util.descriptor.web.MultipartDef");
//设置到WebXml对象中
digester.addSetNext(fullPrefix + "/servlet/multipart-config",
"setMultipartDef",
"org.apache.tomcat.util.descriptor.web.MultipartDef");
//设置上传配置
digester.addCallMethod(fullPrefix + "/servlet/multipart-config/location",
"setLocation", 0);
digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-file-size",
"setMaxFileSize", 0);
digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-request-size",
"setMaxRequestSize", 0);
digester.addCallMethod(fullPrefix + "/servlet/multipart-config/file-size-threshold",
"setFileSizeThreshold", 0);
//是否支持异步
digester.addCallMethod(fullPrefix + "/servlet/async-supported",
"setAsyncSupported", 0);
//设置当前servlet是否可用
digester.addCallMethod(fullPrefix + "/servlet/enabled",
"setEnabled", 0);
//准备addServletMapping方法调用
digester.addRule(fullPrefix + "/servlet-mapping",
new CallMethodMultiRule("addServletMapping", 2, 0));
//准备servletName参数
digester.addCallParam(fullPrefix + "/servlet-mapping/servlet-name", 1);
//准备url参数,后面会调用CallMethodMultiRule的end方法
digester.addRule(fullPrefix + "/servlet-mapping/url-pattern", new CallParamMultiRule(0));
上面在添加servlet规则的时候,添加了一个ServletDefCreateRule规则,我们来分析一下
begin
public void org.apache.tomcat.util.descriptor.web.ServletDefCreateRule.begin(String namespace, String name, Attributes attributes)
throws Exception {
//创建一个类似FilteDef的对象,可想而知,这就是给包装Servlet信息的定义类
ServletDef servletDef = new ServletDef();
digester.push(servletDef);
if (digester.getLogger().isDebugEnabled())
digester.getLogger().debug("new " + servletDef.getClass().getName());
}
end方法没有做什么,只是弹出对象
下面对上面调用的WebXml对象的方法进行一些简单的罗列
注册servlet
//servletName->ServletDef对象
private final Map servlets = new HashMap<>();
public void addServlet(ServletDef servletDef) {
servlets.put(servletDef.getServletName(), servletDef);
if (overridable) {
servletDef.setOverridable(overridable);
}
}
添加Servlet映射
//urlPattern -> urlPattern
private final Map servletMappings = new HashMap<>();
private final Set servletMappingNames = new HashSet<>();
public void addServletMapping(String urlPattern, String servletName) {
addServletMappingDecoded(UDecoder.URLDecode(urlPattern, getCharset()), servletName);
}
public void addServletMappingDecoded(String urlPattern, String servletName) {
//urlPattern -> urlPattern
String oldServletName = servletMappings.put(urlPattern, servletName);
if (oldServletName != null) {
// Duplicate mapping. As per clarification from the Servlet EG,
// deployment should fail.
throw new IllegalArgumentException(sm.getString(
"webXml.duplicateServletMapping", oldServletName,
servletName, urlPattern));
}
servletMappingNames.add(servletName);
}
好了,web.xml的解析先到这,其他的规则自己去研究,这里不过多的赘述
接下来继续ContextConfig的config_start事件
protected void org.apache.catalina.startup.ContextConfig.webConfig() {
。。。。。省略在上面已经分析过的代码
ServletContext sContext = context.getServletContext();
//解析web-fragment(servlet3.0新增的模块化,用于提供在jar包中已经设置好的监听器啊,过滤器啊,servlet啊等等)
//得扫描/WEB-INF/lib/,/WEB-INF/classes下的META-INF/web-fragment.xml
//然后创建WebXml对象,创建的方式和之前分析的一样,这里就不过多的分析
Map fragments = processJarsForWebFragments(webXml, webXmlParser);
// Step 2. Order the fragments.
//给片段进行排序,片段中的filter,listener,servlet等类的创建是具有顺序关系的,所以需要进行排序
//比如在某某之后创建,这就是一种顺序。
Set orderedFragments = null;
orderedFragments =
WebXml.orderWebFragments(webXml, fragments, sContext);
// Step 3. Look for ServletContainerInitializer implementations
//加载jar包,类路径下的META-INF/services/javax.servlet.ServletContainerInitializer配置文件,多个ServletContainerInitializer用换行符分割
//通过反射进行加载
if (ok) {
//(*1*)
processServletContainerInitializers();
}
//通常我们不会指定ServletContainerInitializer,所以typeInitializerMap一般为空
//如果webXml.isMetadataComplete()我们在context.xml配置文件中指定为true,那么将不会进行注解的扫描
if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
// Step 4. Process /WEB-INF/classes for annotations and
// @HandlesTypes matches
Map javaClassCache = new HashMap<>();
if (ok) {
//列出/WEB-INF/classes下的所有资源
WebResource[] webResources =
context.getResources().listResources("/WEB-INF/classes");
for (WebResource webResource : webResources) {
// Skip the META-INF directory from any JARs that have been
// expanded in to WEB-INF/classes (sometimes IDEs do this).
//跳过META-INF
if ("META-INF".equals(webResource.getName())) {
continue;
}
//处理注解@WebServlet,@WebFilter,@WebListener,并根据HandleType是注解还是类型对资源进行筛选
//设置到initializerClassMap中 ServletContainerInitializer -》 Set>
processAnnotationsWebResource(webResource, webXml,
webXml.isMetadataComplete(), javaClassCache);
}
}
// Step 5. Process JARs for annotations and
// @HandlesTypes matches - only need to process those fragments we
// are going to use (remember orderedFragments includes any
// container fragments)
if (ok) {
//处理web-fragment对应jar包的@WebServlet,@WebFilter,@WebListener注解,并根据HandleType是注解还是类型对资源进行筛选
//这里就一个问题了,如果我META-INF下没有web-fragment就不会进行注解的扫描了,想想这也是合理的,要不然所有引用的jar都
//得扫一遍,这个启动就太慢了
processAnnotations(
orderedFragments, webXml.isMetadataComplete(), javaClassCache);
}
// Cache, if used, is no longer required so clear it
javaClassCache.clear();
}
//如果webXml.isMetadataComplete()为true,就不进行操作
if (!webXml.isMetadataComplete()) {
// Step 6. Merge web-fragment.xml files into the main web.xml
// file.
if (ok) {
//合并片段中的数据到主webXml中,比如context-params的值进行合并,属性进行合并
//模块片段相同的属性不会覆盖主配置的值
ok = webXml.merge(orderedFragments);
}
// Step 7. Apply global defaults
// Have to merge defaults before JSP conversion since defaults
// provide JSP servlet definition.
//合并全局和host级别的web.xml,全局的属性不会覆盖主配置的
webXml.merge(defaults);
// Step 8. Convert explicitly mentioned jsps to servlets
//从tomcat的注解直接翻译过来的意思就是:转换显示提到的jsp为servlet
//用我们的话将其实它就是把我们配置的jsp类型的ServletDef的class设置成org.apache.jasper.servlet.JspServlet
//然后添加一个初始化参数jspFile -》 jspFile的路径
if (ok) {
convertJsps(webXml);
}
// Step 9. Apply merged web.xml to Context
if (ok) {
//将webXml对象中的内容设置到context中,其中有一个比较重要的操作也在里面
//那就是将Servlet包装成了context的子容器StandardWrapper
configureContext(webXml);
}
} else {
//和上面是一样的逻辑
webXml.merge(defaults);
convertJsps(webXml);
configureContext(webXml);
}
// Always need to look for static resources
// Step 10. Look for static resources packaged in JARs
if (ok) {
// Spec does not define an order.
// Use ordered JARs followed by remaining JARs
Set resourceJars = new LinkedHashSet<>();
//获取所有的模块片段
for (WebXml fragment : orderedFragments) {
resourceJars.add(fragment);
}
for (WebXml fragment : fragments.values()) {
if (!resourceJars.contains(fragment)) {
resourceJars.add(fragment);
}
}
//处理jar资源,设置META-INF/resources下的静态资源到context的WebResourceRoot中,以便web
//直接访问
processResourceJARs(resourceJars);
// See also StandardContext.resourcesStart() for
// WEB-INF/classes/META-INF/resources configuration
}
// Step 11. Apply the ServletContainerInitializer config to the
// context
if (ok) {
//将ServletContainerInitializer设置到context中
for (Map.Entry>> entry :
initializerClassMap.entrySet()) {
if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(
entry.getKey(), null);
} else {
context.addServletContainerInitializer(
entry.getKey(), entry.getValue());
}
}
}
//(*1*)
protected void processServletContainerInitializers() {
List detectedScis;
try {
//从ApplicationContext指定的jar包路径和context的类路径加载ServletContainerInitializer
WebappServiceLoader loader = new WebappServiceLoader<>(context);
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
log.error(sm.getString(
"contextConfig.servletContainerInitializerFail",
context.getName()),
e);
ok = false;
return;
}
for (ServletContainerInitializer sci : detectedScis) {
//ServletContainerInitializer.class -> new HashSet>()
initializerClassMap.put(sci, new HashSet>());
HandlesTypes ht;
try {
//获取HandlesTypes注解
ht = sci.getClass().getAnnotation(HandlesTypes.class);
} catch (Exception e) {
continue;
}
if (ht == null) {
continue;
}
//获取这个ServletContainerInitializer感兴趣的类型
Class>[] types = ht.value();
if (types == null) {
continue;
}
for (Class> type : types) {
if (type.isAnnotation()) {
handlesTypesAnnotations = true;
} else {
handlesTypesNonAnnotations = true;
}
//typeInitializerMap 类型-》对这个类型感兴趣的ServletContainerInitializer集合
Set scis =
typeInitializerMap.get(type);
if (scis == null) {
scis = new HashSet<>();
typeInitializerMap.put(type, scis);
}
scis.add(sci);
}
}
}
到这里,web.xml的配置就已经结束了,所有配置的信息都已经设置到context中,那么接下来将要做什么呢?我们跳出config_start,回到StandardContext的startInternal方法
protected synchronized void startInternal() throws LifecycleException {
。。。。。。省略已分析过的
//触发config_start事件,这个事件在前面我们已经分析过了
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// Start our child containers, if not already started
//启动子容器,也就是StandardWrapper,它的startInternal方法就进行了一下通知,没有
//做其他的
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
// Start the Valves in our pipeline (including the basic),
// if any
//调用排管的start,循环调用管道阀的start
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// Acquire clustered manager
Manager contextManager = null;
//获取管理器,用于管理session,如果没有创建,那么现在创建
Manager manager = getManager();
if (manager == null) {
if ( (getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error("standardContext.clusterFail", ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}
// Configure default manager if none was specified
//设置session管理器
if (contextManager != null) {
setManager(contextManager);
}
if (manager!=null && (getCluster() != null) && distributable) {
//let the cluster know that there is a context that is distributable
//and that it has its own manager
getCluster().registerManager(manager);
}
}
//如果配置失败,打印失败日志
if (!getConfigured()) {
log.error(sm.getString("standardContext.configurationFail"));
ok = false;
}
// We put the resources into the servlet context
//将context的资源集设置到ApplicationContext中去
//key 为 org.apache.catalina.resources
if (ok)
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources());
if (ok ) {
//设置实例管理器,用于创建实例,销毁实例
if (getInstanceManager() == null) {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
setInstanceManager(new DefaultInstanceManager(context,
injectionMap, this, this.getClass().getClassLoader()));
}
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
}
// Create context attributes that will be required
if (ok) {
//往ApplicationContext中设置jar扫描器
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
}
// Set up the context init params
//合并参数,将context中的初始化参数合并到ApplicationContext中
//如果发生冲突,那么通过ApplicationContext中设置的参数来决定是否可以被覆盖
mergeParameters();
// Call ServletContainerInitializers
for (Map.Entry>> entry :
initializers.entrySet()) {
try {
//调用ServletContainerInitializers,我们可以自定义加载Filter,Servlet,Listener
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
// Configure and call application event listeners
if (ok) {
//使用上面设置的InstanceManager构建监听器对象,比如ServletContextAttributeListener,HttpSessionIdListener等事件监听器
//ServletContextListener,HttpSessionListener为生命周期监听器
//并触发容器启动事件,然后调用ServletContextListener的contextInitialized方法。
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
//检查约束
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
}
try {
// Start manager
Manager manager = getManager();
//调用管理器的start方法,这里是StandManager的start方法,它的方法主要是加载
//javax.servlet.context.tempdir指定的临时目录下的SESSION.ser文件,进行反序列化
//如果上次没有序列化session到这个文件,就不会进行反序列化,直接返回
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
}
// Configure and call application filters
if (ok) {
//创建filter对象,并调用其init方法,然后注册到context的
//HashMap filterConfigs = new HashMap<>();
//中,ApplicationFilterConfig继承FilterConfig,持有Filter对象
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
if (ok) {
//启动loadOnStartup大于0的,内部使用TreeMap进行排序,key值为loadOnStartup,value为Wrapper集合
//通过InstanceManager反射创建Servlet,然后调用其初始化方法
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
//取消绑定,并把老的加载器恢复(恢复现场)
unbindThread(oldCCL);
}
// Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
}
startTime=System.currentTimeMillis();
// Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
// The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
//清理一下缓存
getResources().gc();
// Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
} else {
//设置启动状态starting
setState(LifecycleState.STARTING);
}
}
StandardContext的启动非常的复杂,看这部分源码需要很大的耐心,现在来总结一下,首先tomcat对context进行了fixDocBase,换句话就是对于war包,将docBase设置成解压后的目录中,然后就是设置完全的解析context.xml配置文件,设置专属的类加载器,解析web.xml配置文件,解析模块化jar包中的web配置,解析注解配置,循环调用ServletContainerIniter(可以自定义注册Filter,Servlet,Listener逻辑),反射创建Listeners,并调用其中的ServletContextListener的contextInitialized方法,创建Filter,包装成ApplicationFilterConfig,调用其init方法,创建servlet,并调用其init方法。