Tomcat9源代码分析(二)-初始化
来源:互联网 发布:python 绝对路径 编辑:程序博客网 时间:2024/06/06 05:06
Tomcat9源代码分析(二)-初始化
回顾
前面一篇文章提到,当Tomecat启动(startup.sh)时,会调用org.apache.catalina.startup.Bootstrap.main()方法。
正文
通过《Tomcat9源代码分析(一)-源码切入点》中的Tomcate9源代码下载地址我们获取到了apache-tomcat-9.0.2-src.zip文件,直接解压即可得到apache-tomcat-9.0.2源代码。用IDEA打开后如下图
什么都不说,咱们直奔主题来看看Bootstrap.main()做了些什么。
代码2-1
org.apache.catalina.startup.Bootstrap#main
/** * 通过提供的启动Tomcat时的主要方法和入口点 * * @param args命令行参数进行处理 */ public static void main(String args[]) { if (daemon == null) { // 创建Bootstrap对象 Bootstrap bootstrap = new Bootstrap(); try { //bootstrap初始化 bootstrap.init(); } catch (Throwable t) { handleThrowable(t); t.printStackTrace(); return; } daemon = bootstrap; } else { //重置当前线程类加载器 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"; //Bootstrap加载信息 //内部操作:用反射调用catalinaDaemon的load方法加载和解析server.xml配置文件 daemon.load(args); //Bootstrap启动 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(); } 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(引导程序)对象是否存在
- 不存在则创建Bootstrap对象并初始化
bootstrap.init();
- 存在则将当前的Bootstrap对象的类加载器赋给当前线程的类加载器
- 根据不同参数执行不同的操作
本章讲Tomcat服务启动我们直接看start做了些什么。
- Bootstrap加载基础信息(用反射调用catalinaDaemon的load方法加载和解析server.xml配置文件,往下看)
- Bootstrap启动
我们逐一来了解各步骤做了些什么。(异常情况本系列文章暂不做探究)我们先来看看初始化(==bootstrap.init();
==)做了些什么。
代码2-2
org.apache.catalina.startup.Bootstrap#init()
/** * 初始化程序 * * @throws 抛出异常致命初始化错误 */ public void init() throws Exception { //初始化类加载器 initClassLoaders(); //设置Tomcat私有加载器到当前线程 Thread.currentThread().setContextClassLoader(catalinaLoader); //加载Tomcat容器所需的class SecurityClassLoad.securityClassLoad(catalinaLoader); // 加载我们的启动类并调用它的process()方法 if (log.isDebugEnabled()) log.debug("Loading startup class"); //通过反射找到启动类Catalina Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.getConstructor().newInstance(); // 设置共享类加载器 if (log.isDebugEnabled()) log.debug("Setting startup class properties"); //反射找到父类加载器 String methodName = "setParentClassLoader"; Class<?> paramTypes[] = new Class[1]; //反射找到类加载器ClassLoader paramTypes[0] = Class.forName("java.lang.ClassLoader"); Object paramValues[] = new Object[1]; paramValues[0] = sharedLoader; Method method = startupInstance.getClass().getMethod(methodName, paramTypes); //调用ClassLoader.setParentClassLoader方法(执行加载器) method.invoke(startupInstance, paramValues); catalinaDaemon = startupInstance; }
通过代码2-2我们可以看到引导程序(Bootstrap)初始化的步骤:
- 初始化类加载器
- 将类加载器设置到当前线程
- 加载Tomcat容器所需class
- 通过反射加载Tomcat启动类Catalina并调用process()类
在看初始化类加载的实现之前,我们需要先了解Java类加载器都有哪些,分别是做什么用的。
Java虚拟机规范中提到的主要类加载器;
- Bootstrap Loader:加载lib目录下或者System.getProperty(“sun.boot.class.path”)、或者-XBootclasspath所指定的路径或jar。
- Extended Loader:加载lib\ext目录下或者System.getProperty(“java.ext.dirs”) 所指定的 路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld。
- AppClass Loader:加载System.getProperty(“java.class.path”)所指定的 路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld。
根据java虚拟机的双亲委派模式的原则,类加载器在加载一个类时,首先交给父类加载器加载,层层往上直到Bootstrap Loader。也就是一个类最先由Bootstrap Loader加载,如果没有加载到,则交给下一层的类加载器加载,如果没有加载到,则依次层层往下,直到最下层的类加载器。这也就是说,凡是能通过父一级类加载器加载到的类,对于子类也是可见的。因此可以利用双亲委派模式的特性,使用类加载器对不同路径下的jar包或者类进行环境隔离。
然后用一张图片来展示Tomcat的类加载体系:
结合之前对双亲委派模式的类加载过程的描述,对上图所示类加载体系进行介绍:
- ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现
- commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
- catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
- sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
- WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;
代码2-3
org.apache.catalina.startup.Bootstrap#initClassLoaders
/** * 初始化类加载器 */ private void initClassLoaders() { try { //创建Tomcat基本类加载器 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(); } //创建Tomcat容器私有类加载器 catalinaLoader = createClassLoader("server", commonLoader); //创建WebApp共享类加载器 sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); } }
==代码2-3==我们了解initClassLoaders顺序创建各个类加载器。
代码2-4
org.apache.catalina.startup.Bootstrap#createClassLoader
/** * 创建类加载器 * @param name 加载器名称 * @param parent 加载器 * @return 加载器对象 * @throws Exception 议持仓 */ 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 } // 加载资源文件 if (repository.endsWith("*.jar")) { //全局资源文件(*.jar) repository = repository.substring (0, repository.length() - "*.jar".length()); repositories.add( new Repository(repository, RepositoryType.GLOB)); } else if (repository.endsWith(".jar")) { //JAR包 repositories.add( new Repository(repository, RepositoryType.JAR)); } else { //文件 repositories.add( new Repository(repository, RepositoryType.DIR)); } } //创建加载器对象 return ClassLoaderFactory.createClassLoader(repositories, parent); }
==代码2-4==则是获取创建类加载器所需参数(资源文件),调用==ClassLoaderFactory.createClassLoader==方法创建类加载器对象并返回。
代码2-5
- org.apache.catalina.startup.ClassLoaderFactory#createClassLoader(List, ClassLoader)
/** * 基于配置(默认值和指定的目录路径)创建和返回一个新的类加载器 * * @param 类目录、JAR文件、JAR目录的存储库列表,或者应该添加到存储库中的URL,类加载器 * @param 新类加载器对应的父类加载器 * @return 新类加载器 * * @exception Exception if an error occurs constructing the class loader */ public static ClassLoader createClassLoader(List<Repository> repositories, final ClassLoader parent) throws Exception { if (log.isDebugEnabled()) log.debug("Creating new class loader"); // 为类加载器构建包路径 Set<URL> set = new LinkedHashSet<>(); if (repositories != null) { //遍历资源仓库列表 for (Repository repository : repositories) { //如果资源类型为包路径 if (repository.getType() == RepositoryType.URL) { //根据路径构建类加载器包路径 URL url = buildClassLoaderUrl(repository.getLocation()); if (log.isDebugEnabled()) log.debug(" Including URL " + url); set.add(url); } else if (repository.getType() == RepositoryType.DIR) {//如果资源文件类型为文件 File directory = new File(repository.getLocation()); directory = directory.getCanonicalFile(); //验证资源文件 if (!validateFile(directory, RepositoryType.DIR)) { continue; } //根据文件构建类加载器包路径 URL url = buildClassLoaderUrl(directory); if (log.isDebugEnabled()) log.debug(" Including directory " + url); set.add(url); } else if (repository.getType() == RepositoryType.JAR) {//如果资源文件路径为JAR包 File file=new File(repository.getLocation()); file = file.getCanonicalFile(); //验证JAR包 if (!validateFile(file, RepositoryType.JAR)) { continue; } //根据JAR包构建类加载器包路径 URL url = buildClassLoaderUrl(file); if (log.isDebugEnabled()) log.debug(" Including jar file " + url); set.add(url); } else if (repository.getType() == RepositoryType.GLOB) {//如果资源文件路径为全局资源文件 File directory=new File(repository.getLocation()); directory = directory.getCanonicalFile(); //验证文件夹 if (!validateFile(directory, RepositoryType.GLOB)) { continue; } if (log.isDebugEnabled()) log.debug(" Including directory glob " + directory.getAbsolutePath()); String filenames[] = directory.list(); if (filenames == null) { continue; } //循环构建类加载器包路径 for (int j = 0; j < filenames.length; j++) { String filename = filenames[j].toLowerCase(Locale.ENGLISH); if (!filename.endsWith(".jar")) continue; File file = new File(directory, filenames[j]); file = file.getCanonicalFile(); if (!validateFile(file, RepositoryType.JAR)) { continue; } if (log.isDebugEnabled()) log.debug(" Including glob jar file " + file.getAbsolutePath()); URL url = buildClassLoaderUrl(file); set.add(url); } } } } // 将构造的包放入类加载器中 final URL[] array = set.toArray(new URL[set.size()]); if (log.isDebugEnabled()) for (int i = 0; i < array.length; i++) { log.debug(" location " + i + " is " + array[i]); } //类加载器授权 return AccessController.doPrivileged( new PrivilegedAction<URLClassLoader>() { @Override public URLClassLoader run() { if (parent == null) return new URLClassLoader(array); else return new URLClassLoader(array, parent); } }); }
==代码2-5==可我们可以看出来,类加载器的创建即将资源文件添加到加载器中并授权,到此处我们可以想想,如果我们想Tomcat加载我们自己的类,是否可以在此处做功夫呢?理论可行,有兴趣的同学自己自己尝试下
,
此处本人还有个疑问的地方,老版本的类加载器,创建完毕后会将创建的类加载器注册到JMX服务中,但在Tomcat9中此处不再注册,那移动到哪去了呢?有了解的同学欢迎帮忙解答。
看到此处我们继续深入看看类加载器资源配置文件都写了些什么
代码2-6
conf/catalina.properties
# 多个服务资源文件路径使用“,”分隔# 加载器的前缀用来定义加载器类型(common,server,shared)# 路径不许是相对与catalina.base或catalina.home或绝对路径# “common.loader”如果不设置值则将作为Catalina服务的资源文件# “foo”:添加整个文件夹中的内容# "foo/*.jar" 添加文件夹中所有的.jar文件# "foo/bar.jar" 添加指定的jar文件# 设置值使用“...”的情况下catalina.base或catalina.base路径包含一个逗号,因为“”用于赋值,路径可能会不存在common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"server.loader=shared.loader=
看到==代码2-6==我们会发现基本类加载器加载了Tomcat目录下lib文件夹下的所有文件
通过==代码2-2==我们知道下一步操作为:加载Tomcat容器所需的class
代码2-7
org.apache.catalina.security.SecurityClassLoad#securityClassLoad(ClassLoader)
/** * 加载Tomcat容器所需的class * * @param loader 加载器(CatalinaLoader) * @param requireSecurityManager 空 * @throws Exception */ static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager) throws Exception { if (requireSecurityManager && System.getSecurityManager() == null) { return; } //加载Tomcat 核心class(org.apache.catalina.core) loadCorePackage(loader); loadCoyotePackage(loader); //WebappClassLoade基础class(org.apache.catalina.loader) loadLoaderPackage(loader); loadRealmPackage(loader); //Tomcat有关Servlet的class(org.apache.catalina.servlets) loadServletsPackage(loader); //Tomcat有关session的class(org.apache.catalina.session) loadSessionPackage(loader); //Tomcat工具类的class(org.apache.catalina.util) loadUtilPackage(loader); //Tomcat处理Cookie的 loadJavaxPackage(loader); //Tomcat处理请求的class(org.apache.catalina.connector) loadConnectorPackage(loader); loadTomcatPackage(loader); }
到这里,我们Tomcat初始化就完成了。下一篇文件我们一起来看看初始化后Bootstrap.load做了些什么
- Tomcat9源代码分析(二)-初始化
- Tomcat9源代码分析(一)-源码切入点
- Nginx源代码分析之初始化(二)
- Libevent源代码分析笔记二,初始化
- Chrome源代码分析之初始化(七)
- SDL2源代码分析1:初始化(SDL_Init())
- Nginx源代码分析之初始化(三)
- SDL2源代码分析1:初始化(SDL_Init())
- SDL2源代码分析1:初始化(SDL_Init())
- TCPMP源代码分析(二)
- Iperf 源代码分析(二)
- Hadoop源代码分析(二)
- yaffs2源代码分析(二)
- Iperf 源代码分析(二)
- yaffs2源代码分析(二)
- Gzip源代码分析(二)
- Iperf 源代码分析(二)
- DirectFB源代码阅读(二)初始化 .
- ZYNQ基础系列(二) IO口模拟HDMI
- RabbitMQ入门HelloWorld(C#)
- 160个练手CrackMe-040
- [BZOJ2820]YY的GCD(莫比乌斯反演+线性筛)
- Redis一主两从+哨兵监控
- Tomcat9源代码分析(二)-初始化
- 412. Fizz Buzz
- Http Client 客户端编码
- 面向对象
- Zookeeper_典型应用场景
- Intellij IDEA maven配置执行自动化测试
- Spring中bean的三种装配机制之一—java configure
- 区块链与程序员:赚钱还是创业
- 浅谈接口测试相关概念