在上一章查看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配置文件。