在上一章查看tomcat启动文件都干点啥---catalina.bat,说了在catalina.bat中都走了什么流程,最重要的是,我们得出了如下这段命令:
_EXECJAVA=start "Tomcat" "E:\Program Files\Java\jdk1.7.0_40\bin\java" JAVA_OPTS= -Djava.util.logging.config.file="F:\apache-tomcat-7.0.8\conf\logging.properties" -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager CATALINA_OPTS= DEBUG_OPTS= -Djava.endorsed.dirs="F:\apache-tomcat-7.0.8\endorsed" -classpath "F:\apache-tomcat-7.0.8\bin\bootstrap.jar;F:\apache-tomcat-7.0.8\bin\tomcat-juli.jar" -Dcatalina.base="F:\apache-tomcat-7.0.8" -Dcatalina.home="F:\apache-tomcat-7.0.8" -Djava.io.tmpdir="F:\apache-tomcat-7.0.8\temp" MAINCLASS=org.apache.catalina.startup.Bootstrap CMD_LINE_ARGS= ACTION=start
其中很重要的一个属性是:MAINCLASS=org.apache.catalina.startup.Bootstrap,Bootstrap在bootstrap.jar中,我们看一下Bootstrap的类图:
从每个变量和方法的名字的字面上也能大概看出来变量或者方法的作用。
很显然,程序走到Bootstrap的时候首先调用main方法,main方法是通过脚本启动tomcat的入口,我们看一下main方法中实现的内容:
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(); } 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); }
可以看出main方法主要实现了两个功能:
(1)初始化一个守护进程变量。
(2)加载参数,解析命令,并执行。
下面是初始化守护进程的执行过程:
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); }
可以看到在bootstrap.init()方法中对bootstrap变量进行初始化,然后将结果返回给daemon。下面看一下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; }
init方法中对classLoader进行了初始化,设置了引用的catalinaDaemon变量。
对于如何定义catalinaDaemon变量是应用反射机制:
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;
下面着重说一下关于classLoader。
在Bootstrap类中,最开始的地方,有三个ClassLoader的定义,内容如下:
protected ClassLoader commonLoader = null; protected ClassLoader catalinaLoader = null; protected ClassLoader sharedLoader = null;
先不说这三个classLoader之间的关系,先看一下Bootstrap上的注释也有关于classLoader的说明:
* Bootstrap loader for Catalina. This application constructs a class loader * for use in loading the Catalina internal classes (by accumulating all of the * JAR files found in the "server" directory under "catalina.home"), and * starts the regular execution of the container. The purpose of this * roundabout approach is to keep the Catalina internal classes (and any * other classes they depend on, such as an XML parser) out of the system * class path and therefore not visible to application level classes.
翻译过来就是说:Bootstrap第一个功能是引导Catalina,Bootstrap构造一个class loader来加载Catalina的内部类(所有在catalina.home中的jar文件),第二个功能是启动container。实现catalina的内部类和系统的class path以及应用程序中的class要区分开不能相互访问的目的。
// Set Catalina path setCatalinaHome(); setCatalinaBase(); initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader);
设置当前的线程的class loader为catalinaLoader。使用catalinaLoader加载catalina类,实现反射,定义catalina的反射。然后重点看一下 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); }
在看一下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<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; }
在该方法中引入了CatalinaProperties类,下面看一下CatalinaProperties的内容:
这个类主要是加载catalina.properties配置文件,然后将其中的内容保存到当前环境中,首先查看$CATALINA_BASE/conf/catalina.propertie是否存在,如果不存在的话去读取Bootstrap.jar中的catalina.propertie的文件,如果没有在$CATALINA_BASE/conf/中配置catalina.propertie文件,那么catalina.propertie内容如下所示(tomcat版本大于5.x):
# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # List of comma-separated packages that start with or equal this string # will cause a security exception to be thrown when # passed to checkPackageAccess unless the # corresponding RuntimePermission ("accessClassInPackage."+package) has # been granted. package.access=sun.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper. # # List of comma-separated packages that start with or equal this string # will cause a security exception to be thrown when # passed to checkPackageDefinition unless the # corresponding RuntimePermission ("defineClassInPackage."+package) has # been granted. # # by default, no packages are restricted for definition, and none of # the class loaders supplied with the JDK call checkPackageDefinition. # package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper. # # # List of comma-separated paths defining the contents of the "common" # classloader. Prefixes should be used to define what is the repository type. # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. # If left as blank,the JVM system loader will be used as Catalina's "common" # loader. # Examples: # "foo": Add this folder as a class repository # "foo/*.jar": Add all the JARs of the specified folder as class # repositories # "foo/bar.jar": Add bar.jar as a class repository common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar # # List of comma-separated paths defining the contents of the "server" # classloader. Prefixes should be used to define what is the repository type. # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. # If left as blank, the "common" loader will be used as Catalina's "server" # loader. # Examples: # "foo": Add this folder as a class repository # "foo/*.jar": Add all the JARs of the specified folder as class # repositories # "foo/bar.jar": Add bar.jar as a class repository server.loader= # # List of comma-separated paths defining the contents of the "shared" # classloader. Prefixes should be used to define what is the repository type. # Path may be relative to the CATALINA_BASE path or absolute. If left as blank, # the "common" loader will be used as Catalina's "shared" loader. # Examples: # "foo": Add this folder as a class repository # "foo/*.jar": Add all the JARs of the specified folder as class # repositories # "foo/bar.jar": Add bar.jar as a class repository # Please note that for single jars, e.g. bar.jar, you need the URL form # starting with file:. shared.loader= # # String cache configuration. tomcat.util.buf.StringCache.byte.enabled=true #tomcat.util.buf.StringCache.char.enabled=true #tomcat.util.buf.StringCache.trainThreshold=500000 #tomcat.util.buf.StringCache.cacheSize=5000
在联系在Bootstrap.java中的这段代码:
String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent;
有没有感觉很奇怪,shared.loader,server.loader这两个配置都为空,所以通过initClassLoaders方法可以得出结论,catalinaLoader,sharedLoader均指向commonLoader的引用,所以在apache网站上的classLoader树如下展示:
但是拿两个classLoader为什么会定义呢,那就要看一下在tomcat5.x时代时候的catalina.propertie内容:
server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
catalinaLoader,sharedLoader这可能就是历史遗留的问题了。关于这两个classLoader的内容请在tomcat5.x找答案。
OK,下面我们只需要关注commonLoader变量了,在catalina.properties配置文件中可以得出
common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
下面相信看一下tomcat如何处理commonLoader:
当createClassLoader的参数为common,null的时候,
String value = CatalinaProperties.getProperty(name + ".loader");
此时的value为配置文件中的值(在我的调试环境中,下同):
${catalina.home}/lib,${catalina.home}/lib/*.jar
这个变量可能是不能解析的,需要将${catalina.home}替换为系统的据对路径,通过replace方法替换,可以看一下replace的定义:
protected String replace(String str) { // Implementation is copied from ClassLoaderLogManager.replace(), // but added special processing for catalina.home and catalina.base. String result = str; int pos_start = str.indexOf("${"); if (pos_start >= 0) { StringBuilder builder = new StringBuilder(); int pos_end = -1; while (pos_start >= 0) { builder.append(str, pos_end + 1, pos_start); pos_end = str.indexOf('}', pos_start + 2); if (pos_end < 0) { pos_end = pos_start - 1; break; } String propName = str.substring(pos_start + 2, pos_end); String replacement; if (propName.length() == 0) { replacement = null; } else if (Globals.CATALINA_HOME_PROP.equals(propName)) { replacement = getCatalinaHome(); } else if (Globals.CATALINA_BASE_PROP.equals(propName)) { replacement = getCatalinaBase(); } else { replacement = System.getProperty(propName); } if (replacement != null) { builder.append(replacement); } else { builder.append(str, pos_start, pos_end + 1); } pos_start = str.indexOf("${", pos_end + 1); } builder.append(str, pos_end + 1, str.length()); result = builder.toString(); } return result; }
通过replace返回的结果为:
value=F:\ITSM_V3.1\tomcatSource/lib,F:\ITSM_V3.1\tomcatSource/lib/*.jar
然后顺序执行,很容易得出结论,在执行
ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories, parent);
时候的repositories的值为:
调用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<URL>(); if (repositories != null) { for (Repository repository : repositories) { if (repository.getType() == RepositoryType.URL) { URL url = new URL(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 = directory.toURI().toURL(); 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 = file.toURI().toURL(); 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(); 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 = file.toURI().toURL(); 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]); } return AccessController.doPrivileged( new PrivilegedAction<StandardClassLoader>() { @Override public StandardClassLoader run() { if (parent == null) return new StandardClassLoader(array); else return new StandardClassLoader(array, parent); } }); }
最后返回的 return new StandardClassLoader(array);其中array是一个URL类型的数组,看结果的时候要对照前面的内容file:/F:/ITSM_V3.1/tomcatSource/lib/, file:/F:/ITSM_V3.1/tomcatSource/lib/annotations-api.jar.......(file:/F:/ITSM_V3.1/tomcatSource/lib/下的所有jar文件)。
然后将classLoader注册到MBeanServer中,然后返回classLoader。然后用返回的classLoader加载org.apache.catalina.startup.Catalina,然后进行反射。调用对应的方法。
下面说一下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."); }
其中load方法是将命令行定义的参数传递给通过反射调用的catalinaDaemon.load方法。然后执行的方法也就是在实现的时候调用catalinaDaemon中对应的方法,例如:
/** * Stop the Catalina Daemon. */ public void stop() throws Exception { Method method = catalinaDaemon.getClass().getMethod("stop", (Class [] ) null); method.invoke(catalinaDaemon, (Object [] ) null); }
在此文中我们得出在(tomcat7.0版本中):
(1)Bootstrap中如何通过创建的commonLoader=catalinaLoader=sharedLoader来加载类。
(2)在Bootstrap中使用反射机智来加载来调用catalinaDaemon中的方法。
(3)如何获取catalina.properties配置文件。
如果有疑问或者不对的地方,请指出。谢谢。