精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-11-12
最后修改:2010-11-15
************IMPORTANT*************** 下面这篇文章有欠考虑(但有些思路还是有点用处的,故不忍删除)。故此特别声明,以免误导大家。详细原因请看回复。
现在公司里面用Spring,都是把spring跟每个应用集成到一块。然后几个应用部署在同一台tomcat下面。 这样容易产生的一个问题是每个应用到加载一次Spring的相关jar。如果应用比较多,难免会影响到 jvm的PermanentGenerationSpace. 于是读了下Spring的源码,分析了下把Spring jar包放在tomcat公共目录下面的可行性。 结果发现,确实可以。下面以tomcat6为例,分析一下。 tomcat6 的classloader的结构大致如下: Bootstrap | System | Common / \ Webapp1 Webapp2 ...
其中Common是加载tomcat的jar文件。WebappX对应的是每个web的一个classloader. 这些Webappx classloader不同于一般classloader的地方在于放弃了Parent Delegation 模型,加载class的时候,首先试图从当前classloader对应目录下面加载,如果本classloader加载不到,再把加载请求委托给上级classloader. 所以如果每个web application都有自己的一套Spring jar包。那么肯定是每个都分别加载一次的。下面要分析的就是如果把jar包放在tomcat6的common目录下面,而不是web applicaiton的lib下面,到底能不能加载?
从Spring的源码入手,以下是web application加载webapplicationContext的代码:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!"); } Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started"); } long startTime = System.currentTimeMillis(); try { // Determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(servletContext); // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. this.context = createWebApplicationContext(servletContext, parent); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } return this.context; } catch (RuntimeException ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } catch (Error err) { logger.error("Context initialization failed", err); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); throw err; } } 可以看到每个web application的局部变量servletContext都存储了对应的webapplicationcontext.这就保证了 每当通过web的local instance servletContext来取webapplicationcontext时取到的是自己的context. 注意其中有这么一段:
ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } 如果Spring的jar包是放在tomcat的common目录下面而没有放在web application下面的,那么 ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 这个返回的是WebappX classloader; 而ContextLoader.class.getClassLoader() 返回的是tomcat的 common classloader.
两个classloader是不一样的。于是执行如下代码:
currentContextPerThread.put(ccl, this.context); 注意当前类的classloader也同样是Common classloader. 于是在里面维护了一个 map,这个map的key是common classloader的下级classloader--也就是WebappX classloader;对应的 value是每个web application根据配置文件生成的webapplicationcontext.
这样做的目的在于当通过ConetxtLoader的静态方法获取context的时候,能保证获取的是当前web application的context.实际上就是对于tomcat下面的任何一个线程,我们都能很方便的找出这个线程对应的webapplicationContext.于是在一些不能方便获取servletContext的场合,我们可以通过当前线程获取webapplicationContext. 代码如下:
public static WebApplicationContext getCurrentWebApplicationContext() { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl != null) { WebApplicationContext ccpt = currentContextPerThread.get(ccl); if (ccpt != null) { return ccpt; } } return currentContext; }
当然也可以通过每个web application的servletContext获取webapplicationContext(还记得上面提到的设置的servletContext局部变量吗?). 因为是局部变量,所以也可以顺利的取到对应的webapplicationContext. (同时,这种方式也有另一个作用。那就是某个web application卸载的时候,可以很方便地在ContextLoader里面获取web对应的context并且移除,从而避免了内存溢出。)
于是,截至到webapplicationContext的生成和获取,是OK 的。 下面需要注意的就是因为webapplicationContext以及他的子类的classloader都是common classloader. 那么必须要保证webapplicationContext的实现类里面没有跟webapplication相关的class变量。否则 多个webapplicaton将能够互相影响。看了一下XmlWebApplicationContext的类层次关系(这个类是在web系统中默认的webapplicationContext的实现类),发现没有static变量。OK.
由此可以断定,哪怕是common classloader加载,各个web application的context也是可以互不影响的!
以上仅限于理论。我会找时间做一个测试,看看这种猜测是否准确。 如果有什么地方我忽略了,也欢迎大家拍砖~
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-11-12
WebapplicationContext是应该可以用,但是log4j动作是否正常呢?
|
|
返回顶楼 | |
发表时间:2010-11-15
恩。我试了一下。确实有不少问题。我试着弄了一下发现问题多多啊。
1. 首先就是bean加载的时候。bean class的加载流程?因为如果spring的classloader是commonclassloader,这个classloader采用双亲代理的模式,是不可能加载得到我们自己定义的bean class的。如果没有做一些classloader的特别处理(比如通过thread 的contextclassloader从而在一个上级的classloader里面能够引用到下级的classloader),那么应用肯定会报错。 2. spring引用的一些第三方包。比如:log4j,struts,ibatis,ehcache.spring提供了对这些第三方框架的支持。处于跟上面同样的原因,假如要多个应用共享spring包,那么这些包不可避免 的也要放在tomcat公共目录下面。这些第三方 包本身对这种跨应用的共享是否支持? 由此看来,我之前写的那些东西确实是欠考虑的。不好意思,误导大家了~我会在正文里面更正。 |
|
返回顶楼 | |
浏览 2535 次