Tomcat是后端服务最常见的web容器,对于后端程序员来说,了解它对个人自身技术能力以及业务开发能力都是必要的。
tomcat 版本:9.0.16
Tomcat是可以独立启动的,java程序启动是需要main方法的,因此读tomcat源码就从它的main方法开始。Tomcat的main方法在org.apache.catalina.startup.Bootstrap 里
public final class Bootstrap { …… /** * Daemon object used by main. */ private static final Object daemonLock = new Object(); …… /** * Main method and entry point when starting Tomcat via the provided * scripts. * * @param args Command line arguments to be processed */ public static void main(String args[]) { synchronized (daemonLock) { if (daemon == null) { // Don't set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { bootstrap.init(); } catch (Throwable t) { handleThrowable(t); t.printStackTrace(); return; } daemon = bootstrap; } else { // When running as a service the call to stop will be on a new // thread so make sure the correct class loader is used to // prevent a range of class not found exceptions. Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); } } try { String command = "start"; if (args.length > 0) { command = args[args.length - 1]; } if (command.equals("startd")) { args[args.length - 1] = "start"; daemon.load(args); daemon.start(); } else if (command.equals("stopd")) { args[args.length - 1] = "stop"; daemon.stop(); } else if (command.equals("start")) { daemon.setAwait(true); daemon.load(args); daemon.start(); if (null == daemon.getServer()) { System.exit(1); } } else if (command.equals("stop")) { daemon.stopServer(args); } else if (command.equals("configtest")) { daemon.load(args); if (null == daemon.getServer()) { System.exit(1); } System.exit(0); } else { log.warn("Bootstrap: command /"" + command + "/" does not exist."); } } catch (Throwable t) { // Unwrap the Exception for clearer error reporting if (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } handleThrowable(t); t.printStackTrace(); System.exit(1); } } …… }
上面代码逻辑很简单,就是创建一个 Bootstrap 对象,调用它的 init 方法初始化,然后根据启动参数,分别调用 Bootstrap 对象的不同方法。
1.1 init方法
首先看 Bootstrap 的 init 方法
/** * Initialize daemon. * @throws Exception Fatal initialization error */ public void init() throws Exception { initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.getConstructor().newInstance(); // Set the shared extensions class loader if (log.isDebugEnabled()) log.debug("Setting startup class properties"); 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; }
init 方法也比较简单,先调用了 initClassLoaders() 来初始化一些 ClassLoader,tomcat 需要加载应用程序里,所以需要 ClassLoader,然后设置了一下当天线程的 contextClassLoader 为
catalinaLoader,这个 catalinaLoader就是在 initClassLoaders() 初始化的,最后通过反射创建了一个 Catalina 类型的 startupInstance 对象,并调用了它的 setParentClassLoader 方法。
接着看一下 initClassLoaders() 方法
ClassLoader commonLoader = null; ClassLoader catalinaLoader = null; ClassLoader sharedLoader = null; private void initClassLoaders() { try { commonLoader = createClassLoader("common", null); if( commonLoader == null ) { // no config file, default to this loader - we might be in a 'single' env. commonLoader=this.getClass().getClassLoader(); } catalinaLoader = createClassLoader("server", commonLoader); sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); } }
initClassLoaders() 就是为了初始化 Bootstrap 类里的三个 ClassLoader 成员变量,看 createClassLoader 方法的定义
private ClassLoader createClassLoader(String name, ClassLoader parent)
可以看出,catalinaLoader 和 sharedLoader 的 parentClassLoader 是 commonLoader。
1.1.2 initClassLoaders 方法
下面看看 createClassLoader 方法
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; value = replace(value); List<Repository> repositories = new ArrayList<>(); String[] repositoryPaths = getPaths(value); for (String repository : repositoryPaths) { // Check for a JAR URL repository try { @SuppressWarnings("unused") URL url = new URL(repository); repositories.add(new Repository(repository, RepositoryType.URL)); continue; } catch (MalformedURLException e) { // Ignore } // Local repository 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)); } } return ClassLoaderFactory.createClassLoader(repositories, parent); }
方法的逻辑也比较简单
就是从 catalina.property文件里找 common.loader, shared.loader, server.loader 对应的值,然后构造成Repository 列表,再将Repository 列表传入ClassLoaderFactory.createClassLoader 方法,ClassLoaderFactory.createClassLoader 返回的是 URLClassLoader,而Repository 列表就是这个URLClassLoader 可以加在的类的路径。
在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 其实是一个对象。在Tomcat之前的版本里,这三个是不同的URLClassLoader对象。
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.getConstructor().newInstance();
初始化完三个ClassLoader对象后,init() 方法就使用 catalinaClassLoader 加载了org.apache.catalina.startup.Catalina 类,并创建了一个对象,然后通过反射调用这个对象的 setParentClassLoader 方法,传入的参数是 sharedClassLoader。最后吧这个 Catania 对象复制给 catalinaDaemon 属性。
main 方法初始化完 Bootstrap 对象后,就根据传入的参数,分别调用 Bootstrap 不同的方法,以 "start" 参数为例,调用了这三个方法
daemon.setAwait(true); daemon.load(args); daemon.start();
这三个方法的调用的共同点是通过反射去调用 init 方法里初始化过的 Catalina 对象的同名的方法。这三个方法将在下一篇文章总结。
Bootstrap 有一个 static 的代码块,这个代码块的作用是用来初始化 Bootstrap 里的两个static属性的
private static final File catalinaBaseFile; private static final File catalinaHomeFile;
2. 小结
可以看出 Bootstrap 的作用是初始化公共资源,创建一个 Catalina 类,并执行其相关方法,起了一个引导的作用。