内容简介:【Tomcat学习笔记】9-ClassLoader
一直在入门,但从未精通,所以不敢说深入分析 XXX,顶多就是一个学习笔记,但是是原创,转载请注明出处,好吧,说的好像有人会看一样。
说明,为了简洁,这里贴的代码都有所删减。
先来张图:
ClassLoader基础
Java 自带了三个类加载器,BootstrapClassLoader、ExtClassLoader 和 AppClassLoader,它们之间的层级关系如图,上面的 ClassLoader 是下面 ClassLoader 的 parent.不同的 ClassLoader 有不同的职责:
- BootstrapClassLoader, 引导类加载器,加载JDK最核心的类,比如 rt.jar, java.lang.String, java.lang.Integer 这些类都在这个 JAR 中
- ExtClassLoader, 扩展类加载器,加载 jre/lib/ext 目录下的类
- AppClassLoader, 系统类加载器,加载应用程序 classpath 目录下的所有jar和class文件
如果想看 ClassLoader 具体加载了哪些类,可以 getURLs, 然后将它们打印出来
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); URL[] urls = ((URLClassLoader)Thread.currentThread().getContextClassLoader()).getURLs();
应用程序也可以自己 创建 ClassLoader, 指定它的 parent, 指定它要加载的 类。那么,类的加载过程是怎样的呢,这里有个 双亲委派模型(parent delegation model) 的概念。即,某 ClassLoader XXX 加载一个类的时候,一直向上委托,直到最顶端,然后开始加载,如果顶端找不到该类,再依次往下,最后回到 XXX , 如果 XXX 自己还是找不到该类,就报 ClassNotFoundException . 双亲委派模型这种机制解决了什么问题呢,如果不用这种机制,用户可以自己定义一个JDK中的类(package也要一样)替换掉JDK中类。(这里有个问题,如果一个类已经被系统加载过了,再次加载的时候是会报错呢,还是以第一次加载的为准呢,还是以最后一次加载的为准呢?你这么聪明,肯定不用我说了)。上面大概就是 Java 自带 ClassLoader 的一些关键知识了,主要就是一个层级关系和双亲委托模型。
Tomcat的三大ClassLoader
So, 为什么 Tomcat 里要自定义 ClassLoader 呢,先来考虑一个问题:一个Tomcat 部署两个应用,App1 和 App2, App1 里定义了一个 com.fdx.AAA 类,App2 也定义了一个 com.fdx.AAA 类,但是里面的实现是不一样的,如果不自定义 ClassLoader,
而都用 AppClassLoader 来加载的话,你让它加载哪一个呢,一个 ClassLoader 是不能加载两个一样的类的。所以,ClassLoader 最重要的一个功能就是 类隔离。
接下来看看 Tomcat 是如何弄的. Bootstrap#initClassLoaders 方法中会去创建 commonLoader(对应配置common.loader)、catalinaLoader(对应配置server.loader) 和 sharedLoader(对应配置shared.loader),每个 loader 要加载哪些路径下载的类会在 conf/catalina.properties 中配置,默认配置是:
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" server.loader= shared.loader=
在server.loader和shared.loader为空的情况下,这三个其实是同一个 ClassLoader, 不为空的情况下,commonLoader是它们的parent. 所以我图中将它们画在了一个层级。
Tomcat的WebappClassLoader创建过程
WebappClassLoader 和 StandardContext 是一一对应的,StandardContext 和 应用又是一一对应的。每个 WebappClassLoader 只加载对应应用的类,这样应用之间的类就隔离了。来看 StandardContext 启动过程中的一段代码。
protected synchronized void startInternal()throwsLifecycleException{ if (getLoader() == null) { WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); webappLoader.setDelegate(getDelegate()); setLoader(webappLoader); } ClassLoader oldCCL = bindThread(); Loader loader = getLoader(); if ((loader != null) && (loader instanceof Lifecycle)) ((Lifecycle) loader).start(); unbindThread(oldCCL); oldCCL = bindThread(); }
3、4、5 行 创建了一个 WebappLoader(这个并不是ClassLoader,它的作用是来管理WebappClassLoader),设置 delegate(delegate为true是,就是正常的加载顺序,为false,就不是标准的双亲委派了,而是WebappClassLoader自己先加载,加载不到再往上委派,默认是false)。11行启动了 WebappLoader, WebappLoader#startInternal 方法里会去创建 WebappClassLoader,设置它的 classPath, 安全权限,启动WebappClassLoader(Tomcat里基本上是个东西都实现了Lifecycle接口)。最后通过ubind, bind操作将当前线程的ClassLoader 设置为 WebappClassLoader。 WebappClassLoader的启动过程值得关注下,这里将应用的 /WEB-INF/classes 和 /WEB-INF/lib 加到了 localRepositories 中,这两个目录一个是打包后应用自身代码所在目录,一个是应用依赖的jar所以的目录。
public void start()throwsLifecycleException{ WebResource classes = resources.getResource("/WEB-INF/classes"); if (classes.isDirectory() && classes.canRead()) { localRepositories.add(classes.getURL()); } WebResource[] jars = resources.listResources("/WEB-INF/lib"); for (WebResource jar : jars) { if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) { localRepositories.add(jar.getURL()); jarModificationTimes.put( jar.getName(), Long.valueOf(jar.getLastModified())); } } }
默认情况都是使用lWebappClassLoader, 在 Tomcat 8+ 中,还加入了 ParallelWebappClassLoader,可以并行的加载类。用并行加载,需要在conf/context.xml中配置loaderClass,但是一般情况下,这种需求不大,得要多大的项目才有那么多类需要并行来加载啊。关于并行类加载器,有兴趣可以看 Tomcat8+引入的并发ClassLoader
Tomcat的WebappClassLoader加载类的过程
WebappClassLoaderBase#loadClass 方法实现了整个加载过程,主要有以下操作
- 检查该类是否有被加载过,如果加载过了,直接返回
- 使用 system classLoader 加载该类,如果加载到了,直接返回。(这一步主要是为了防止应用实现JDK里的类)
- 用SecurityManager进行安全检查,如果不通过,就抛出ClassNotFoundException, 这个安全过滤机制,是可以配置的,后面有空在细说
- 接下来有两个分支,如果delegate是true, 则先用parent加载,加载不到再自己加载。如果是false,则自己先加载,加载不到再用parent加载
那么 WebappClassLoader 自己在加载类的时候是如何绕过双亲委派机制的呢,代码好多,说不清楚,自己看去吧。
最后,留个问题, 为什么Tomcat要打破这种双亲委派机制呢?
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【每日笔记】【Go学习笔记】2019-01-04 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-02 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-07 Codis笔记
- Golang学习笔记-调度器学习
- Vue学习笔记(二)------axios学习
- 算法/NLP/深度学习/机器学习面试笔记
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。