之前的欠帳,如今補上,歡迎指正和討論。
Spring Web MVC的實現
關於MVC,這是和WEB開發相關的部分,顯然你們都是很熟悉了。從最初的JSP到struts,再到像wicket等等,真是百花齊放,百家爭鳴.在WEB UI上,這部分是作web應用架構選擇不可缺乏的一部分。而做爲MVC框架,也許SPRING MVC不能算得上是表現力最出色的UI框架,但無疑,它的實現也是很是的優秀,同時,咱們能夠從它的實現上,看到一個很是清晰的MVC實現的過程,從這點上看,真是很是的過癮啊!
在瞭解IOC容器的基本實現的基礎上,下面咱們來看看,在典型的Web環境中,Spring IOC容器是如何在Web環境中被載入並起做用的。咱們能夠看到,對於MVC這部分,主要創建在IOC的基礎上,AOP的特性應用得並很少。Spring並非天生就能在Web容器中起做用的,一樣也須要一個啓動過程,把本身的IOC容器導入,並在Web容器中創建起來。
與對IoC容器的初始化的分析同樣,咱們一樣看到了loadBeanDefinition對BeanDefinition的載入。在Web環境中,對定位BeanDefinition的Resource有特別的要求,對這個要求的處理體如今getDefaultConfigLocations方法的處理中。能夠看到,在這裏,使用了默認的BeanDefinition的配置路徑,這個路徑在XmlWebApplicationContext中,已經做爲一個常量定義好了,這個常量就是/WEB-INF/applicationContext.xml。這裏的loadBeanDefinition實現以下所示:
web
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { app
/** Default config location for the root context */ 框架
//這裏是設置缺省BeanDefinition的地方,在/WEB-INF/applicationContext.xml文件裏,若是不特殊指定其餘文件,IoC容器會從這裏讀取BeanDefinition來初始化IoC容器 ide
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"; post
/** Default prefix for building a config location for a namespace */ ui
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/"; this
/** Default suffix for building a config location for a namespace */ url
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml"; spa
//咱們又看到了熟悉的loadBeanDefinition,就像咱們前面對IOC容器的分析同樣,這個加載過程在容器refresh()時啓動。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// 對於XmlWebApplicationContext,固然是使用XmlBeanDefinitionReader來對BeanDefinition信息進行解析
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
// 這裏設置ResourceLoader,由於XmlWebApplicationContext是DefaultResource的子類,因此這裏一樣會使用DefaultResourceLoader來定位BeanDefinition
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
//這裏使用定義好的XmlBeanDefinitionReader來載入BeanDefinition
loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}
//若是有多個BeanDefinition的文件定義,須要逐個載入,都是經過reader來完成的,這個初始化過程是由refreshBeanFactory方法來完成的,這裏只是負責載入BeanDefinition
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
進入DispatcherServlet和MVC實現
完成了在Web環境中,IoC容器的創建之後,也就是在完成對ContextLoaderListener的初始化之後,Web容器開始初始化DispatcherServlet,接着,會執行DispatcherServlet持有的IoC容器的初始化過程,在這個初始化過程當中,一個新的上下文被創建起來,這個DispatcherServlet持有的上下文,被設置爲根上下文的子上下文。能夠大體認爲,根上下文是和Web應用相對應的一個上下文,而DispatcherServlet持有的上下文是和Servlet對應的一個上下文,在一個Web應用中,每每能夠容納多個Servlet存在;與此相對應,對於應用在Web容器中的上下體系,也是很相似的,一個根上下文能夠做爲許多Servlet上下文的雙親上下文。在DispatcherServlet,咱們能夠看到對MVC的初始化,是在DispatcherServlet的initStrategies完成的。
在這個初始化完成之後,會在上下文中創建器一個執行器於url的對應關係,這個對應關係可讓在url請求到來的時候,MVC能夠檢索到相應的控制器來進行處理,如如下代碼所示:
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
//這裏從request中獲得請求的url路徑
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//這裏使用獲得的url路徑對Handler進行匹配,獲得對應的Handler,若是沒有對應的Hanlder,返回null,這樣默認的Handler會被使用
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to handler '" + handler + "'");
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}
// lookupHandler是根據url路徑,啓動在handlerMap中對handler的檢索,並最終返回handler對象
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, null);
}
// Pattern match?
String bestPathMatch = null;
for (String registeredPath : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPath, urlPath) &&
(bestPathMatch == null || bestPathMatch.length() < registeredPath.length())) {
bestPathMatch = registeredPath;
}
}
if (bestPathMatch != null) {
handler = this.handlerMap.get(bestPathMatch);
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPathMatch, urlPath);
Map<String, String> uriTemplateVariables =
getPathMatcher().extractUriTemplateVariables(bestPathMatch, urlPath);
return buildPathExposingHandler(handler, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
最後,咱們能夠結合在DispatcherServlet中,對請求的分發處理來了解一個url請求到來時,MVC的實現和協同處理過程,如如下代碼所示:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
//這裏爲視圖準備好一個ModelAndView,這個ModelAndView持有handler處理請求的結果
try {
ModelAndView mv = null;
boolean errorView = false;
try {
processedRequest = checkMultipart(request);
// Determine handler for the current request.
// 根據請求獲得對應的handler,hander的註冊以及getHandler的實如今前面已經分析過
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Apply preHandle methods of registered interceptors.
// 調用hander的攔截器,從HandlerExecutionChain中取出Interceptor進行前處理
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
// Actually invoke the handler.
// 這裏是實際調用handler的地方,在執行handler以前,用HandlerAdapter先檢查一下handler的合法性:是否是按Spring的要求編寫的handler
// handler處理的結果封裝到ModelAndView對象,爲視圖提供展示數據
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//這裏經過調用HandleAdapter的handle方法,實際上觸發對Controller的handleRequest方法的調用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
// Apply postHandle methods of registered interceptors.
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
}
catch (ModelAndViewDefiningException ex) {
logger.debug("ModelAndViewDefiningException encountered", ex);
mv = ex.getModelAndView();
}
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
// Did the handler return a view to render?
// 這裏使用視圖對ModelAndView數據的展示
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
// Trigger after-completion for successful outcome.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
catch (Exception ex) {
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
finally {
// Clean up any resources used by a multipart request.
if (processedRequest != request) {
cleanupMultipart(processedRequest);
}
}
}
經過MVC框架,其實是DispatcherServlet的協調運做,獲得了ModelAndView對象做爲數據處理結果,最後,DispatcherServlet把得到的模型數據交給特定的視圖對象,從而完成這些數據的視圖呈現工做,這個視圖呈現由視圖對象的render方法來完成,毫無疑問,對應於不一樣的視圖對象,render方法會完成不一樣的視圖呈現處理,從而爲用戶提供豐富的Web UI表現。關於這些不一樣的視圖展示,還能夠看到不少頗有參考意義的開源軟件的靈活使用,限於篇幅,這裏就不詳細說了。
對Spring MVC框架的我的理解
對Spring做爲應用平臺的Web應用開發而言,Spring爲它們提供了Spring MVC框架,做爲一個像struts這樣的Web框架的替代;固然,做爲應用平臺,Spring並不會強制應用對Web框架的選擇。但對Web應用開發而言,選擇直接使用Spring MVC,能夠給應用開發帶來許多便利。由於Spring MVC, 毫無疑問,很好的提供了與Web環境中的IoC容器的集成。同時,和其餘Web應用同樣,使用Spring MVC, 應用只須要專一於處理邏輯和視圖呈現的開發(固然這些開發須要符合Spring MVC的開發習慣),在視圖呈現部分,Spring MVC同時也集成了許多現有的Web UI實現,好比像Excel, PDF這些文檔視圖的生成,由於,集成第三方解決方案,實在能夠說是Spring的拿手好戲,從這種一致性的開發模式上看,它在很大程度上下降了Web應用開發的門檻。