继前文深入剖析双亲委派机制之后,本文将引直接走进具体的代码实现,一探其真正的实现思路。
Tomcat 启动的起点在于 Bootstrap 类的 main()方法。在 main()方法执行之前,其静态代码块(static{})会率先被执行。因此,我们将首先深入探讨静态代码块的运行机制,然后再分析 main()方法的执行流程。
static { // 获取用户目录 String userDir = System.getProperty("user.dir"); // 优先从环境变量获取 CATALINA_HOME String home = System.getProperty(Globals.CATALINA_HOME_PROP); File homeFile = null; if (home != null) { File f = new File(home); try { homeFile = f.getCanonicalFile(); } catch (IOException ioe) { homeFile = f.getAbsoluteFile(); } } // 若环境变量中未获取到,则尝试从 bootstrap.jar 所在目录的上一级目录获取 if (homeFile == null) { File bootstrapJar = new File(userDir, "bootstrap.jar"); if (bootstrapJar.exists()) { File f = new File(userDir, ".."); try { homeFile = f.getCanonicalFile(); } catch (IOException ioe) { homeFile = f.getAbsoluteFile(); } } } // 若以上两种方式均未获取到,则使用用户目录作为 CATALINA_HOME if (homeFile == null) { File f = new File(userDir); try { homeFile = f.getCanonicalFile(); } catch (IOException ioe) { homeFile = f.getAbsoluteFile(); } } // 设置 CATALINA_HOME 属性 catalinaHomeFile = homeFile; System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath()); // 获取 CATALINA_BASE,若未设置,则使用 CATALINA_HOME 作为 CATALINA_BASE String base = System.getProperty(Globals.CATALINA_BASE_PROP); if (base == null) { catalinaBaseFile = catalinaHomeFile; } else { File baseFile = new File(base); try { baseFile = baseFile.getCanonicalFile(); } catch (IOException ioe) { baseFile = baseFile.getAbsoluteFile(); } catalinaBaseFile = baseFile; } // 设置 CATALINA_BASE 属性 System.setProperty(Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());}
在启动的初始阶段,Tomcat 需要确定自身的根目录(catalina.home)和工作目录(catalina.base)。 这段代码描述了寻找这两个关键路径的逻辑:
最后,代码会将最终确定的 catalina.home 和 catalina.base 路径信息设置为系统属性,以便在后续的启动流程中使用。
这段代码如同为 Tomcat 构建一座稳固的基石,它负责加载并设置 catalina.home 和 catalina.base 相关的信息,为后续的启动流程奠定基础。
Tomcat 的 main 方法可概括为两个主要阶段:**初始化 (**init) 和 加载与启动 (load+start);。
public static void main(String args[]) { // 初始化阶段 main方法第一次执行的时候,daemon肯定为null,所以直接new了一个Bootstrap对象,然后执行其init()方法 if (daemon == null) { Bootstrap bootstrap = new Bootstrap(); try { bootstrap.init(); } catch (Throwable t) { handleThrowable(t); t.printStackTrace(); return; } //daemon守护对象设置为bootstrap daemon = bootstrap; } else { // 当作为服务运行时,对stop的调用将在新线程上进行, // 因此确保使用正确的类加载器以防止一系列类未找到异常。 Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); } // 加载与启动阶段 // 执行守护对象的load方法和start方法 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) { // 展开异常以获得更清晰的错误报告 if (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } handleThrowable(t); t.printStackTrace(); System.exit(1); }}
我们点到init()里面去看看~
public void init() throws Exception { // 非常关键的地方,初始化类加载器s,后面我们会详细具体地分析这个方法 initClassLoaders(); // 设置上下文类加载器为catalinaLoader,这个类加载器负责加载Tomcat专用的类 Thread.currentThread().setContextClassLoader(catalinaLoader); // 暂时略过,后面会讲 SecurityClassLoad.securityClassLoad(catalinaLoader); // 使用catalinaLoader加载我们的Catalina类 // 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(); // 设置Catalina类的parentClassLoader属性为sharedLoader // 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); // catalina守护对象为刚才使用catalinaLoader加载类、并初始化出来的Catalina对象 catalinaDaemon = startupInstance;}
initClassLoaders 方法是 Tomcat 类加载机制的核心,它负责初始化 Tomcat 的各个类加载器,构建起 Tomcat 独特的类加载体系。通过分析这个方法,我们可以清晰地验证上一节提到的 Tomcat 类加载图。
private void initClassLoaders() { try { // 创建commonLoader,如果未创建成果的话,则使用应用程序类加载器作为commonLoader 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,父类加载器为commonLoader catalinaLoader = createClassLoader("server", commonLoader); // 创建sharedLoader,父类加载器为commonLoader sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { // 如果创建的过程中出现异常了,日志记录完成之后直接系统退出 handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); }}
createClassLoader 方法是 Tomcat 类加载器创建的底层方法,它负责根据配置文件 catalina.properties 中的配置信息来创建具体的类加载器实例。 CatalinaProperties.getProperty("xxx") 方法用于从 conf/catalina.properties 文件中获取指定属性的值,为 createClassLoader 方法提供必要的配置信息。
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { // 获取类加载器待加载的位置,如果为空,则不需要加载特定的位置,使用父类加载返回回去。 String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; // 替换属性变量,比如:${catalina.base}、${catalina.home} value = replace(value); List<Repository> repositories = new ArrayList<>(); // 解析属性路径变量为仓库路径数组 String[] repositoryPaths = getPaths(value); // 对每个仓库路径进行repositories设置。我们可以把repositories看成一个个待加载的位置对象,可以是一个classes目录,一个jar文件目录等等 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);}
我们来分析一下ClassLoaderFactory.createClassLoader--类加载器工厂创建类加载器。
public static ClassLoader createClassLoader(List<Repository> repositories, final ClassLoader parent) throws Exception { if (log.isDebugEnabled()) log.debug("Creating new class loader"); // Construct the "class path" for this class loader Set<URL> set = new LinkedHashSet<>(); // 遍历repositories,对每个repository进行类型判断,并生成URL,每个URL我们都要校验其有效性,有效的URL我们会放到URL集合中 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) { File file=new File(repository.getLocation()); file = file.getCanonicalFile(); if (!validateFile(file, RepositoryType.JAR)) { continue; } 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); } } } } // Construct the class loader itself 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]); } // 从这儿看,最终所有的类加载器都是URLClassLoader的对象~~ return AccessController.doPrivileged( new PrivilegedAction<URLClassLoader>() { @Override public URLClassLoader run() { if (parent == null) return new URLClassLoader(array); else return new URLClassLoader(array, parent); } });}
对 initClassLoaders 分析完,Tomcat 还会进行一项至关重要的安全措施,即 SecurityClassLoad.securityClassLoad 方法。 让我们深入探究这个方法,看看它在 Tomcat 的安全体系中扮演着怎样的角色。
public static void securityClassLoad(ClassLoader loader) throws Exception { securityClassLoad(loader, true);}static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager) throws Exception { if (requireSecurityManager && System.getSecurityManager() == null) { return; } loadCorePackage(loader); loadCoyotePackage(loader); loadLoaderPackage(loader); loadRealmPackage(loader); loadServletsPackage(loader); loadSessionPackage(loader); loadUtilPackage(loader); loadValvesPackage(loader); loadJavaxPackage(loader); loadConnectorPackage(loader); loadTomcatPackage(loader);} private static final void loadCorePackage(ClassLoader loader) throws Exception { final String basePackage = "org.apache.catalina.core."; loader.loadClass(basePackage + "AccessLogAdapter"); loader.loadClass(basePackage + "ApplicationContextFacade$PrivilegedExecuteMethod"); loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedForward"); loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedInclude"); loader.loadClass(basePackage + "ApplicationPushBuilder"); loader.loadClass(basePackage + "AsyncContextImpl"); loader.loadClass(basePackage + "AsyncContextImpl$AsyncRunnable"); loader.loadClass(basePackage + "AsyncContextImpl$DebugException"); loader.loadClass(basePackage + "AsyncListenerWrapper"); loader.loadClass(basePackage + "ContainerBase$PrivilegedAddChild"); loadAnonymousInnerClasses(loader, basePackage + "DefaultInstanceManager"); loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntry"); loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntryType"); loader.loadClass(basePackage + "ApplicationHttpRequest$AttributeNamesEnumerator");}
这段代码使用 catalinaLoader 加载了 Tomcat 源代码中各个专用类,这些类主要分布在以下几个包中:
至此,我们已经逐一分析了 init 方法中的关键方法,包括 initClassLoaders、SecurityClassLoad.securityClassLoad 等,了解了 Tomcat 初始化阶段的各个步骤。
在深入探究 Tomcat 启动流程的过程中,我们发现似乎遗漏了一个关键角色——WebApp 类加载器。
WebApp 类加载器是每个 Web 应用独有的,而每个 Web 应用本质上就是一个 Context。 因此,我们理所当然地将目光投向了 Context 的实现类。 Tomcat 中,StandardContext 是 Context 的默认实现,而 WebApp 类加载器正是诞生于 StandardContext 类的 startInternal() 方法中。
protected synchronized void startInternal() throws LifecycleException { if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); }}
这段代码的逻辑非常简洁,它在 WebApp 类加载器不存在的情况下,会创建一个新的 WebApp 类加载器,并将其设置为当前 Context 的加载器(setLoader)。
到这里,我们已经完成了对 Tomcat 启动过程和类加载机制的全面解析,从 Bootstrap 类开始,一步步深入,最终揭开了 WebApp 类加载器的面纱。
通过这个分析过程,我们不仅了解了 Tomcat 启动和类加载的具体步骤,更深刻地理解了 Tomcat 采用这种独特的多层级类加载机制的深层原因,以及这种设计带来的种种优势。
本文链接:http://www.28at.com/showinfo-26-112754-0.html类加载机制的源码解读
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com