- 浏览: 7110 次
- 性别:
- 来自: 北京
文章分类
最新评论
对于tomcat这么一个庞大的东西,要去分析它,一开始有种不知从和入手好,原因如下:可切入的点太多了,随便捡个角落都能说半天,但是任何一个东西都不是孤立存在的,要说清楚一个东西,必定会引入其他的东西,这样错综复杂,要把一个点介绍完整,终究不是件容易的事情。
既然无从入手,我们就按平时的使用过程来介绍吧,从启动开始,一步一步的展开我们的旅程。当我们从启动开始,逐步往下行进,最后回到停止时,我们的旅程也就结束了。
好吧,开始吧!
我们一般都是通过运行tomcat/bin下的startup.bat(或者startup.sh)来启动tomcat的,startup.bat脚本的执行不是我们分析的重点,在此简单带过。在startup里会调用catalina脚本,在catalina里运行bootstrap.jar,而bootstrap.jar的启动类是org.apache.catalina.startup.Bootstrap,这是一个普通的java类,我们看看它的main方法 :
public static void main(String args[]) { 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; } 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(); } 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) { handleThrowable(t); // Unwrap the Exception for clearer error reporting if (t instanceof InvocationTargetException && t.getCause() != null) { t = t.getCause(); } t.printStackTrace(); System.exit(1); } }
第一次运行生成一个BootStrap的实例,调用bootstrap.init();我们看看这个init方法主要做了一下几件事:
1)初始化classLoader
2)将catalinaClassLoader设置为当前上下文classLoader和安全了classLoader
3)生成一个org.apache.catalina.startup.Catalina的实例
4)设置 Catalina的实例 的parentClassLoader为java.lang.ClassLoader
/** * Initialize daemon. */ public void init() throws Exception { // Set Catalina path setCatalinaHome();//如果没有设置catalina.home,就将当前目录复制给catalina.home setCatalinaBase();//如果没有设置catalina.base,就将当前目录复制给catalina.base /* *初始化了3个classLoader:commonLoader,catalinaLoader,sharedLoader */ 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.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; }
我们有必要看一下initClassLoaders();方法 :
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); } } 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<Repository>(); StringTokenizer tokenizer = new StringTokenizer(value, ","); while (tokenizer.hasMoreElements()) { String repository = tokenizer.nextToken().trim(); if (repository.length() == 0) { continue; } // 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)); } } ClassLoader classLoader = ClassLoaderFactory.createClassLoader (repositories, parent); // Retrieving MBean server MBeanServer mBeanServer = null; if (MBeanServerFactory.findMBeanServer(null).size() > 0) { mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0); } else { mBeanServer = ManagementFactory.getPlatformMBeanServer(); } // Register the server classloader ObjectName objectName = new ObjectName("Catalina:type=ServerClassLoader,name=" + name); mBeanServer.registerMBean(classLoader, objectName); return classLoader; }
实际上 initClassLoaders构造了三个 classLoader:commonClassLoader,catalinaClassLoader,sharedClassLoder,他们各自负责加载的类在配置文件 /conf/catalina.properties文件里定义了 。
common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
commonClassLoader主要负责一些基础依赖类和jar包的加载
server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
catalinaClassLoader主要负责tomcat自身的一些类和jar加载
shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
sharedClassLoder 主要负责一些共享类和jar包的加载
commonClassLoader作为catalinaClassLoader和sharedClassLoder的父加载类。上面说过catalinaClassLoader作为当前上下文classLoader也就是说,后续没有什么意外的话,运用程序的类都是用catalinaClassLoader来加载的。
当做完这几个初始化动作后,main方法里接着就来解析命令了:
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(); } 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."); }
startd命令执行动作: daemon.load(args),实际上就是执行Catalina的load方法,代码如下:
/** * Load daemon. */ 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); if (log.isDebugEnabled()) log.debug("Calling startup class " + method); method.invoke(catalinaDaemon, param); }
我们看看Catalina 的load 做了什么工作:
public void load() { long t1 = System.nanoTime(); initDirs();//初始化几个目录 // Before digester - it may be needed initNaming();//初始化几个跟naming相关的变量 //Digester是干嘛的?它就是用来解析xml的, //从其代码来看,个人感觉createStartDigester方法写得还是比较笨拙,不过这样的好处是代码一目了然 //好吧,就到这吧,不想太深入的介入 Digester,我们只要对它有个印象就可以了。 //在这里它主要定义了server.xml 文件的解析规则 // Create and execute our Digester Digester digester = createStartDigester(); InputSource inputSource = null; InputStream inputStream = null; File file = null; try { file = configFile(); inputStream = new FileInputStream(file); inputSource = new InputSource("file://" + file.getAbsolutePath()); } 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); inputStream.close(); } catch (SAXParseException spe) { log.warn("Catalina.start using " + getConfigFile() + ": " + spe.getMessage()); return; } catch (Exception e) { log.warn("Catalina.start using " + getConfigFile() + ": " , e); return; } //到上面为止就是加载并解析了server.xml文件 getServer().setCatalina(this); // 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"); }
上面主要就是加载并解析了server.xml文件,初始化了server。
我们看一下tomcat按什么规则去解析server.xml:
protected Digester createStartDigester() { long t1=System.currentTimeMillis(); // Initialize the digester Digester digester = new Digester(); digester.setValidating(false); digester.setRulesValidation(true); HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<Class<?>, List<String>>(); ArrayList<String> attrs = new ArrayList<String>(); attrs.add("className"); fakeAttributes.put(Object.class, attrs); digester.setFakeAttributes(fakeAttributes); digester.setClassLoader(StandardServer.class.getClassLoader()); // Configure the actions we will be using 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.NamingResources"); digester.addSetProperties("Server/GlobalNamingResources"); digester.addSetNext("Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addObjectCreate("Server/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Listener"); digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); digester.addObjectCreate("Server/Service/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Listener"); digester.addSetNext("Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); //Executor digester.addObjectCreate("Server/Service/Executor", "org.apache.catalina.core.StandardThreadExecutor", "className"); digester.addSetProperties("Server/Service/Executor"); digester.addSetNext("Server/Service/Executor", "addExecutor", "org.apache.catalina.Executor"); digester.addRule("Server/Service/Connector", new ConnectorCreateRule()); digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(new String[]{"executor"})); digester.addSetNext("Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector"); digester.addObjectCreate("Server/Service/Connector/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Connector/Listener"); digester.addSetNext("Server/Service/Connector/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // Add RuleSets for nested elements digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); digester.addRuleSet(new EngineRuleSet("Server/Service/")); digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); digester.addRuleSet(new ClusterRuleSet("Server/Service/Engine/Host/Cluster/")); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader. digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(parentClassLoader)); digester.addRuleSet(new ClusterRuleSet("Server/Service/Engine/Cluster/")); long t2=System.currentTimeMillis(); if (log.isDebugEnabled()) log.debug("Digester for server.xml created " + ( t2-t1 )); return (digester); }
接下来我们回到main函数里的daemon.start();Bootstrap的方法:
public void start() throws Exception { if( catalinaDaemon==null ) init(); Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null); method.invoke(catalinaDaemon, (Object [])null); }
我们看到,实际上就是调用了Catalina的start()方法。
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(); // Start the new server try { getServer().start(); } catch (LifecycleException e) { log.error("Catalina.start: ", e); } long t2 = System.nanoTime(); if(log.isInfoEnabled()) log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); try { // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( false); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. } if (await) { await(); stop(); } }
在start方法里,调用了LifecycleBase的start()方法 ,然后构造了一个CatalinaShutdownHook实例,在JVM退出时关闭tomcat。CatalinaShutdownHook是Catalina的一个内部类,它的实现很简单:
protected class CatalinaShutdownHook extends Thread { @Override public void run() { try { if (getServer() != null) { Catalina.this.stop(); } } catch (Throwable ex) { ExceptionUtils.handleThrowable(ex); log.error(sm.getString("catalina.shutdownHookFail"), ex); } finally { // If JULI is used, shut JULI down *after* the server shuts down // so log messages aren't lost LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).shutdown(); } } } }
就是关闭server。再回去看下LifecycleBase的start()方法:
public final synchronized void start() throws LifecycleException { if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) || LifecycleState.STARTED.equals(state)) { if (log.isDebugEnabled()) { Exception e = new LifecycleException(); log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e); } else if (log.isInfoEnabled()) { log.info(sm.getString("lifecycleBase.alreadyStarted", toString())); } return; } if (state.equals(LifecycleState.NEW)) { init(); } else if (!state.equals(LifecycleState.INITIALIZED) && !state.equals(LifecycleState.STOPPED)) { invalidTransition(Lifecycle.BEFORE_START_EVENT); } setStateInternal(LifecycleState.STARTING_PREP, null, false); try { startInternal(); } catch (LifecycleException e) { setStateInternal(LifecycleState.FAILED, null, false); throw e; } if (state.equals(LifecycleState.FAILED) || state.equals(LifecycleState.MUST_STOP)) { stop(); } else { // Shouldn't be necessary but acts as a check that sub-classes are // doing what they are supposed to. if (!state.equals(LifecycleState.STARTING)) { invalidTransition(Lifecycle.AFTER_START_EVENT); } setStateInternal(LifecycleState.STARTED, null, false); } }
这个方法是同步的(synchronized),第一次启动的时候,state的值为NEW,会调用init()方法:
public final synchronized void init() throws LifecycleException { if (!state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } setStateInternal(LifecycleState.INITIALIZING, null, false); try { initInternal(); } catch (LifecycleException e) { setStateInternal(LifecycleState.FAILED, null, false); throw e; } setStateInternal(LifecycleState.INITIALIZED, null, false); }
这个方法主要是注册了Lifecycle的事件监听。
到这里一个server就启动起来了,总结一下,tomcat的启动主要就是设置变量、生成3个类加载器并加载各自负责的类,加载解析配置文件,初始化server、注册监听事件。
发表评论
-
tomcat源码分析系列之请求处理---大厨颠勺
2011-08-13 22:04 0上一回,我们已经吃上了香喷喷的狗肉,味道好是好,但作为 ... -
tomcat源码分析系列之请求处理---关门打狗
2011-08-13 17:06 1530上回我们把请求放进来了,这回我们关上门,好好修理修理它 ... -
tomcat源码分析系列之请求处理---请君入瓮
2011-08-13 14:31 1337费了九牛二虎之力 ... -
tomcat源码分析系列之启动---庐山真面目
2011-08-11 23:28 1493上回我们说到Http11Protocol,它的ini ... -
tomcat源码分析系列之启动---暗度陈仓
2011-08-10 22:27 1219前面我们分析了tomcat是如何启动的,但我们好像 ... -
tomcat源码分析系列之HTTP
2011-07-25 23:33 0tomcat是一个web容器,对于互联网来说,H ... -
tomcat源码分析系列之前言
2011-07-24 11:40 30我这人很懒,工作中经常使用tomcat,一直以来就 ...
相关推荐
【标题】"Tomcat源码分析系列文档"深入解析了Apache Tomcat服务器的内部工作原理,涵盖了一系列关键知识点,如HTTP协议、类加载机制、容器设计模式等。这些文档为理解Tomcat的运行机制提供了宝贵的资源。 【描述】...
### TOMCAT源码分析——启动框架详解 #### 一、前言 TOMCAT作为一款广泛使用的开源Java Servlet容器,其内部实现复杂且强大。本文旨在深入剖析TOMCAT的启动框架及其整体架构,帮助读者更好地理解其工作原理。...
总的来说,Tomcat源码分析涉及了从启动流程到核心组件的各个方面,深入学习这些内容能够提升开发者对Web服务器的理解,从而提高开发和维护效率。通过对源码的解读,我们可以更有效地解决性能瓶颈、优化配置,以及...
《Tomcat源码研读笔记》是对Apache Tomcat服务器内部工作原理的深度探索。Tomcat作为一款广泛应用的开源Java Servlet容器,它的源码是理解Java Web应用运行机制的关键。本笔记将围绕Tomcat的核心组件、架构设计以及...
【标题】"Tomcat源码资源包"是一个包含Apache Tomcat服务器源代码的压缩文件,旨在帮助开发者深入了解Tomcat的工作原理以及进行定制化开发。Tomcat是Java Servlet和JavaServer Pages(JSP)的开源应用服务器,是轻量...
### Tomcat源码研究知识点概览 #### 1.1 Catalina.bat脚本解析 - **脚本功能**:`catalina.bat`是Tomcat启动过程中的关键脚本之一,其主要作用在于构建合适的Java命令行参数,进而启动Tomcat服务。此脚本根据环境...
### tomcat源码解析 #### 简介与概览 Tomcat作为一款开源的Servlet容器,被广泛应用于Java ...通过对Tomcat源码的深入分析,我们可以更好地理解它是如何工作的,以及如何利用其强大的功能来构建高效、稳定的Web应用。
Tomcat作为一款广泛应用的开源Web服务器和Servlet容器,其源码分析对于Java Web开发者来说具有极高的学习价值。本篇文章将重点围绕"MyEclipse下Tomcat_7.0.78源码,可以直接运行"这一主题,深入探讨如何在MyEclipse...
【Tomcat源码分析】 Tomcat作为一款广泛应用的开源Java Servlet容器,它的源码解析对于深入理解Web服务器的工作原理和优化应用性能至关重要。本文将主要探讨Tomcat的启动框架、核心组件及其相互关系。 首先,...
通过研究Tomcat源码,开发者可以学习到如何构建一个高性能的Servlet容器,掌握Java Web应用的核心运行机制,这对于提升Java EE开发能力大有裨益。同时,如果你遇到Tomcat的使用问题或者想要进行定制化开发,源码分析...
在深入探讨Tomcat源码之前,我们先了解一下Tomcat是什么。Tomcat是一款开源的Java Servlet容器,由Apache软件基金会开发,它实现了Java EE中的Web应用服务器部分,特别是Servlet和JavaServer Pages (JSP)规范。《How...
【标题】"Tomcat 7.0.70 源码分析与Eclipse工程转换" 在Java Web开发领域,Apache Tomcat是一个广泛使用的开源应用服务器,尤其在处理Servlet和JSP方面。Tomcat 7.0.70是7.x系列的一个版本,它包含了对Java Servlet...
在Tomcat7的启动过程中,涉及到很多内部组件的初始化,其中`Digester`是一个重要的工具,用于解析XML配置文件,将XML结构映射为Java对象。本篇文章将深入剖析`Digester`的使用以及它在Tomcat7启动过程中的作用。 `...
【标题】:Tomcat源码分析 【描述】:Tomcat是Apache软件基金会下的一个开源项目,是一款广泛使用的Java Servlet容器,它实现了Java EE的Web应用服务器标准。深入理解Tomcat的源码对于提升Java Web开发技能、优化...
在标签中提到的“源码”和“工具”,意味着这个备忘可能还涵盖了如何查看和调试Tomcat源码,以及使用相关工具(如JConsole、VisualVM等)监控Tomcat的运行状态,分析性能和内存使用情况。 总之,这个备忘录是关于...
本文将通过对"tomcatsrc:tomcat源码分析"这一主题的探讨,帮助读者深入理解Tomcat的核心机制,提升在系统开源领域的专业素养。 一、Tomcat架构概览 Tomcat的架构分为几个主要部分,包括Catalina(核心引擎)、 ...
它继承自`Lifecycle`接口,并实现了一系列的生命周期方法,如`start()`和`stop()`,确保Tomcat的正确启动和优雅退出。 `StandardService`是另一个重要的类,它是服务层的容器,管理一组`Engine`实例。在启动时,`...