在研究Tomcat之前,一般是借用现有的UML工具分析Tomcat整体结构,但要分析Tomcat的流程就必须从分析Tomcat的StartUp入手。Tomcat的启动是从解析bat文件开始,bat文件最终调用org.apache.catalina.startup.Bootstrap开始类的加载。
一.Tomcat的ClassLoader:
TOMCAT自己的类载入器(ClassLoader)
+---------------------------+
| Bootstrap |
| | |
| System |
| | |
| Common |
| / / |
| Catalina Shared |
+---------------------------+
其中:
- Bootstrap - 载入JVM自带的类和$JAVA_HOME/jre/lib/ext/*.jar
- System
- 载入$CATALINA_HOME/bin/bootstrap.jar 初始化Tomcat,执行Main方法
- $JAVA_HOME/lib/tools.jar Sun的工具类,包括编译Jsp为Servlet的工具类
- Common
这个目录下的类虽然对TOMCAT和所有的WEB APP都可见.但是Web App的类不应该放在这个目录下,所有未打包的Class都在$CATALINA_HOME/common/classes下,所有打包的jar都在$CATALINA_HOME/commons/endorsed和$CATALINA_HOME/common/lib下,默认情况会包含以下几个包:
1. jndi.jar JNDI API接口,这个包仅在Java1.2时候装入,1.3以后的版本JDK已自动装入.
2. naming-common.jar JNDI接口实现类,Tomcat用这些类在内存中使用Context.
3. naming-resources.jar JNDI实现,Tomcat用它们定位Web App的静态资源.
4. servlet.jar Servlet,Jsp API
5. xerces.jar XML解析器,特定的Web App可以在自己的/WEB-INF/lib 中覆盖.
- Catalina
装入Tomcat实现所有接口的类,这些类对Web App是完全不可见的,所有未打包的类在$CATALINA_HOME/server/classes所有jar包在$CATALINA_HOME/server/lib下.一般情况该ClassLoader将Load下面几个包:
1. catalina.jar Servlet容器的Tomcat实现包
2. jakarta-regexp-X.Y.jar 正则表达式,请求过滤时使用
3. servlets-xxxxx.jar Servlet支持包
4. tomcat-coyote.jar Tomcat的Coyote连接实现包
5. tomcat-jk.jar Web Server绑定包,允许Tomcat绑定Apache等作为Web Server
6. tomcat-jk2.jar 功能同上
7. tomcat-util.jar Tomcat工具类,可能被一些Connector用到
8. tomcat-warp.jar 用于Apache Server包
- Shared 载入所有WEB APP都可见的类,对TOMCAT不可见. 所有未打包的类在
$CATALINA_HOME/shared/classes所有jar包在$CATALINA_HOME /lib下.
默认情况包含下面几个包:
1. jasper-compiler.jar Jsp编译器,编译Jsp为Servlet
2. jasper-runtime.jar Jsp(已编译成Servlet)运行支持包
3. naming-factory.jar 支持Web App使用JNDI的封装包
-WebAppX Web App ClassLoader,当Web App被部署是该ClassLoader被创建.所有class都在
WEB-INF/classes下,所有jar在WEB-INF/lib下.
特别注意WEB APP自己的ClassLoader的实现与众不同:
它先试图从WEB APP自己的目录里载入,如果失败则请求父ClassLoader的代理这样可以让不同的WEB APP之间的类载入互不干扰.另,Tomcat Server使用的是CatalinaClassLoader,一般的Web App使用的WebApp ClassLoader.
二. org.apache.catalina.startup.Bootstrap
该类是Tomcat的执行入口点,我们着重分析下面两个方法:
2.1. initClassLoaders,创建ClassLoader层次
private void initClassLoaders() { try { ClassLoaderFactory.setDebug(debug); //创建common ClassLoader,没有父ClassLoader commonLoader = createClassLoader("common", null); //创建catalina ClassLoader,父ClassLoader为common catalinaLoader = createClassLoader("server",commonLoader); //创建shared ClassLoader, 父ClassLoader为common sharedLoader = createClassLoader("shared",commonLoader); } catch (Throwable t) { log("Class loader creation threw exception", t); System.exit(1); } }
2.2 createClassLoader,负责具体的创建工作
在$CATALINA_HOME/conf/catalina.properties中定义了common, server, shared ClassLoader载入类的路径及一些包的安全权限.
//common载入类的路径 common.loader=${catalina.home}/common/classes, ${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar //server载入类的路径 server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar //shared载入类的路径 shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar /** *param name:Load Name *param parent:父Loader *classLoader的资源分三种: *1.未打包的classes,一般是一个目录 *2.打包的jar目录 *3.网络资源,一般是网上的一个jar包 (Applet经常用到这样的loader) */ private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { //从catalina.properties中取得改Loader的配置信息 String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; //classes目录 ArrayList unpackedList = new ArrayList(); //jar目录 ArrayList packedList = new ArrayList(); //网络路径指定的包 ArrayList urlList = new ArrayList(); StringTokenizer tokenizer = new StringTokenizer(value, ","); //当前Loader该装载的类 while (tokenizer.hasMoreElements()) { String repository = tokenizer.nextToken(); // Check for a JAR URL repository try { //如果是网络路径追加url urlList.add(new URL(repository)); continue; } catch (MalformedURLException e) { // Ignore } // 本地路径 boolean packed = false; //${catalina.home} if (repository.startsWith(CATALINA_HOME_TOKEN)) { repository = getCatalinaHome() + repository.substring(CATALINA_HOME_TOKEN.length()); //${catalina.base} } else if (repository.startsWith(CATALINA_BASE_TOKEN)) { repository = getCatalinaBase() + repository.substring(CATALINA_BASE_TOKEN.length()); } /**经过上述操作,把catalina.properties里的路径替换成绝对路径*/ //如果是jar文件路径 if (repository.endsWith("*.jar")) { packed = true; repository = repository.substring (0, repository.length() - "*.jar".length()); } if (packed) { packedList.add(new File(repository)); } else { unpackedList.add(new File(repository)); } } File[] unpacked = (File[]) unpackedList.toArray(new File[0]); File[] packed = (File[]) packedList.toArray(new File[0]); URL[] urls = (URL[]) urlList.toArray(new URL[0]); //调用Factory的方法创建ClassLoader return ClassLoaderFactory.createClassLoader (unpacked, packed, urls, parent); }
三. ClassLoaderFactory
ClassLoaderFactory是用于创建ClassLoader的工厂类,这个类比较简单.
//参数含义不再说明,参看上面的分析 public static ClassLoader createClassLoader(File unpacked[], File packed[], URL urls[], ClassLoader parent) throws Exception { if (debug >= 1) log("Creating new class loader"); // Construct the "class path" for this class loader ArrayList list = new ArrayList(); // 通过class目录构造file协议的url,并追加的list if (unpacked != null) { for (int i = 0; i < unpacked.length; i++) { File file = unpacked[i]; if (!file.exists() || !file.canRead()) continue; if (debug >= 1) log(" Including directory or JAR " + file.getAbsolutePath()); URL url = new URL("file", null, file.getCanonicalPath() + File.separator); list.add(url.toString()); } } //取出所有jar目录里的jar文件,逐一构造url,并追加的list if (packed != null) { for (int i = 0; i < packed.length; i++) { File directory = packed[i]; if (!directory.isDirectory() || !directory.exists() || !directory.canRead()) continue; String filenames[] = directory.list(); for (int j = 0; j < filenames.length; j++) { String filename = filenames[j].toLowerCase(); if (!filename.endsWith(".jar")) continue; File file = new File(directory, filenames[j]); if (debug >= 1) log(" Including jar file " + file.getAbsolutePath()); URL url = new URL("file", null, file.getCanonicalPath()); list.add(url.toString()); } } } //追加网络路径的资源 if (urls != null) { for (int i = 0; i < urls.length; i++) { list.add(urls[i].toString()); } } //调用StandardClassLoader创建实际的Loader String array[] = (String[]) list.toArray(new String[list.size()]); StandardClassLoader classLoader = null; if (parent == null) classLoader = new StandardClassLoader(array); else classLoader = new StandardClassLoader(array, parent); classLoader.setDelegate(true); return (classLoader); }
四. StandardClassLoader
StandardClassLoader继承了URLClassLoader, URLClassLoader类具有从硬盘目录装载类,或从本地或远程装载jar文件的能力.这个类也实现了Reloader接口,提供了自动重新装载类的功能.我们主要看这个类以下及个方法:
4.1.构造函数StandardClassLoader
/** * @param repositories url数组,见上分析 * @param parent 父loader */ public StandardClassLoader(String repositories[], ClassLoader parent) { //调用父类的构造函数 //父类的构造函数将用转换后的repositories生成URLClassPath的实例 //ucp是类成员变量ucp= new URLClassPath(urls); // URLClassPath是sun的扩展包,无法继续跟踪 super(convert(repositories), parent); this.parent = parent; this.system = getSystemClassLoader(); securityManager = System.getSecurityManager(); if (repositories != null) { for (int i = 0; i < repositories.length; i++) //处理url addRepositoryInternal(repositories[i]); } }
4.2 addRepositoryInternal
/** * @param repository 要处理的url */ protected void addRepositoryInternal(String repository) { URLStreamHandler streamHandler = null; String protocol = parseProtocol(repository); if (factory != null) streamHandler = factory.createURLStreamHandler(protocol); // 当前url是指向本地或网路的jar文件,验证jar的正确性 //下面的代码看似无用其实是在验证jar文件的正确性,如果jar文件错误抛异常中止执行. if (!repository.endsWith(File.separator) && !repository.endsWith("/")) { JarFile jarFile = null; try { Manifest manifest = null; //jar协议 if (repository.startsWith("jar:")) { URL url = new URL(null, repository, streamHandler); JarURLConnection conn = (JarURLConnection) url.openConnection(); conn.setAllowUserInteraction(false); conn.setDoInput(true); conn.setDoOutput(false); conn.connect(); jarFile = conn.getJarFile(); //file协议 } else if (repository.startsWith("file://")) { jarFile = new JarFile(repository.substring(7)); //file } else if (repository.startsWith("file:")) { jarFile = new JarFile(repository.substring(5)); //本地路径的jar文件 } else if (repository.endsWith(".jar")) { URL url = new URL(null, repository, streamHandler); URLConnection conn = url.openConnection(); JarInputStream jis = new JarInputStream(conn.getInputStream()); manifest = jis.getManifest(); //其他情况均为错误 } else { throw new IllegalArgumentException ("addRepositoryInternal: Invalid URL '" + repository + "'"); } } catch (Throwable t) { t.printStackTrace(); throw new IllegalArgumentException ("addRepositoryInternal: " + t); } finally { if (jarFile != null) { try { jarFile.close(); } catch (Throwable t) {} } } } //增加当前的url到系统内部的url列表 synchronized (repositories) { String results[] = new String[repositories.length + 1]; System.arraycopy(repositories, 0, results, 0, repositories.length); results[repositories.length] = repository; repositories = results; } }
repositories是一个类变量,存放着所有的url列表.
通过这个方法我们基本可以确定几点:
1.ClassLoader可以把一个以”/”或File.separator结尾的路径作为装载类的资源
2.也可以把本地或网路上的jar文件作为装入class的资源,其他任何形式的资源(比如zip)都是不合法的.
3.网络上的资源必须是jar包的形式
到此,ClassLoader的构造结束.
4.3 loadClass方法
上面我们做了很多的工作,目的是构建一个classLoader,构建ClassLoader的目的是用它load想要的class,因此loadClass方法才是我们的重心,同时通过它可以彻底解开ClassLoader的神秘面纱.
/** *param name:要load的类名 *param resolve:如果是true将调用resolveClass */ public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (debug >= 2) log("loadClass(" + name + ", " + resolve + ")"); Class clazz = null; //检查缓存,看是否类已经被load clazz = findLoadedClass(name); if (clazz != null) { if (debug >= 3) log(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // 如果是java包用系统ClassLoader取load if( name.startsWith("java.") ) { ClassLoader loader = system; clazz = loader.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return (clazz); } throw new ClassNotFoundException(name); } // 检查是否有权限对该类对应的包做load,包的load权限在catalina.properties里定义 if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) { try { securityManager.checkPackageAccess(name.substring(0,i)); } catch (SecurityException se) { String error = "Security Violation, attempt to use " + "Restricted Class: " + name; System.out.println(error); se.printStackTrace(); log(error); throw new ClassNotFoundException(error); } } } //该类是否委派给父ClassLoader去装载 if (delegate) { if (debug >= 3) log(" Delegating to parent classloader"); ClassLoader loader = parent; if (loader == null) loader = system; try { clazz = loader.loadClass(name); if (clazz != null) { if (debug >= 3) log(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } //从本地装载该类 if (debug >= 3) log(" Searching local repositories"); try { //调用Tomcat实现ClassLoader的核心方法去寻找类 clazz = findClass(name); if (clazz != null) { if (debug >= 3) log(" Loading class from local repository"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } //如果该类没有被委派到父类,则用系统loader去装载 if (debug >= 3) log(" Delegating to parent classloader"); ClassLoader loader = parent; if (loader == null) loader = system; try { clazz = loader.loadClass(name); if (clazz != null) { if (debug >= 3) log(" Loading class from parent"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } // This class was not found throw new ClassNotFoundException(name); }
4.4 findClass方法
public Class findClass(String name) throws ClassNotFoundException { if (debug >= 3) log(" findClass(" + name + ")"); // 检查包的定义,我们不对此处做深究可略过 if (securityManager != null) { int i = name.lastIndexOf('.'); if (i >= 0) { try { if (debug >= 4) log(" securityManager.checkPackageDefinition"); securityManager.checkPackageDefinition(name.substring(0,i)); } catch (Exception se) { if (debug >= 4) log(" -->Exception-->ClassNotFoundException", se); throw new ClassNotFoundException(name); } } } // 如果在本地不能地位类则请求父类加载 // (throws ClassNotFoundException if it is not found) Class clazz = null; try { if (debug >= 4) log(" super.findClass(" + name + ")"); try { synchronized (this) { clazz = findLoadedClass(name); if (clazz != null) return clazz; //请求父类加载 clazz = super.findClass(name); } } catch(AccessControlException ace) { throw new ClassNotFoundException(name); } catch (RuntimeException e) { if (debug >= 4) log(" -->RuntimeException Rethrown", e); throw e; } if (clazz == null) { if (debug >= 3) log(" --> Returning ClassNotFoundException"); throw new ClassNotFoundException(name); } } catch (ClassNotFoundException e) { if (debug >= 3) log(" --> Passing on ClassNotFoundException", e); throw e; } // Return the class we have located if (debug >= 4) log(" Returning class " + clazz); if ((debug >= 4) && (clazz != null)) log(" Loaded by " + clazz.getClassLoader()); return (clazz); }
4.5 父类(URLClassLoader)的findClass方法
前面我们曾经说过,URLClassLoader具有从本地装载class,从本地或网络装载jar的功能.
简单说findClass方法只是简单地调用了PrivilegedExceptionAction的run方法,关于这个类我们不必细致了解,只需看下面打蓝色的部分就可以了.
protected Class findClass(final String name) throws ClassNotFoundException { try { return (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws ClassNotFoundException { //将类名替换成硬盘绝对路径 String path = name.replace('.', '/').concat(".class"); //关于ucp类变量的说明见上面 //该调用主要是把本地的class文件转换成Resource(不考虑转换细节) Resource res = ucp.getResource(path, false); if (res != null) { try { //通过类名与类的Resource生成Class return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { throw new ClassNotFoundException(name); } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } }
4.6 defineClass方法
该方法主要是调用JVM的native方法构建一个class对象,关于这个类的细节我们没必要很清除,我们要知道的是JVM利用Resource构造了Class对象.我们只需关注下面蓝色的部分.
/** *param name:类名 *param res:这个参数是通过ucp变量得到,ucp是通过我们传入的url构建,url就是 *ClassLoader Load Class 的路径 *换句话说我们辛苦构造的StandClassLoader就是为了得到Resource *Resource是JVM用来构造Class必须条件 */ private Class defineClass(String name, Resource res) throws IOException { int i = name.lastIndexOf('.'); URL url = res.getCodeSourceURL(); if (i != -1) { String pkgname = name.substring(0, i); // Check if package already loaded. Package pkg = getPackage(pkgname); Manifest man = res.getManifest(); if (pkg != null) { // Package found, so check package sealing. if (pkg.isSealed()) { // Verify that code source URL is the same. if (!pkg.isSealed(url)) { throw new SecurityException( "sealing violation: package " + pkgname + " is sealed"); } } else { // Make sure we are not attempting to seal the package // at this code source URL. if ((man != null) && isSealed(pkgname, man)) { throw new SecurityException( "sealing violation: can't seal package " + pkgname + ": already loaded"); } } } else { if (man != null) { definePackage(pkgname, man, url); } else { definePackage(pkgname, null, null, null, null, null, null, null); } } } //下面是JVM通过二进制代码构造一个Class byte[] b = res.getBytes(); java.security.cert.Certificate[] certs = res.getCertificates(); CodeSource cs = new CodeSource(url, certs); return defineClass(name, b, 0, b.length, cs); }
至此,我们彻底破译了Tomcat的ClassLoader,下面我们再画一下ClassLoader的构造及loadClass的过程.这里不再画sequence图,只是简单表示一下各方法之间的调用关系.
其实,从上面我们可以真正起到创建ClassLoader关键作用的是URLClassLoader和JVM的defineClass方法,这些方法已被封装或是本地调用,要进一步研究就需要花费更多的精力.阅读了这个流程后相信每人都能定制出己需要的ClassLoader,比如加密ClassLoader.