springMVC源码阅读
SpringMVC:
在典型的web环境中,Spring IOC容器是怎样被载入和起作用的了?简单的说,在web容器中,
通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。
一般要在web.xml文件中配置一个listener:ContextLoaderListener.
这个listener实现了ServletContextListener,并重写了contextInitialized和
contextDestroyed方法,当该listenter侦听到web应用启动和关闭时会调用这两个方法。
代码如下:
public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader(); this.contextLoader.initWebApplicationContext(event.getServletContext()); } public void contextDestroyed(ServletContextEvent event) { if (this.contextLoader != null) { this.contextLoader.closeWebApplicationContext(event.getServletContext()); } }
方法很简单,初始化时实例化一个contextLoader对象,并调用initWebApplicationContext方法。
销毁时调用closeWebApplicationContext方法。
看来重点在ContextLoader这个类中!
一个重要的类:ContextLoader,加载上下文的类。
类中声明一个静态块
static { try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } }
解析上述静态块:
1、此块在ContextLoader初始化时执行;
2、这句的作用是要设置defaultStrategies为org.springframework.web.context.support.XmlWebApplicationContext
3、这里涉及到读取properties文件,其实很简单:直接调用properties的load(InputStrem)方法,即可把文件中的键值对
装载到properties对象中。
spring中默认使用的日志记录器为commons-logging,所以使用spring时一定要先导入该jar.
竟然也有的代码用的log4j,乱了!
下面来看看ContextLoader的关键方法:initWebApplicationContext
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!"); }
首先在servletContext中获取属性org.springframework.web.context.WebApplicationContext.ROOT,如果存在,则抛出异常!
这个属性是做什么用的,不知道???
ApplicationContext parent = loadParentContext(servletContext);
加载父上下文,经调试,我的项目中该值为null。
接下来就是createWebApplicationContext:
protected WebApplicationContext createWebApplicationContext( ServletContext servletContext, ApplicationContext parent) throws BeansException { Class contextClass = determineContextClass(servletContext); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setParent(parent); wac.setServletContext(servletContext); wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM)); customizeContext(servletContext, wac);//用户可以自定义,默认不进行任何自定义操作 wac.refresh(); return wac; }
determineContextClass用来设置上下文类,一般就是默认的org.springframework.web.context.support.XmlWebApplicationContext,
然后设置ConfigurableWebApplicationContext(就是XmlWebApplicationContext)的父上下文、servletContext及配置路径,
此处的配置路径就是我们在web.xml中配置的路径:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param
倒数第二行代码调用了一个refresh方法,这个方法实际上是在AbstractApplicationContext这个抽象类中定义的,
XmlWebApplicationContext是该类的子孙类(这个方法找了好久,spring中的层层继承实在是太复杂了)。
具体代码如下:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { prepareRefresh();//设置开发时间startupDate,并将标志位active设置为true //ps:这里用到了多线程,抽时间研究一下 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { postProcessBeanFactory(beanFactory); invokeBeanFactoryPostProcessors(beanFactory); registerBeanPostProcessors(beanFactory); initMessageSource(); initApplicationEventMulticaster(); onRefresh(); registerListeners(); finishBeanFactoryInitialization(beanFactory); finishRefresh(); } catch (BeansException ex) { beanFactory.destroySingletons(); cancelRefresh(ex); throw ex; } } }
//refresh此处水很深啊,其实是IOC中的内容,先不去管;接着往下看
回到initWebApplicationContext方法中来:
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
将刚才创建的XmlWebApplicationContext保存在servletContext局部变量中;
currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);
Thread.currentThread().getContextClassLoader()这一句是获取当前线程的上下文ClassLoader,以前总觉得web中
很少用到线程,现在看来线程无处不在啊。
接下来就是记录日志:
if (logger.isDebugEnabled()) ......
if (logger.isInfoEnabled()) ......
这里做的比较好,spring自动判断是否开始日志的debug或info模式,如果开启了才会输出日志
以上就是initWebApplicationContext的全过程了......
接下来分析closeWebApplicationContext:
源码如下:
public void closeWebApplicationContext(ServletContext servletContext) { servletContext.log("Closing Spring root WebApplicationContext"); try { if (this.context instanceof ConfigurableWebApplicationContext) { ((ConfigurableWebApplicationContext) this.context).close(); } } finally { currentContextPerThread.remove(Thread.currentThread().getContextClassLoader()); servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (this.parentContextRef != null) { this.parentContextRef.release(); } } }
先看这一句((ConfigurableWebApplicationContext) this.context).close();
这里close方法的具体实现如下(和refresh方法一样,仍然在AbstractApplicationContext类中):
public void close() { synchronized (this.startupShutdownMonitor) {//这个锁和refresh方法中的是同一个 doClose(); if (this.shutdownHook != null) { Runtime.getRuntime().removeShutdownHook(this.shutdownHook);//貌似很深奥啊,一会在深入研究 } } }
doClose方法的具体实现:
protected void doClose() { if (isActive()) {//此时为true,初始值为fasle,执行refresh()时设置true. if (logger.isInfoEnabled()) { logger.info("Closing " + this); } try { // Publish shutdown event. publishEvent(new ContextClosedEvent(this)); } catch (Throwable ex) { logger.error("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } // Stop all Lifecycle beans, to avoid delays during individual destruction. Map lifecycleBeans = getLifecycleBeans();//获取所有单例bean for (Iterator it = new LinkedHashSet(lifecycleBeans.keySet()).iterator(); it.hasNext();) { String beanName = (String) it.next(); doStop(lifecycleBeans, beanName); } // Destroy all cached singletons in the context's BeanFactory. destroyBeans(); // Close the state of this context itself. closeBeanFactory(); onClose(); synchronized (this.activeMonitor) { this.active = false; } } }
publishEvent(new ContextClosedEvent(this));这句代码调用了方法实现方式如下:
public void publishEvent(ApplicationEvent event) { Assert.notNull(event, "Event must not be null"); if (logger.isTraceEnabled()) { logger.trace("Publishing event in context [" + getId() + "]: " + event); } getApplicationEventMulticaster().multicastEvent(event); if (this.parent != null) { this.parent.publishEvent(event); } }
未完待续...