精华帖 (0) :: 良好帖 (3) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-03-25
最后修改:2010-12-22
上一篇文章
主要分析了Bootstrap main
方法的总体流程,并讨论
了JDK兼容性和启动参数。本篇开始深入细节。
Tomcat 的 运行时视图,简单地看,其实就是一些相互关联的组件。这些组件相互协作,完成一定的任务(比如部署Web 应用、处理到HTTP 请求等)。Tomcat 启动过程中所做的主要工作,也就是创建这些组件,并建立组件之间的关联。当 然,要创建哪些组件,组件之间怎么关联,这是根据配置文件来定制的。 服务器程序的启动过程一般都有“三段式”,Tomcat 也不例外,它的三段式分别是init 、load 和start 。 init 方 法我们先看看Bootstrap 的init 方法。public void init() throws Exception { // Set Catalina path setCatalinaHome(); setCatalinaBase(); 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; } 该 方法的主要工作依次是:
Catalina_Home 和Catalina_Base首先,我们看看这两个路径有何区别。Tomcat的启动脚本已经设置了CATALINA_HOME 和CATALINA_BASE 的 值,而且两者的值是相同的,都是Tomcat的根目录。那么为什么还要设置这两个变量呢?我们可以从Tomcat 5.5 的配置文档(http://tomcat.apache.org/tomcat-5.5-doc/config/host.html ) 中找到答案: The description below uses the variable name $CATALINA_HOME to refer to the directory into which you have installed Tomcat 5, and is the base directory against which most relative paths are resolved. However, if you have configured Tomcat 5 for multiple instances by setting a CATALINA_BASE directory, you should use $CATALINA_BASE instead of $CATALINA_HOME for each of these references. 从这段描述可以看出CATALINA_HOME 和CATALINA_BASE 的区别。简单的说,CATALINA_HOME 是Tomcat 的安装目 录,CATALINA_BASE 是Tomcat 的工作目录。如果我们想要运行Tomcat 的 多个实例,但是不想安装多个Tomcat 软件副本。那么我们可以配置多个工作 目录,每个运行实例独占一个工作目录,但是共享同一个安装目录。 Tomcat 每个运行实例需要使用自己的conf 、logs 、temp 、webapps 、work 和shared 目录,因此CATALINA_BASE 就 指向这些目录。 而其他目录主要包括了Tomcat 的二进制文件和脚本,CATALINA_HOME 就指向这些目录。 如果我们希望再运行另一个Tomcat 实例,那么我们可以建立一个目录,把conf 、logs 、temp 、webapps 、work 和shared 拷贝 到该目录下,然后让CATALINA_BASE 指向该目录即可。 下 面,我们看看Bootstrap 是如何设置CATALINA_HOME和CATALINA_BASE。 private void setCatalinaHome() { if (System.getProperty("catalina.home") != null) return; File bootstrapJar = new File(System.getProperty("user.dir"), "bootstrap.jar"); if (bootstrapJar.exists()) { try { System.setProperty ("catalina.home", (new File(System.getProperty("user.dir"), "..")) .getCanonicalPath()); } catch (Exception e) { // Ignore System.setProperty("catalina.home", System.getProperty("user.dir")); } } else { System.setProperty("catalina.home", System.getProperty("user.dir")); } } CATALINA_HOME 保 存在系统变量catalina.home 中。setCatalinaHome 方法首先检查catalina.home 系统变量是否设置。如果已经设置,则直接返回;否则,就检查Tomcat 的启动目录(系统变量user.dir)。如果启动目录是bin,那么启动目录下就存在bootstrap.jar ,故CATALINA_HOME 就是bin 的父目录;如果启动目录下没有bootstrap.jar ,那么就假定启动目录就是CATALINA_HOME 。 private void setCatalinaBase() { if (System.getProperty("catalina.base") != null) return; if (System.getProperty("catalina.home") != null) System.setProperty("catalina.base", System.getProperty("catalina.home")); else System.setProperty("catalina.base", System.getProperty("user.dir")); } CATALINA_BASE 保存在系统变量catalina.base 中。setCatalinaBase 方 法首先检查catalina.base 系统变量是否设置,如果已经设置,就直接返回。 否则,就检查catalina.home 系统变量是否设置。如果已经设置,则以CATALINA_HOME 作为CATALINA_BASE 。 否则,就以Tomcat 的启动目录(系统变量user.dir )作为CATALINA_BASE 。 catalina.bat 中已经设置了catalin.home 和catalina.base 的值,详见下面代码: %_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION% 我们可以通过修改catalina.bat 中CATALINA_HOME 和CATALINA_BASE 的值 ,来设置catalina.home 和catalina.base 这两个系统变量。 初始化类加载器体系顾名思义,initClassLoaders() 负责初始化类加载器。看代码之前,我们先看看Tomcat 的类加载器体系。Tomcat 的类加载器体系各种组件容器(Tomcat 、JBoss 、GlassFish 、Geronimo 等), 都会有自己的类加载器体系。这主要是为了要把开发者编写的各种组件应用(WAR 、EAR 等)部署到容器中,并实现组件应用之间的隔离。Tomcat 也实现了自己的类加载器体系。这个在Tomcat 的官方文档中有详细介绍,详见http://tomcat.apache.org/tomcat-5.5-doc/class-loader-howto.html 。 这里做点简单介绍。 Tomcat 的类加载器体系如下图所示: Bootstrap | System | Common / \ Catalina Shared / \ Webapp1 Webapp2 ... BootStrap 就是JVM 的启动类加载器,负责加载Java 核心类库和系统扩展类库(%JAVA_HOME%/jre/lib/ext 下的jar 文件)。有的JVM实现提供了两个类加载器,分别加载核心类库和系统扩展类库。我们这里仍用一个Bootstrap 类加载器表示,不影响理解。 System 是JVM的系统类加载器,负责加载CLASSPATH 下的jar 文件,这些文件包 括:
Common 是公共类加载器,负责加载Tomcat 内部和Web 应用程序都可以看到的类。%CATALINA_HOME%/conf/catalina.properties 文 件中指定了这些类: common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar 可 见,Common 加载的是%CATALINA_HOME%/common 目录下的jar 文件。 Catalina
负责加载Tomcat
内部使用的类,这些类对于Web
应用程序不可见。同样,%CATALINA_HOME%/conf/catalina.properties
文件中指定了
这些类:
PS:Tomcat 7 的类加载体系有所简化,如下图所示: Bootstrap
在这种体系下,Tomcat 内部实现类对Web 应用是可见的。如果担心安全风险,可以以-security 方式启动Tomcat 。 sh startup.sh -security 这样,绝大部分内部实现类对Web 应用均不可见。
了解Tomcat 类加载器体系之后,我们来看看initClassLoaders 方法的代码。 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) { log.error("Class loader creation threw exception", t); System.exit(1); } } 可见,该方法以通过createClassLoader
依次创建了common
、server
和shared
类加载器,并且设置三者之间的父子关系。这三者就是前面提到的Common
、Catalina
和Shared
类加载器。如果%CATALINA_HOME%/conf/catalina.properties
中没有指定Common
的搜索路径,那么就是用当前类的类加载器——系统类加载器作为Common
。 private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { // CatalinaProperties类读取并封装了catalina.properties中的配置信息 String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; // 解析catalina.properties中配置的类搜索路径 // 将类路径中的${catalina.home}替换成CATALINA_HOME的值,将${catalina.base}替换成CATALINA_BASE的值 ArrayList repositoryLocations = new ArrayList(); ArrayList repositoryTypes = new ArrayList(); int i; StringTokenizer tokenizer = new StringTokenizer(value, ","); while (tokenizer.hasMoreElements()) { String repository = tokenizer.nextToken(); // Local repository boolean replace = false; String before = repository; while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) { replace=true; if (i>0) { repository = repository.substring(0,i) + getCatalinaHome() + repository.substring(i+CATALINA_HOME_TOKEN.length()); } else { repository = getCatalinaHome() + repository.substring(CATALINA_HOME_TOKEN.length()); } } while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) { replace=true; if (i>0) { repository = repository.substring(0,i) + getCatalinaBase() + repository.substring(i+CATALINA_BASE_TOKEN.length()); } else { repository = getCatalinaBase() + repository.substring(CATALINA_BASE_TOKEN.length()); } } if (replace && log.isDebugEnabled()) log.debug("Expanded " + before + " to " + replace); // 区分四种类型的路径 // Check for a JAR URL repository try { URL url=new URL(repository); repositoryLocations.add(repository); repositoryTypes.add(ClassLoaderFactory.IS_URL); continue; } catch (MalformedURLException e) { // Ignore } if (repository.endsWith("*.jar")) { repository = repository.substring (0, repository.length() - "*.jar".length()); repositoryLocations.add(repository); repositoryTypes.add(ClassLoaderFactory.IS_GLOB); } else if (repository.endsWith(".jar")) { repositoryLocations.add(repository); repositoryTypes.add(ClassLoaderFactory.IS_JAR); } else { repositoryLocations.add(repository); repositoryTypes.add(ClassLoaderFactory.IS_DIR); } } String[] locations = (String[]) repositoryLocations.toArray(new String[0]); Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]); // 创建类加载器对象 ClassLoader classLoader = ClassLoaderFactory.createClassLoader (locations, types, parent); // 类加载器被注册成MBean // Retrieving MBean server MBeanServer mBeanServer = null; if (MBeanServerFactory.findMBeanServer(null).size() > 0) { mBeanServer = (MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0); } else { mBeanServer = MBeanServerFactory.createMBeanServer(); } // Register the server classloader ObjectName objectName = new ObjectName("Catalina:type=ServerClassLoader,name=" + name); mBeanServer.registerMBean(classLoader, objectName); return classLoader; }
创建类加载器,必须要知道类搜索路径是什么。前面提到,Tomcat 各类加载器的类搜索路径都定义在%CATALINA_HOME%/conf/catalina.properties 中。该配置文件的主要内容如下: common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
配置文件内容的读取是由CatalinaProperties类 完成的。另外,CatalinaProperties 类还提供了额外的功能:
CatalinaProperties
在最大程度上保证了配置文件总是存在的。 一个疑问至此,类加载器体系基本建立。但是,init 方法又做了两件事件:
Catalina 对象init 方法接下来的工作就很简单了:
在Tomcat 5.5 中:
load 方法下面是Boostrap 类的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); if (log.isDebugEnabled()) log.debug("Calling startup class " + method); method.invoke(catalinaDaemon, param); }
可以看出,Catalina 的load 方法有两个版本:有参数和无参 数。 有参数版本,首先调用了arguments方法来处理参数。如果处理成功,则再调用无参数版本。因此,load的核心逻辑在无参数版本中。 public void load(String args[]) { try { if (arguments(args)) load(); } catch (Exception e) { e.printStackTrace(System.out); } }
无参数版本的load 方法做的工作,主要是根据Tomcat 的配置文件,创建各个组件,并建立组件之间的关联。比如,创建核心的Server 组件、Service 组 件、Manager 组件、 Loader 组 件等。 这个细节我们等会讨论,首先简单看看arguments 方法。arguments 方法对理解启动过程不是很关键,如果不感兴趣可以跳过。 Catalina 类的命令行参数Catalina.bat
脚本的参数,其实是直接传递到Catalina
类的。arguments
方法正是处理这些参数的。
PS:Tomcat 7 新加了-configtest 选项。
protected boolean arguments(String args[]) { boolean isConfig = false; if (args.length < 1) { usage(); return (false); } for (int i = 0; i < args.length; i++) { if (isConfig) { configFile = args[i]; isConfig = false; } else if (args[i].equals("-config")) { isConfig = true; } else if (args[i].equals("-nonaming")) { setUseNaming( false ); } else if (args[i].equals("-help")) { usage(); return (false); } else if (args[i].equals("start")) { starting = true; stopping = false; } else if (args[i].equals("stop")) { starting = false; stopping = true; } else { usage(); return (false); } } return (true); }
加载过程下面我们看看Catalina
类的load
方法的代码。 public void load() { // 初始化CATALINA_HOME、CATALINA_BASE和临时目录,该方法在Embedded类中。 initDirs(); // 初始化命名服务的基本配置,包括java.naming.factory.url.pkgs和java.naming.factory.initial // java.naming.factory.url.pkgs的默认值为 org.apache.naming // java.naming.factory.initial 的默认值为org.apache.naming.java.javaURLContextFactory // Before digester - it may be needed initNaming(); // 创建Digester对象。该对象被用来解析配置文件(默认为conf/server.xml) // Create and execute our Digester Digester digester = createStartDigester(); long t1 = System.currentTimeMillis(); Exception ex = null; InputSource inputSource = null; InputStream inputStream = null; File file = null; try { // 配置文件,由命令行参数-config指定,否则取默认值conf/server.xml file = configFile(); inputStream = new FileInputStream(file); inputSource = new InputSource("file://" + file.getAbsolutePath()); } catch (Exception e) { ; } // 如果配置文件不存在,则在类路径中加载 if (inputStream == null) { try { inputStream = getClass().getClassLoader() .getResourceAsStream(getConfigFile()); inputSource = new InputSource (getClass().getClassLoader() .getResource(getConfigFile()).toString()); } catch (Exception e) { ; } } // 如果类路径中也找不到,则加载server-embed.xml // 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 ((inputStream == null) && (file != null)) { log.warn("Can't load server.xml from " + file.getAbsolutePath()); return; } // 使用Digester对象解析配置文件,解析的过程中会创建各种组件,包括Server组件。 try { inputSource.setByteStream(inputStream); digester.push(this); digester.parse(inputSource); inputStream.close(); } catch (Exception e) { log.warn("Catalina.start using " + getConfigFile() + ": " , e); return; } // 将系统标准输出(System.out)和系统错误输出(System.err)重定向到定制的SystemLogHandler。 // SystemLogHandler可以将每个线程的输出隔离到不同的输出流中。 // Stream redirection initStreams(); // 初始化Server组件。成员变量server就代表Server组件 // Start the new server if (server instanceof Lifecycle) { try { server.initialize(); } catch (LifecycleException e) { log.error("Catalina.start", e); } } long t2 = System.currentTimeMillis(); if(log.isInfoEnabled()) log.info("Initialization processed in " + (t2 - t1) + " ms"); }
Catalina
加载的主要流程参见上述代码中的中文注释,应该比较清楚,这里就不一一赘述了。 <?xml version="1.0" encoding="UTF-8"?> <Server port="8005" shutdown="SHUTDOWN"> <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" /> <Service name="Catalina"> <Connector port="8080" maxHttpHeaderSize="8192" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" connectionTimeout="20000" disableUploadTimeout="true" /> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false"> ... </Host> </Engine> </Service> </Server>
protected Digester createStartDigester() { // ... // Configure the actions we will be using // 创建Server组件,即StandardServer对象 digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); // 设置StandardServer的成员变量port和shutdown digester.addSetProperties("Server"); // 建立Catalina对象和StandardServer对象之间的关联,前者包含后者 digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); //... // 创建Listener对象,类由className属性决定 digester.addObjectCreate("Server/Listener", null, // MUST be specified in the element "className"); // 设置Listener对象的成员变量 digester.addSetProperties("Server/Listener"); // 建立StandardServer对象和Listener对象之间的关联,前者包含后者 digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // 创建Service组件,即StandardService对象 digester.addObjectCreate("Server/Service", "org.apache.catalina.core.StandardService", "className"); // 设置StandardService的成员变量 name digester.addSetProperties("Server/Service"); // 建立StandardServer对象和StandardService对象之间的关联,前者包含后者 digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); // ... return (digester); }
概括地
说,addObjectCreate
表示创建对象,addSetProperties
表
示设置对象的属性,addSetNext
表示设置对象的包含对象。这都是
Digester
的常用规则。当然,Tomcat
也定制了一些规则,以执行更加复杂的操作。 start 方法public void start() throws Exception { if( catalinaDaemon==null ) init(); Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null); method.invoke(catalinaDaemon, (Object [])null); } 该方法其实仅仅是调用了Catalina 类的start 方法,因此我们重点看Catalina 类的start 方法。 public void start() { //确保load方法已经被调用。load方法会创建StandardServer 实例,并赋值给成员变量server。 if (server == null) { load(); } long t1 = System.currentTimeMillis(); //调用server的生命周期方法start // Start the new server if (server instanceof Lifecycle) { try { ((Lifecycle) server).start(); } catch (LifecycleException e) { log.error("Catalina.start: ", e); } } long t2 = System.currentTimeMillis(); if(log.isInfoEnabled()) log.info("Server startup in " + (t2 - t1) + " ms"); //注册JVM的shutdown钩子 try { // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); } } catch (Throwable t) { // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. } //如果await设置成true,则进入await状态 if (await) { await(); //退出await状态后,就停止Tomcat stop(); } }
start
方法执行之前,需要确保load
方法已经执行。如果load
方法已经执行,那么成员变量server
肯定被赋值。因此,start
方法首先判断成员变量
server
是
否为null
,如果是,则调用load
方法。 await状态setAwait方法Bootstrap 类的main方法中,处理start 启动参数时,会调用 setAwait 方法。 public void setAwait(boolean await) throws Exception { Class paramTypes[] = new Class[1]; paramTypes[0] = Boolean.TYPE; Object paramValues[] = new Object[1]; paramValues[0] = new Boolean(await); Method method = catalinaDaemon.getClass().getMethod("setAwait", paramTypes); method.invoke(catalinaDaemon, paramValues); }
public void setAwait(boolean b) { await = b; }
await 变成true 之后,Tomcat 就进入await 状态,这一点在start 方法中已经分析 过。 await方法如果成员变量await 为true ,那么Catalina 类的await 方法就会被调用。 public void await() { //直接调用StandardServer的await方法 server.await(); }
下面我们看看StandardServer 类的await 方法。 public void await() { // port是SHUTDOWN端口。如果值为-2,则表示不进入await状态,直接返回;如果值为-1,则表示通过简单循环的方式来实现await状态。 // 如果port为-2,则表示不进入await状态,直接返回;如果值为-1,则表示通过简单循环的方式来实现 await状态。 // 如果port为-1,则表示通过简单循环的方式来实现 await状态。此方法适合嵌入式Tomcat。 // 如果port为其他值,则表示通过监听网络端口的方式来实现 await状态。 // Negative values - don't wait on port - tomcat is embedded or we just don't like ports if( port == -2 ) { // undocumented yet - for embedding apps that are around, alive. return; } if( port==-1 ) { while( true ) { try { Thread.sleep( 100000 ); } catch( InterruptedException ex ) { } 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-03-27
分析的很精辟啊,而且你推荐的那本书确实牛,看了一节就很大收益
|
|
返回顶楼 | |
发表时间:2010-03-31
分析的很详细啊,感谢,看来我得照着5.5看看,本地是6.0很多不对了。
另:楼上说的是哪本书啊,分享下啊。 |
|
返回顶楼 | |
发表时间:2010-03-31
最后修改:2010-03-31
jackdown 写道 分析的很详细啊,感谢,看来我得照着5.5看看,本地是6.0很多不对了。
另:楼上说的是哪本书啊,分享下啊。 《How Tomcat Works》,你可以上百度文库搜一下,里面有的。 |
|
返回顶楼 | |
发表时间:2010-04-06
jarfield 写道 jackdown 写道 分析的很详细啊,感谢,看来我得照着5.5看看,本地是6.0很多不对了。
另:楼上说的是哪本书啊,分享下啊。 《How Tomcat Works》,你可以上百度文库搜一下,里面有的。 嗯,看到了,感觉不错,谢谢。 |
|
返回顶楼 | |
发表时间:2011-02-24
佩服楼主 现在正在看 《how tomcat works》 ,楼主能够提供这本书里面的例子程序啊?我的邮箱 huqiao86@gmail.com
|
|
返回顶楼 | |
浏览 7578 次