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

************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也是可以互不影响的!

 

 

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

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

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值