- 浏览: 26463 次
- 性别:
- 来自: 广州
最新评论
在讲Tomcat的载入器之前,先要了解一下java的类加载机制,这里就不具体说了,仅仅写一点我认为比较重要的东西:
1:一般实现自己的类加载器是重写ClassLoader的findClass方法,然后在这个方法里面读取class文件为byte[]数组,传入defineClass方法,defineClass方法返回我们加载的类。这样便实现了我们自己的简单的类加载器。下面是一个简单的自定义类加载器的findClass方法:
protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); //getClassData方法是通过class文件的名字得到一个字节数组 if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); //加载进jvm中。 } }
问题是: 可不可以重写ClassLoader的loadClass方法来实现自己的类加载器? 答案是可以,Tomcat就是用的这种方法。jdk建议我们实现自己的类加载器的时候是重写findClass方法,不建议重写loadclass方法,因为ClassLoader的loadclass方法保证了类加载的父亲委托机制,如果重写了这个方法,就意味着需要实现自己在重写的loadclass方法实现父亲委托机制,下面看看ClassLoader的loadclass方法,看怎么实现父亲委托机制的:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name);//检查该类是否已经被加载了 if (c == null) { try { if (parent != null) {//如果父加载器不为空就用父加载器加载该类。 c = parent.loadClass(name, false); } else { c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name);//在自己实现的类加载器中重写这个findClass方法。 } } if (resolve) { resolveClass(c); } return c; }
这样就保证了父亲委托机制。当所有父类都加载不了,才会调用findClass方法,即调用到我们自己的类加载器的findClass方法。以此实现类加载。
2:在我们自己实现的类加载器中,defineClass方法才是真正的把类加载进jvm,defineClass是从ClassLoader继承而来,把一个表示类的字节数组加载进jvm转换为一个类。
3:我们自己实现的类加载器跟系统的类加载器没有本质的区别,最大的区别就是加载的路径不同,系统类加载器会加载环境变量CLASSPATH中指明的路径和jvr文件,我们自己的类加载器可以定义自己的需要加载的类文件路径.同样的一个class文件,用系统类加载器和自定义加载器加载进jvm后类的结构是没有区别的,只是他们访问的权限不一样,生成的对象因为加载器不同也会不一样.当然我们自己的类加载器可以有更大的灵活性,比如把一个class文件(其实就是二进制文件)加密后(简单的加密就把0和1互换),系统类加载器就不能加载,需要由我们自己定义解密类的加载器才能加载该class文件.
现在来初步的看看Tomcat的类加载器,为什么Tomcat要有自己的类加载器.这么说吧,假如没有自己的类加载器,我们知道,在一个Tomcat中是可以部署很多应用的,如果所有的类都由系统类加载器来加载,那么部署在Tomcat上的A应用就可以访问B应用的类,这样A应用与B应用之间就没有安全性可言了。还有一个原因是因为Tomcat需要实现类的自动重载,所以也需要实现自己的类加载器。Tomcat的载入器是实现了Loader接口的WebappLoader类,也是Tomcat的一个组件,实现Lifecycle接口以便统一管理,启动时调用start方法,在start方法主要做了以下的事情:
1:创建一个真正的类加载器以及设置它加载的路径,调用createClassLoader方法
2:启动一个线程来支持自动重载,调用threadStart方法
看createClassLoader方法:
private WebappClassLoader createClassLoader() throws Exception { Class clazz = Class.forName(loaderClass); WebappClassLoader classLoader = null; if (parentClassLoader == null) { // Will cause a ClassCast is the class does not extend WCL, but // this is on purpose (the exception will be caught and rethrown) classLoader = (WebappClassLoader) clazz.newInstance(); } else { Class[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); } return classLoader; }
在createClassLoader方法内,实例化了一个真正的类加载器WebappClassLoader,代码很简单。WebappClassLoader是Tomcat的类加载器,它继承了URLClassLoader(这个类是ClassLoader的孙子类)类。重写了findClass和loadClass方法。Tomcat的findClass方法并没有加载相关的类,只是从已经加载的类中查找这个类有没有被加载,具体的加载是在重写的loadClass方法中实现,从上面的对java的讨论可知,重写了loadClass方法就意味着失去了类加载器的父亲委托机制,需要自己来实现父亲委托机制。Tomcat正是这么做的,下面看WebappClassLoader的loadClass方法:
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (debug >= 2) log("loadClass(" + name + ", " + resolve + ")"); Class clazz = null; // Don't load classes if class loader is stopped if (!started) { log("Lifecycle error : CL stopped"); throw new ClassNotFoundException(name); } // (0) Check our previously loaded local class cache //查找本地缓存是否已经加载了该类 clazz = findLoadedClass0(name); if (clazz != null) { if (debug >= 3) log(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.1) Check our previously loaded class cache clazz = findLoadedClass(name); if (clazz != null) { if (debug >= 3) log(" Returning class from cache"); if (resolve) resolveClass(clazz); return (clazz); } // (0.2) Try loading the class with the system class loader, to prevent // the webapp from overriding J2SE classes try { clazz = system.loadClass(name);//(0.2)先检查系统ClassLoader,因此WEB-INF/lib和WEB-INF/classes或{tomcat}/libs下的类定义不能覆盖JVM底层能够查找到的定义(譬如不能通过定义java.lang.Integer替代底层的实现 if (clazz != null) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } ............................................... //这是一个很奇怪的定义,JVM的ClassLoader建议先由parent去load,load不到自己再去load(见如上ClassLoader部分),而Servelet规范的建议则恰好相反,Tomcat的实现则做个折中,由用户去决定(context的delegate定义),默认使用Servlet规范的建议,即delegate=false boolean delegateLoad = delegate || filter(name); //(1)先由parent去尝试加载,此处的parent是SharedClassLoader,见如上说明,如上说明,除非设置了delegate,否则这里不执行 // (1) Delegate to our parent if requested if (delegateLoad) { if (debug >= 3) log(" Delegating to parent classloader"); ClassLoader loader = parent; if (loader == null)//此处parent是否为空取决于context的privileged属性配置,默认privileged=true,即parent为SharedClassLoader 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) { ; } } // 到WEB-INF/lib和WEB-INF/classes目录去搜索,细节部分可以再看一下findClass,会发现默认是先搜索WEB-INF/classes后搜索WEB-INF/lib if (debug >= 3) log(" Searching local repositories"); try { clazz = findClass(name); if (clazz != null) { if (debug >= 3) log(" Loading class from local repository"); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } // (3) Delegate to parent unconditionally if (!delegateLoad) { 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); }
上面的代码很长,但实现了Tomcat自己的类加载机制,具体的加载规则是:
1:因为所有已经载入的类都会缓存起来,所以先检查本地缓存
2:如本地缓存没有,则检查上一级缓存,即调用ClassLoader类的findLoadedClass()方法;
3:若两个缓存都没有,则使用系统的类进行加载,防止Web应用程序中的类覆盖J2EE的类
4:若打开标志位delegate(表示是否代理给父加载器加载),或者待载入的类是属于包触发器的包名,则调用父类载入器来加载,如果父类载入器是null,则使用系统类载入器
5:从当前仓库中载入相关类
6:若当前仓库中没有相关类,且标志位delegate为false,则调用父类载入器来加载,如果父类载入器是null,则使用系统类载入器(4跟6只能执行一个步骤的)
这里有有一点不明白的是在第三步使用系统类加载若失败后,在第四步和第六步就没必要但父类载入器为null的时候再用系统类载入器来载入了???有谁明白的麻烦留个言,不胜感激~
参考了一下http://ayufox.iteye.com/blog/631190这篇博文.
发表评论
-
Tomcat源码分析(一)--服务启动
2012-07-05 11:11 430对Tomcat感兴趣是由 ... -
Tomcat源码分析(二)--连接处理
2012-07-06 08:35 387目标:在这篇文章希望搞明白http请求到tomcat后是 ... -
Tomcat源码分析(三)--连接器是如何与容器关联的?
2012-07-06 11:18 537这篇文章要弄懂一个问题,我们知道,一个链接器是跟一个容器关 ... -
Tomcat源码分析(四)--容器处理链接之责任链模式
2012-07-07 16:21 359目标:在这篇文章希望搞明白connector.getCont ... -
Tomcat源码分析(五)--容器处理连接之servlet的映射
2012-07-08 09:32 398本文所要解决的问题:一个http请求过来,容器是怎么知道选 ... -
Tomcat源码分析(六)--日志记录器和国际化
2012-07-09 08:34 489日志记录器挺简单的,没有很多东西,最主要的就是一个Lo ... -
Tomcat源码分析(七)--单一启动/关闭机制(生命周期)
2012-07-10 12:23 447在前面的大部分文章都是讲连接器和容器的,以后的内容会偏向写一 ... -
Tomcat源码分析(十)--部署器
2012-07-12 09:02 491我们知道,在Tomcat的世界里,一个Host容器代表一 ... -
Tomcat源码分析(九)--Session管理
2012-07-11 15:16 646在明白Tomcat的Session机 ...
相关推荐
tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-redis-session-manager-1.2-tomcat-7-java-7tomcat-...
tomcat-redis-session-manager源码
解决tomcat8-maven-plugin-3.0-r1655215.jar阿里云同有的问题。放到路径org\apache\tomcat\maven\tomcat8-maven-plugin\3.0-r1655215\就可以了
开发工具 apache-tomcat-8.0.41-windows-x86开发工具 apache-tomcat-8.0.41-windows-x86开发工具 apache-tomcat-8.0.41-windows-x86开发工具 apache-tomcat-8.0.41-windows-x86开发工具 apache-tomcat-8.0.41-...
tomcat6-dta-ssl-1.0.0.jar 此类文件将有助于tomcat支持ssl协议
tomcat9负载均衡tomcat-cluster-redis-session-manager_4.0
apache-tomcat-8.5.20.tar.gz源码包和context.xml文件,这套配置是我自己亲测可用的。。另外我用的redis4这个版本。注意:如果你使用的TOMCAT其他版本。例如tomcat6或者7这套JAR包可能不可用,tomcat8.0没有测试。...
Maven使用tomcat8-maven-plugin插件
Tomcat8亲测可用 tomcat-redis-session-manager的jar包 修改了tomcat-redis-session-manager源码进行的编译生成的jar包
文件名写错了,此压缩文件支持tomcat8.5。是否支持8.0请自行测试,本人只测试了8.5,可以使用。压缩文件包括tomcat-redis-session-manager-...apache-tomcat-8.5.33.tar.gz,nginx-1.6.2.tar.gz也打包进去,一步到位。
修改版tomcat7-maven-plugin-2.2.jar
支持tomcat8的redis-session-manager-master,测试环境jdk-8u191、apache-tomcat-8.0.53
tomcat-redis-session-manager-2.0.0.jar
用于配置 tomcat-redis-session-manager
tomcat-embed-core-8.5.23.jar
tomcat-juli.jar和tomcat-juli-adapters.jar tomcat-juli.jar和tomcat-juli-adapters.jar
tomcat6-maven-plugin-2.1插件包
apache-tomcat-9.0.45-windows-x64apache-tomcat-9.0.45-windows-x64apache-tomcat-9.0.45-windows-x64apache-tomcat-9.0.45-windows-x64apache-tomcat-9.0.45-windows-x64apache-tomcat-9.0.45-windows-x64apache-...
使用tomcat-redis-session-manager进行统一session管理所需jar包,包括tomcat6-jdk6、tomcat7-jdk7、tomcat8-jdk8