Resin服务器getResource揭秘
(感谢网友 liuxiaori 继续分享其经历)这样的详细的图文并茂的文章让我很佩服!
目录
前言
接上文“由一个问题到Resin ClassLoader的学习”,本文将以this.getClass().getResource(“/”).getPath()和this.getClass().getResourceAsStream(“/a.txt”)为例,一步步解析加载的过程。
调试环境
- 下载resin3.0.23的源码(http://www.caucho.com/download/resin-3.0.23-src.zip)。
- 部署到myeclipse中,有错误,本人忽略了。Resin可运行。
- 将EhCacheTestAnnotation部署到resin3.0.23中。
- 调试this.getClass().getResource(“/”).getPath()。
问题来了,无论如何也模拟不出来<compiling-loader>所造成的影响,一直输出:/D:/work_other/project/resin-3.0.23/bin/ 。无奈之下,采用了这种方式:使用两个eclipse,一个使用发布版本的,部署EhCacheTestAnnotation进行调试;另外一个部署resin3.0.23源码,调试到哪里对照看源码。
开始
1) this.getClass().getResource(“/”).getPath()
本次调试涉及的所有类加载器为:
EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]
EnvironmentClassLoader$7806641[host:http://localhost:8787]
EnvironmentClassLoader$22459270[servlet-server:]
首先进入Class的getResource(String name)方法,如下图:
最后委托给ClassLoader的getResource方法。那么这个ClassLoader是哪个呢?一看下图便知:
是DynamicClassLoader的getResource方法,原理上文已述。
最终会委托给[email protected]类加载器的getResource方法,返回null,然后开始回溯。
还记得吗?当java.net.URLClassLoader分支的ClassLoader的getResource方法返回值为null后,就要遍历嵌入DynamicClassLoader中的Resin的Loader(即_loaders集合)。
当然回溯到EnvironmentClassLoader$22459270[servlet-server:]中,那么它中_loaders这个集合中的Loader又有哪些呢?
以图为证,当天确实回溯到该ClassLoader,而且开始准备遍历_loaders集合。
DynamicClassLoader的1306行,没问题,resin3.0.23源码截图为证:
不做多余解释,那么“servlet-server”这个ClassLoader中的_loaders集合中都放了一些什么呢?
存放了两个TreeLoader(Loader的子类),然未找到结果,返回null。继续回溯。
这次轮到遍历EnvironmentClassLoader$7806641[host:http://localhost:8787]的_loaders。下图为证:
_loaders中的内容如下图:
比较长,我贴出来:
[CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes], LibraryLoader[[email protected]], CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes], LibraryLoader[[email protected]], CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes], LibraryLoader[[email protected]]]
注意到了吧,主角来了。那仔细调试下把。爆料一下:CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes]就是主角。
看到了吧,遍历时,当前的Loader为CompilingLoader[src:/D:/work/resin-3.0.23/webapps/WEB-INF/classes],而且url可是不为null了哦。再贴一张,看看url的值到底是什么!
嗯,不用多做解释了吧。
最后看看程序输出是否吻合,如下图:
然后修改resin.conf中的<compiling-loader>将其注释掉,看看程序结果会不会是我们期望的:/D:/work/resin-3.0.23/webapps/EhCacheTestAnnotation/WEB-INF/classes/。拭目以待。
为节省篇幅,一下只关注关键位置。
首先调试到EnvironmentClassLoader$7806641[host:http://localhost:8787],我们需要停下来一下。
再看一下_loaders的值。
贴一个详细的:
[LibraryLoader[[email protected]], LibraryLoader[[email protected]], LibraryLoader[[email protected]]]
对比一下,在注释掉<compiling-loader>后,loaders中是没有CompilingClassLoader实例的。
继续,下面就轮到EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]这个ClassLoader了,会是什么样子呢?
进入该ClassLoader时,url值依旧为null,那_loaders会有变化吗?如下图:
继续遍历_loaders。
到这里就结束了,url在EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]中被加载。
1) this.getClass().getResourceAsStream(“/a.txt”)
getResourceAsStream(String name)方法也是采用双亲委派的方式。在前一篇文章中提出“getResourceAsStream可是将获取路径委托给getResource,<compiling-loader>却没有对getResourceAsStream产生影响”
ClassLoader中getResourceAsStream源码也确实是委托为getResource了,可是为什么呢?
getResourceAsStream(String name)方法。
public InputStream getResourceAsStream(String name) { URL url = getResource(name); try { return url != null ? url.openStream() : null; } catch (IOException e) { return null; } }
其实不难解释,JVM中ClassLoader的getResourceAsStream(“/a.txt”)返回了null,然后开始回溯,与getResource方法的原理一致,直到某个ClassLoader及其子类或者Loader及其子类找到了”/a.txt”,并以流的形式返回,当然谁都没找到就返回null。
捡重点的说。
调试到[email protected],即ClassLoader的子类,情形如下图:
看见getResource(name)喽,按F5进去看个究竟。如下图,其parent为:[email protected],其返回null。
开始回溯到:EnvironmentClassLoader$1497769[servlet-server:],与getResource方法一致,开始遍历_loaders集合。
这样就可以解释为何<compiling-loader>没有影响到getResourceAsStream了。因为资源(这里是/a.txt),就不是由AppClassLoader和ExtClassLoader加载的,而是由DynamicClassLoader或者其内部的_loaders集合完成的加载。或者更确切的说是由CompilingClassLoader获取到的URL,再转换成InputStream。
<comiling-loader>其实对getResourceAsStream还是有点影响的,如果配置中配置了<comiling-loader>,并且<comiling-loader>配置的路径下,与实际项目的指定路径下,都放置了同名资源,则会先加载<comiling-loader>配置路径下的资源。
比如,下图所示:
<compiling-loader>配置的路径为:<compiling-loader path=”webapps/WEB-INF/classes”/>
在加载”/a.txt”时,优先加载webapps/WEB-INF/classes/a.txt。
总结
- <compiling-loader>如被注释掉,则只会在EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]中的_loaders中被初始化,否则会在EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]和EnvironmentClassLoader$7806641[host:http://localhost:8787两个类加载器各自的_loaders集合中被初始化。(通过调试this.getClass().getResource(“/test”).getPath()验证)
- <compiling-loader>未注释掉,”/”(根路径)由EnvironmentClassLoader$7806641[host:http://localhost:8787]加载,注释掉后由EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]加载。
- EnvironmentClassLoader$7806641[host:http://localhost:8787]为Resin server的类加载器实例,EnvironmentClassLoader$24156236[web-app:http://localhost:8787/EhCacheTestAnnotation]为Web应用程序的类加载器实例。他们都属于java.net.URLClassLoader的实例。
- <compiling-loader>某种程度上对getResourceAsStream方法有影响。
现在<compiling-loader>如何影响getResource(“/”),以及getResourceAsStream“不”被影响全部真相大白。
注:<compiling-loader>只对获取根路径产生影响,也就是参数为”/”。比如加载”/test/Path.class”不会产生影响。
(全文完)
(转载本站文章请注明作者和出处 酷 壳 – CoolShell ,请勿用于任何商业用途)
《Resin服务器getResource揭秘》的相关评论
牛人!
我顶一下,这种钻研精神很感动我。
看国内其他知名论坛,都是就事论事,似是而非,根本自己不做研究,东抄西拉来的文章。无聊而浪费时间!
所以基本不看国内论坛或者Blog。
但是楼主的文章我最近知晓后看了不少,很受启发。谢谢!
唉,咋会贴这种文章,而且是两篇,不像酷克的风格啊. 这种分析完全可以看java源码啊.而且getResource 跟resin有啥关系啊,它只是调用了getResource,这完全是java的实现啊.
酷克发点好文吧
@fakeuser 你看到的只是技术细节,我看到的是一个程序员专研的精神。所以,我认为这种可以让大家了解他人是如何专研技术的文章就是好文。
谢谢你关心酷壳的文章质量,也欢迎你能加入一起分享。
@fakeuser
首先很谢谢你的再次关注,再一个这就是看源码发现的问题,然后去解决的。不是单单jdk的源码就能解释。
可能这个对你来说so easy,但是我很享受这个过程,因为对于现在的我,弄清楚它我很骄傲。望谅解。
很多东西看似简单,当你真正投入进去会发现别有洞天。
最后,很期待你的文章。
真心感谢。
看到这种文章总是会觉得很感动,一方面来自于感受到作者自己的心血,虽然不可能有他自己心情的十分之一,但多少能感觉到一些。另外一方面就是分享精神。
技术强人啊。。
老兄,还真是写了续篇,不得不顶啊
@煲仔饭
哈哈,见笑了,已答应了,就要做到嘛!况且上一篇问题也未解决。
有什么不合适的地方还希望能多多交流。
程序员专研的精神!
每个应用服务器实现的方式都是不一样的,这个可能只适用于resin
很早之前就见到这篇文章了,今天终于用到了。
被这种精神深深打动了,学习
Can you message me with a few tips about how you made this blog look like this, Id be thankful!