再次强调一下,Tomcat系列全部文章基于9.0.12版本。
二:start()方法
在BootStrap类中的核心代码是这几行。
String command = "start";
else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
}
看到这里我很纳闷,为什么要setAwait呢?各位观众老爷应该也很疑惑把,没事,我们跟踪代码去一探究竟。
反射就是调用了Catalina的setAwait方法来给catalinaDaemon对象的await属性赋值,因为在Catalina对象初始化的时候默认await属性是false,这里设置成true,。如果大家对这里初始化的时候知道类名,方法名,这里为什么要用反射来执行有疑问,请看上一篇的Question3和Answers3的解答。
public void setAwait(boolean await)
throws Exception {
Class> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
那么为什么要设置成true呢?这个属性有什么用呢?别着急,我们搜索一下这个属性在哪里用到了,tomcat的解耦做的非常好,同一个属性一般只应用到这个类里面。所以我们直接Ctrl+F搜索。发现在Catalina的strat方法中有这么一段
if (await) {
await();
stop();
}
如果是true,则执行await方法。继续跟踪await方法
public void await() {
getServer().await();
}
发现这里执行的是server对象的awiat方法。我们点进去发现await是一个接口,找到其实现类。这里讲一个小技巧。源码看多了大家就会发现,像这种接口一般都会有一个标准或者默认的实现类,一般名字都叫做Standardxxx或者xxxBase。果不其然,我们在StandardServer类中找到await方法,大概看一下,发现这里就是new ServerSocket的地方,应该就是启动端口的那部分代码。跟踪到这里我们暂时先放下这一块的代码。
总结一下就是await的初始化的时候是false,需要在启动的时候设置标志位为true才可以正常启动端口来监听请求。
我们现在回到Bootstrap里面,第二步是daemon.load(args)这部分。
我们进入load方法,和上面一样,都是反射执行Catalina的load方法。
这个方法有很多注意的地方,可能有点长,但是还是很有必要来细细分析的。
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Create and execute our Digester
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), e);
}
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
}
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
1. initDirs(),名称是初始化文件夹,就是初始化catalina-home/temp这个文件夹。大家导入源码的时候会新建这个文件夹,不然会打印错误日志,其实就是这里在控制的。
2.initNaming()这一部分没什么好说的,就是使用System.property方法将原来jdk的系统属性替换成tomcat自定义的系统属性。
3.createStartDigster这个方法比较特殊,乍一看有点蒙,digester这个词是消化器的意思,那么tomcat在消化什么呢?后来上网一搜,发现digester这个东西是和dom4j一样,都是解析xml文件用的,dom4j是将文件全部读取到内存中来解析的,效率比较高,digester是一边解析,一边读取的,效率较低,但是节约内存。而且被apache收购,tomcat在digester的基础上又封装了一层的东西。总的来说,digester就是一个解析xml文件的东西。点进去一看,果不其然,和我们的server.xml文件目录一样。那到底digester用什么规则来解析呢?这个方法的作用就是这个,将一些规则和类添加进去初始化。
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
然后就是读取文件,对文件输入输出流的一些操作,真正的解析是在digester.push() 和digester.parse()这两个地方。
解析完成之后,我们tomcat中定义的一些组件和server.xml文件中定义的组件关联起来啦。
接下来还有一块的的核心代码就是getServer.init(),这个方法点进去之后发现是Lifecycle这个接口,这是管理生命周期的接口,所有组件都实现了start方法和init方法,确切的说实现的是startInternal方法和initInternal方法,这都是设计模式的体现,我会在设计模式专题中详细讲解这一部分。我们重点先回到init方法里面。记住,去找实现类,StandardServer的initInternal方法。我们来看一下。
protected void initInternal() throws LifecycleException {
super.initInternal();
//这里和jvm有关,就是添加一个全局的string缓存标识,如果有多个的话就直接从缓存中读取
onameStringCache = register(new StringCache(), "type=StringCache");
// 工厂模式的体现,engine包裹的内容都可以叫做container,用工厂模式来统一添加
MBeanFactory factory = new MBeanFactory();
//所有的子集的添加都是添加container
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
//初始化全局命名资源
globalNamingResources.init();
//这里放在文字中讲解,比较重要
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// 这里也放在文字中讲解
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
需要详细讲解的地方,首先获取catalina的类加载器,然后判断父加载器是不是系统级别的类加载器,如果你认真读过第二篇的话会知道catalina的类加载器的上层是common类加载器,这里其实就是判断是不是common类加载器,如果是的话就去目录里面将所有的tomcat的内置jar包加载。
下面的service方法都一样,这里需要回顾一下tomcat的组件结构,如果不熟悉的童鞋请回顾一下第一篇,接下来所有的init顺序都是和第一篇中介绍的吻合。
接下来我们来到第三步,进入start方法。
其实start方法要说的倒不是很多了,和上面的init的模式基本一致。
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
这里我们注意一下 long t1,long t2这两个变量。long t2下面是不是看到了一串熟悉的日志~,看到这里是不是有一些成就感呢~嘿嘿嘿。
核心代码是getServer().start方法这里。
也是按照tomcat的层次模型一层一层逐一start的。这里面注意一下connector的startInternal方法比较复杂。需要先初始化protocolHandler这个类,然后又需要先初始化endPoint,这个对象是一个抽象类,走到这里的时候我们看一下endPoint这个对象的实现方式。
是不是看到了我们经常说的nio,bio这些io模型呢?这里是tomcat9.0的版本,已经没有bio的实现了,我们tomcat的默认实现是nio的方式。apr模式大家可能不了解,我大致介绍一下,apr就是直接调用native本地方法库来解决高并发问题的一种方式,但是这种方式目前还不是很成熟,效率其实也一般,最重要的是使用起来比较繁琐,不是修改一下xml文件就可以的。还需要下载一些依赖。
至于protocolHandler,这种xxxHandler我们一般叫做分发器,叫适配器设计模式,这里的作用是负责找到适配的io模型然后分发。
好啦,启动流程大致就到这里啦。下面该是手写实现篇咯。大家对这种讲解有什么意见或者建议呢,还请大家留下宝贵的意见。