Java开源笔记:Spring源代码解析

夜半待客客不至,闲敲棋子落灯花; 一个好的开源软件代码就像一卷优雅的棋谱,让我们好好享受一下吧!

2007年6月3日星期日

上面我们分析了IOC容器本身的实现,下面我们看看在典型的web环境中,Spring IOC容器是怎样起作用的。
对于一个Spring激活的J2EE web应用程序,可以通过使用Spring代码声明式的指定在web应用程序启动时载入应用程序上下文 (WebApplicationContext),Spring的ContextLoader是提供这样性能的类,我们可以使用 ContextLoaderServlet或者ContextLoaderListener的启动时载入的Servlet来实例化Spring IOC容器 - 为什么会有两个不同的类来装载它呢,这是因为它们的使用需要区别不同的Servlet容器支持的Serlvet版本。但不管是ContextLoaderSevlet还是 ContextLoaderListener都使用ContextLoader来完成实际的WebApplicationContext的初始化工作。这个ContextLoder就像是Spring Web应用程序在Web容器中的booter。当然这些Servlet的具体使用我们都要借助web容器中的部署描述符来进行相关的定义。
下面我们使用ContextLoaderListener作为载入器作一个详细的分析,首先从中得到ServletContext,然后可以读到配置好的web.xml的中的各个属性值,包括bean配置文件的 位置等,然后ContextLoder实例化WebApplicationContext完成其载入。当 ApplicationContext载入后,他被绑定到web应用程序的ServletContext上。任何需要访问该ApplicationContext的应用程序代码都可以从 WebApplicationContextUtils类通过调用静态方法来得到它:
WebApplicationContext getWebApplicationContext(ServletContext sc)
以Tomcat作为Servlet容器为例,下面是具体的步骤:
1.Tomcat启动时需要从web.xml中读取启动参数,在web.xml中我们需要对ContextLoaderListener进行配置,对于在web应用启动入口是在ContextLoaderListener中的初始化部分;从Spring MVC上看,实际上在web容器中维护了一系列的IOC容器,其中在ContextLoader中载入的IOC容器作为根上下文而存在于ServletContext中。
//这里对根上下文进行初始化。
public void contextInitialized(ServletContextEvent event) {
//这里创建需要的ContextLoader
this.contextLoader = createContextLoader();
//这里使用ContextLoader对根上下文进行载入和初始化
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
通过ContextLoader建立起根AppplicationContext并载入ApplicationContext,我们可以在ContextLoader中看到:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
throws IllegalStateException, BeansException {
//这里先看看是不是已经在ServletContext中存在WebApplicationContext,如果有说明前面已经被载入过,或者是配置文件有错误。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
//直接抛出异常
.........
}

...............
try {
// 这里载入ApplicationContext的父上下文
ApplicationContext parent = loadParentContext(servletContext);

//这里创建根WebApplicationContext作为整个应用的上下文同时把它存到ServletContext中去,注意这里使用的ServletContext的属性值是
//ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,以后都是根据这个属性值来取得根上下文的。
this.context = createWebApplicationContext(servletContext, parent);
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
..........

return this.context;
}
............
}

建立根WebApplicationContext的父上下文使用的是下面的代码,取决于在web.xml中定义的参数:locatorFactorySelector,这是一个可选参数:
protected ApplicationContext loadParentContext(ServletContext servletContext)
throws BeansException {

ApplicationContext parentContext = null;

String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);

if (locatorFactorySelector != null) {
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
........
//得到根上下文的父上下文的引用
this.parentContextRef = locator.useBeanFactory(parentContextKey);
//这里建立得到根上下文的父上下文
parentContext = (ApplicationContext) this.parentContextRef.getFactory();
}

return parentContext;
}
得到根上下文的父上下文以后,就是根上下文的创建过程:
protected WebApplicationContext createWebApplicationContext(
ServletContext servletContext, ApplicationContext parent) throws BeansException {
//这里需要确定我们载入的根WebApplication的类型,由在web.xml中配置的contextClass中配置的参数可以决定我们需要载入什么样的ApplicationContext,
//如果没有使用默认的。
Class contextClass = determineContextClass(servletContext);
.........
//这里就是WebApplicationContext的创建过程
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
//这里保持对父上下文和ServletContext的引用到根上下文中
wac.setParent(parent);
wac.setServletContext(servletContext);

//这里从web.xml中取得相关的初始化参数
String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocation != null) {
wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,
ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
//这里对WebApplicationContext进行初始化,我们又看到了熟悉的refresh调用。
wac.refresh();
return wac;
}
初始化根ApplicationContext后将其存储到SevletContext中去以后,这样就建立了一个全局的关于整个应用的上下文。这个根上下文会被以后的DispatcherServlet初始化自己的时候作为自己ApplicationContext的父上下文。这个在对DispatcherServlet做分析的时候我们可以看看到。

3.完成对ContextLoaderListener的初始化以后,Tomcat开始初始化DispatchServlet,- 还记得我们在web.xml中队载入次序进行了定义。DispatcherServlet会建立自己的ApplicationContext,同时建立这个自己的上下文的时候会从ServletContext中得到根上下文作为父上下文,然后再对自己的上下文进行初始化,并最后存到ServletContext中去供以后检索和使用。
可以从DispatchServlet的父类FrameworkServlet的代码中看到大致的初始化过程,整个ApplicationContext的创建过程和ContextLoder创建的过程相类似:

protected final void initServletBean() throws ServletException, BeansException {
.........
try {
//这里是对上下文的初始化过程。
this.webApplicationContext = initWebApplicationContext();
//在完成对上下文的初始化过程结束后,根据bean配置信息建立MVC框架的各个主要元素
initFrameworkServlet();
}
........
}
对initWebApplicationContext()调用的代码如下:
protected WebApplicationContext initWebApplicationContext() throws BeansException {
//这里调用WebApplicationContextUtils静态类来得到根上下文
WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

//创建当前DispatcherServlet的上下文,其上下文种类使用默认的在FrameworkServlet定义好的:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
WebApplicationContext wac = createWebApplicationContext(parent);
........
if (isPublishContext()) {
//把当前建立的上下文存到ServletContext中去,注意使用的属性名是和当前Servlet名相关的。
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
其中我们看到调用了WebApplicationContextUtils的静态方法得到根ApplicationContext:
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
//很简单,直接从ServletContext中通过属性名得到根上下文
Object attr = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
.......
return (WebApplicationContext) attr;
}
然后创建DispatcherServlet自己的WebApplicationContext:
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
throws BeansException {
.......
//这里使用了BeanUtils直接得到WebApplicationContext,ContextClass是前面定义好的DEFAULT_CONTEXT_CLASS =
//XmlWebApplicationContext.class;
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());

//这里配置父上下文,就是在ContextLoader中建立的根上下文
wac.setParent(parent);

//保留ServletContext的引用和相关的配置信息。
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());

//这里得到ApplicationContext配置文件的位置
if (getContextConfigLocation() != null) {
wac.setConfigLocations(
StringUtils.tokenizeToStringArray(
getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

//这里调用ApplicationContext的初始化过程,同样需要使用refresh()
wac.refresh();
return wac;
}

4.然后就是DispatchServlet中对Spring MVC的配置过程,首先对配置文件中的定义元素进行配置 - 请注意这个时候我们的WebApplicationContext已经建立起来了,也意味着DispatcherServlet有自己的定义资源,可以需要从web.xml中读取bean的配置信息,通常我们会使用单独的xml文件来配置MVC中各个要素定义,这里和web容器相关的加载过程实际上已经完成了,下面的处理和普通的Spring应用程序的编写没有什么太大的差别,我们先看看MVC的初始化过程:
protected void initFrameworkServlet() throws ServletException, BeansException {
initMultipartResolver();
initLocaleResolver();
initThemeResolver();
initHandlerMappings();
initHandlerAdapters();
initHandlerExceptionResolvers();
initRequestToViewNameTranslator();
initViewResolvers();
}
5.这样MVC的框架就建立起来了,DispatchServlet对接受到的HTTP Request进行分发处理由doService()完成,具体的MVC处理过程我们在doDispatch()中完成,其中包括使用Command模式建立执行链,显示模型数据等,这些处理我们都可以在DispatcherServlet的代码中看到:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
......
try {
doDispatch(request, response);
}
.......
}
实际的请求分发由doDispatch(request,response)来完成:
protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception {
.......
// 这是Spring定义的执行链,里面放了映射关系对应的handler和定义的相关拦截器。
HandlerExecutionChain mappedHandler = null;

......
try {
//我们熟悉的ModelAndView在这里出现了。
ModelAndView mv = null;
try {
processedRequest = checkMultipart(request);

//这里更具request中的参数和映射关系定义决定使用的handler
mappedHandler = getHandler(processedRequest, false);

......
//这里是handler的调用过程,类似于Command模式中的execute.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

.......
//这里将模型数据通过视图进行展现
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
}
........
}
这样具体的MVC模型的实现就由bean配置文件里定义好的view resolver,handler这些类来实现用户代码的功能。
总结上面的过程,我们看到在web容器中,ServletContext可以持有一系列的web上下文,而在整个web上下文中存在一个根上下文来作为其它Servlet上下文的父上下文。这个根上下文是由ContextLoader载入并进行初始化的,对于我们的web应用,DispatcherSerlvet载入并初始化自己的webApplication,这个上下文的父上下文是根上下文,并且我们也能从ServletContext中根据Servlet的名字来检索到我们需要的web上下文,但是根上下文的名字是由Spring唯一确定的。
具体的web请求处理在上下文体系建立完成以后由DispactcherServlet来完成,上面对MVC的运作做了一个大致的描述,下面我们会具体就SpringMVC的框架实现作一个详细的分析。

没有评论:

博客归档