目录
前言
Tomcat的使用已经十分方便,大部分人工作中可能都不会有二次开发它的必要了。那么我们为什么还需要阅读它的源码呢?
往往熟练的使用,仅仅能让你成为一个熟练的代码民工。阅读源码,会让你在这一领域拥有更多的认知积累,逐渐成为一个拥有整套完整思维模式和优秀认知事物能力的人。仅仅停留在见过,会用,确从没想过代码背后蕴含的思想,更少有人研究过源码,进而体会大师们在某些问题解决上秉承的思想和思维的风格。
一、环境搭建:建议拿来主义
如果使用eclipse,可以直接从官网下载并导入源码,ant编译后运行。习惯了idea+maven的童鞋就难受了。需要手动添加pom.xml文件。tomcat10以上的版本,代码仓库的jar无法满足需求,还需要手动从tomcat的lib文件下去添加依赖。有兴趣的童鞋自行尝试。失败了也没关系,毕竟咱们还有plan B。
懒人推荐:直接引入已经别人调试成功的源码。跳过繁琐配置。根据我多年读代码的经验,每多一步繁琐的操作,就会磨灭一点你看源码的热情。撸起袖子,直接开始,手撕源码。
推荐一个源码下载地址:成功环境 (不是我搭建的,我也是直接导入)
二、从时序图,看tomcat启动
1.时序图
了解一个陌生事物,最简单的方式就是看别人的总结,so,我们先看一下Tomcat启动的时序图:
好家伙,这么多概念。没有关系,不必理会这些容器的定义,现在就犹如探索一堆黑盒子。我们要做的,仅仅是从代码里探索,了解这些容器什么时候被创建,是如何分工,处理请求,返回响应等等逻辑。最后再由我们自己给出这些容器的定义,与书本上的内容对比。有点啰嗦,上代码:
a).Bootstrap 的main 函数
public static void main(String args[]) {
// 这里加锁 应该是为了防止多次初始化吧
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
// 1、初始化类加载器,并初始化calatina
bootstrap.init();
} catch (Throwable t) {
//省略非关键代码。。。。。
}
daemon = bootstrap;
} else {
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
String command = "start";
// 默认command=start,如果args有参数,必须把start放在最后
// 如 -config xxx start
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("start")) {
// 以下三个方法都是反射调用calatina的方法
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else {
//其他分支代码均省略。。。。。。。。。。。
}
} catch (Throwable t) {
//省略非关键代码。。。。。
}
}
可以看到,main作为入口,完成两件事:
1.初始化类加载器,并初始化calatina (后一章会详细介绍tomcat的类加载机制)。
2.调用了setAwait load start 方法。
b).Bootstrap 的init()
初始化类加载器代码先跳过。在init时,创建了时序图中第二个对象,catalina,并将sharedLoader 设置为父类加载器。那么catalina的load过程是在什么时候被调用。别急,沿着代码继续看。
public void init() throws Exception {
// 初始化三个classLoader:
// commonLoader、catalinaLoader、sharedLoader
initClassLoaders();
// 设置当前线程的ContextClassLoader为catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
// 用 catalinaLoader加载并实例化Catalina
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 调用 catalina 的方法setParentClassLoader,
// 设置catalina的parentClassLoader 为 sharedLoader
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
回到a中,此时我们init代码已经执行完毕,接下来将执行setAwait,load,start 的方法 。
c).Bootstrap.load()
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
method.invoke(catalinaDaemon, param);
}
最后一句,通过反射的方式,使catalina动态的执行了load()方法。留个疑问:此处为什么使用反射,直接使用catalina.load()执行呢?
d).catalina.load()
代码看到这里,与我们所见的时序图完全一致。catalina 的load方法中,做了server.xml的解析,这里不详细说了(另开一章Digester)。解析完成后会生成一个StandardServer ,后面的getServer()返回就是这个对象。
public void load() {
//省略非关键代码。。。。。。。。。。。。。
// Before digester - it may be needed
initNaming();
// Parse main server.xml
// 解析server.xml
parseServerXml(true);
Server s = getServer();
if (s == null) {
return;
}
// 设置Server的Catalina、CatalinaHome、CatalinaBase
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
//省略非关键代码。。。。。。。。。。。。。
}
}
致此,我相信还是比较简单的。(因为难得部分都被我跳过了)
代码中server的init(),我们可以看一下这个类图:
init是调用父类方法,这里是典型的模板模式。子类实现的是initInternal()方法。这样做的好处就是再init中定义了一个算法的骨架,而将一些步骤的具体实现延迟到子类中。
e).Server.init()
先看看骨架,公共代码部分,主要是维护生命周期的状态:
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
setStateInternal(LifecycleState.INITIALIZING, null, false);
initInternal();
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
接下来看看子类的具体实现,注意代码最后一句,是一个for循环来创建多个service
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize utility executor
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
register(utilityExecutor, "type=UtilityExecutor");
// Register global String cache
// Note although the cache is global, if there are multiple Servers
// present in the JVM (may happen when embedding) then the same cache
// will be registered under multiple names
onameStringCache = register(new StringCache(), "type=StringCache");
// Register the MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
// class loaders
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
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 | IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// Initialize our defined Services
for (Service service : services) {
service.init();
}
}
2.思考
代码看到这里,我们对bootstrap启动,到创建service的过程有了个大致了解。同时留下两个坑: