论坛首页 Java企业应用论坛

关于Spring放在web容器公共目录共享lib下面的分析

浏览 2535 次
精华帖 (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也是可以互不影响的!

 

 

以上仅限于理论。我会找时间做一个测试,看看这种猜测是否准确。

如果有什么地方我忽略了,也欢迎大家拍砖~

 

 

 

 

 

 

   发表时间:2010-11-12  
WebapplicationContext是应该可以用,但是log4j动作是否正常呢?
0 请登录后投票
   发表时间: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公共目录下面。这些第三方 包本身对这种跨应用的共享是否支持?

由此看来,我之前写的那些东西确实是欠考虑的。不好意思,误导大家了~我会在正文里面更正。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics