Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
源:https://www.cnblogs.com/youzhibing/p/10695012.html
作者:youzhibing2904
遺留問題
在關(guān)于利用maven搭建ssm的博客,我們一起來探討下問的最多的問題中,我遺留了一個(gè)問題:Spring mvc是何時(shí)、何地、如何將Model中的屬性綁定到哪個(gè)作用域,這里的作用域指的是Servlet的四大作用域;不了解問題背景的可以回過頭去看看我的上篇博文。
明確的解答我會放到最后,在解答問題之前,我先和大家一起來捋一捋Spring mvc的工作原理。廢話不多說,開始我們神秘的探險(xiǎn)之旅!
在講工作原理之前,我們先看一個(gè)簡單的spring mvc(ssm)示例,以及實(shí)現(xiàn)的效果
工程代碼地址:ssm-web
工程結(jié)構(gòu)與效果如上所示,我們不做過多的探究,我們打起精神往下看本篇的重點(diǎn)
準(zhǔn)備 - 資源的加載與初始化
1、DispatcherServlet 靜態(tài)初始化
DispatcherServlet中有如下靜態(tài)塊
static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage()); } }
這里會將DispatcherServlet.properties中的內(nèi)容讀取到DispatcherServlet的屬性:private static final Properties defaultStrategies中,DispatcherServlet.properties內(nèi)容如下
# Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
指定了DispatcherServlet策略接口的默認(rèn)實(shí)現(xiàn),后續(xù)DispatcherServlet初始化策略的時(shí)候會用到
2、interceptor定義的加載
spring啟動(dòng)過程中會調(diào)用InterceptorsBeanDefinitionParser的parse方法來解析出我們自定義的interceptor定義,封裝成MappedInterceptor類型的bean定義,并放到spring容器中;我們可以簡單的認(rèn)為spring容器中已經(jīng)存在了我們自定義的interceptor的bean定義
3、DispatcherServlet初始化策略:initStrategies
DispatcherServlet的繼承圖如下
DispatcherServlet是一個(gè)Servlet,tomcat啟動(dòng)過程中會調(diào)用其init方法,一串的調(diào)用后,會調(diào)用DispatcherServlet的initStrategies方法
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
實(shí)例化步驟1中的默認(rèn)實(shí)現(xiàn),并填充到DispatcherServlet各個(gè)屬性值中
4、DefaultAnnotationHandlerMapping的攔截器初始化
DispatcherServlet.properties種指定了兩個(gè)默認(rèn)的HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping,這兩者的類繼承圖如下(我們暫時(shí)只關(guān)注DefaultAnnotationHandlerMapping)
DefaultAnnotationHandlerMapping間接實(shí)現(xiàn)了ApplicationContextAware,那么在DefaultAnnotationHandlerMapping實(shí)例初始化過程中,會調(diào)用setApplicationContext(ApplicationContext applicationContext)方法,一串調(diào)用后,會來到AbstractUrlHandlerMapping的initApplicationContext()
@Override protected void initApplicationContext() throws BeansException { extendInterceptors(this.interceptors); detectMappedInterceptors(this.mappedInterceptors); initInterceptors(); }
初始化了DefaultAnnotationHandlerMapping的攔截器:interceptor
我們來看下具體的初始化過程,看看上面的順序是否只是我個(gè)人的臆想?
可以看到,初始化順序就是我們上面說的,不是我個(gè)人的意淫;此時(shí)的DefaultAnnotationHandlerMapping中有我們自定義的MyInterceptor。初始化過程我們需要關(guān)注的就是上述這些,下面我們一起看看具體請求的過程
請求的處理
請求從servlet的service開始,一路到DispatcherServlet的doDispatch,如下圖
/** * Process the actual dispatching to the handler. 將請求分發(fā)到具體的handler,也就是我們的controller * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters * to find the first that supports the handler class. * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers * themselves to decide which methods are acceptable. * @param request current HTTP request * @param response current HTTP response * @throws Exception in case of any kind of processing failure */ protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; // Determine handler for the current request. 決定哪個(gè)handler來處理當(dāng)前的請求 // mappedHandler是由handler和interceptor集合組成的一個(gè)執(zhí)行鏈,有點(diǎn)類似FilterChain mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. 決定哪個(gè)adapter來處理當(dāng)前的請求 // handlerMapping是找出適配的handler,而真正回調(diào)handler的是adapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // handler的前置處理,也就是調(diào)用適配當(dāng)前url的interceptor的preHandler方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler. 真正調(diào)用handler mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv); // handler的后置處理,也就是調(diào)用適配當(dāng)前url的interceptor的postHandler方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } // 處理handler返回的結(jié)果,會調(diào)用適配當(dāng)前url的interceptor的afterCompletion方法 // 這里會將響應(yīng)結(jié)果返回給請求者 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
handlerMapping具體如何找到匹配當(dāng)前url的handler(一般而言就是我們的controller)、handlerAdapter具體如何回調(diào)真正的handler,有興趣的可以自行去跟下,我就不跟了。我們具體看下processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 這個(gè)與我們最初的疑問有關(guān)
processDispatchResult
可以看到model中的persons會被設(shè)置到request的attributes中,然后轉(zhuǎn)發(fā)請求到show_person.jsp,轉(zhuǎn)發(fā)過程中request作用域的變量仍然有效,所以show_person.jsp中的jstl標(biāo)簽和el表達(dá)式能夠取到persons變量,最后將show_person.jsp中的內(nèi)容填充好之后的靜態(tài)內(nèi)容返回給請求者;至此就完成了一次請求的響應(yīng)
回到我們開篇的疑問:Spring mvc是何時(shí)、何地、如何將Model中的屬性綁定到哪個(gè)作用域?想必大家已經(jīng)知道答案了
Controller中的model、ModelMap的注入由spring mvc完成,這個(gè)不是請求傳入的參數(shù),用于綁定變量到Servlet作用域;默認(rèn)情況下,在DispatcherServlet調(diào)用了真正的handler之后,將結(jié)果返回給請求者的過程中,將model、modelMap中的變量設(shè)置到了request的attributes中,轉(zhuǎn)發(fā)的過程中,request中的變量仍然有效,所以show_person.jsp中能取到persons這個(gè)變量,自此疑問得到解答
1、Spring MVC工作原理圖
圖是用的別人的,具體是誰的我也不記得了(捂臉)
2、DefaultAnnotationHandlerMapping在spring3.2中被廢棄,替換成了RequestMappingHandlerMapping
對于 Web 應(yīng)用程序而言,我們從瀏覽器發(fā)起一個(gè)請求,請求經(jīng)過一系列的分發(fā)和處理,最終會進(jìn)入到我們指定的方法之中,這一系列的的具體流程到底是怎么樣的呢?
記得在初入職場的時(shí)候,面試前經(jīng)常會背一背 Spring MVC 流程,印象最深的就是一個(gè)請求最先會經(jīng)過 DispatcherServlet 進(jìn)行分發(fā)處理,DispatcherServlet 就是我們 Spring MVC 的入口類,下面就是一個(gè)請求的大致流轉(zhuǎn)流程(圖片參考自 Spring In Action):
上面就是一個(gè)傳統(tǒng)的完整的 Spring MVC 流程,為什么要說這是傳統(tǒng)的流程呢?因?yàn)檫@個(gè)流程是用于前后端沒有分離的時(shí)候,后臺直接返回頁面給瀏覽器進(jìn)行渲染,而現(xiàn)在大部分應(yīng)用都是前后端分離,后臺直接生成一個(gè) Json 字符串就直接返回前端,不需要經(jīng)過視圖解析器進(jìn)行處理,也就是說前后端分離之后,流程就簡化成了 1-2-3-4-7(其中第四步返回的一般是 Json 格式數(shù)據(jù))。
Spring MVC主要可以分為兩大過程,一是初始化,二就是處理請求。初始化的過程主要就是將我們定義好的 RequestMapping 映射路徑和 Controller 中的方法進(jìn)行一一映射存儲,這樣當(dāng)收到請求之后就可以處理請求調(diào)用對應(yīng)的方法,從而響應(yīng)請求。
初始化過程的入口方法是 DispatchServlet 的 init() 方法,而實(shí)際上 DispatchServlet 中并沒有這個(gè)方法,所以我們就繼續(xù)尋找父類,會發(fā)現(xiàn) init 方法在其父類(FrameworkServlet)的父類 HttpServletBean 中。
在這個(gè)方法中,首先會去家在一些 Servlet 相關(guān)配置(web.xml),然后會調(diào)用 initServletBean() 方法,這個(gè)方法是一個(gè)空的模板方法,業(yè)務(wù)邏輯由子類 FrameworkServlet 來實(shí)現(xiàn)。
這個(gè)方法本身沒有什么業(yè)務(wù)邏輯,主要是初始化 WebApplicationContext 對象,WebApplicationContext 繼承自 ApplicationContext,主要是用來處理 web 應(yīng)用的上下文。
initWebApplicationContext() 方法主要就是為了找到一個(gè)上下文,找不到就會創(chuàng)建一個(gè)上下文,創(chuàng)建之后,最終會調(diào)用方法 configureAndRefreshWebApplicationContext(cwac) 方法,而這個(gè)方法最終在設(shè)置一些基本容器標(biāo)識信息之后會去調(diào)用 refresh()方法,也就是初始化 ioc 容器。
當(dāng)調(diào)用 refresh() 方法初始化 ioc 容器之后,最終會調(diào)用方法 onRefresh(),這個(gè)方法也是一個(gè)模板鉤子方法,由子類實(shí)現(xiàn),也就是回到了我們 Spring MVC 的入口類 DispatcherServlet。
onRefresh() 方法就是 Spring MVC 初始化的最后一個(gè)步驟,在這個(gè)步驟當(dāng)中會初始化 Spring MVC 流程中可能需要使用到的九大組件。
這個(gè)組件比較熟悉,主要就是用來處理文件上傳請求,通過將普通的 Request 對象包裝成 MultipartHttpServletRequest 對象來進(jìn)行處理。
LocaleResolver 用于初始化本地語言環(huán)境,其從 Request 對象中解析出當(dāng)前所處的語言環(huán)境,如中國大陸則會解析出 zh-CN 等等,模板解析以及國際化的時(shí)候都會用到本地語言環(huán)境。
這個(gè)主要是用戶主題解析,在 Spring MVC 中,一套主題對應(yīng)一個(gè) .properties 文件,可以存放和當(dāng)前主題相關(guān)的所有資源,如圖片,css樣式等。
用于查找處理器(Handler),比如我們 Controller 中的方法,這個(gè)其實(shí)最主要就是用來存儲 url 和 調(diào)用方法的映射關(guān)系,存儲好映射關(guān)系之后,后續(xù)有請求進(jìn)來,就可以知道調(diào)用哪個(gè) Controller 中的哪個(gè)方法,以及方法的參數(shù)是哪些。
這是一個(gè)適配器,因?yàn)?Spring MVC 中支持很多種 Handler,但是最終將請求交給 Servlet 時(shí),只能是 doService(req,resp) 形式,所以 HandlerAdapter 就是用來適配轉(zhuǎn)換格式的。
這個(gè)組件主要是用來處理異常,不過看名字也很明顯,這個(gè)只會對處理 Handler 時(shí)產(chǎn)生的異常進(jìn)行處理,然后會根據(jù)異常設(shè)置對應(yīng)的 ModelAndView,然后交給 Render 渲染成頁面。
這個(gè)主鍵主要是從 Request 中獲取到視圖名稱。
這個(gè)組件會依賴于 RequestToViewNameTranslator 組件獲取到的視圖名稱,因?yàn)橐晥D名稱是字符串格式,所以這里會將字符串格式的視圖名稱轉(zhuǎn)換成為 View 類型視圖,最終經(jīng)過一系列解析和變量替換等操作返回一個(gè)頁面到前端。
這個(gè)主鍵主要是用來管理 FlashMap,那么 FlashMap 又有什么用呢?要明白這個(gè)那就不得不提到重定向了,有時(shí)候我們提交一個(gè)請求的時(shí)候會需要重定向,那么假如參數(shù)過多或者說我們不想把參數(shù)拼接到 url 上(比如敏感數(shù)據(jù)之類的),這時(shí)候怎么辦呢?因?yàn)閰?shù)不拼接在 url 上重定向是無法攜帶參數(shù)的。
FlashMap 就是為了解決這個(gè)問題,我們可以在請求發(fā)生重定向之前,將參數(shù)寫入 request 的屬性 OUTPUT_FLASH_MAP_ATTRIBUTE 中,這樣在重定向之后的 handler 中,Spring 會自動(dòng)將其設(shè)置到 Model 中,這樣就可以從 Model 中取到我們傳遞的參數(shù)了。
在九大組件初始化完成之后,Spring MVC 的初始化就完成了,接下來就是接收并處理請求了,那么處理請求的入口在哪里呢?處理請求的入口方法就是 DispatcherServlet 中的 doService 方法,而 doService 方法又會調(diào)用 doDispatch 方法。
這個(gè)方法最關(guān)鍵的就是調(diào)用了 getHandler 方法,這個(gè)方法就是會獲取到前面九大組件中的 HandlerMapping,然后進(jìn)行反射調(diào)用對應(yīng)的方法完成請求,完成請求之后后續(xù)還會經(jīng)過視圖轉(zhuǎn)換之類的一些操作,最終返回 ModelAndView,不過現(xiàn)在都是前后端分離,基本也不需要用到視圖模型,在這里我們就不分析后續(xù)過程,主要就是分析 HandlerMapping 的初始化和查詢過程。
這個(gè)方法里面會遍歷 handllerMappings,這個(gè) handllerMappings 是一個(gè) List 集合,因?yàn)?HandlerMapping 有多重實(shí)現(xiàn),也就是 HandlerMapping 不止一個(gè)實(shí)現(xiàn),其最常用的兩個(gè)實(shí)現(xiàn)為 RequestMappingHandlerMapping 和 BeanNameUrlHandlerMapping。
AbstractHandlerMapping 是一個(gè)抽象類,其 getHandlerInternal 這個(gè)方法也是一個(gè)模板方法:
getHandlerInternal 方法最終其會調(diào)用子類實(shí)現(xiàn),而這里的子類實(shí)現(xiàn)會有多個(gè),其中最主要的就是 AbstractHandlerMethodMapping 和 AbstractUrlHandlerMapping 兩個(gè)抽象類,那么最終到底會調(diào)用哪個(gè)實(shí)現(xiàn)類呢?
這時(shí)候如果拿捏不準(zhǔn)我們就可以看一下類圖,上面我們提到,HandlerMapper 有兩個(gè)非常主要的實(shí)現(xiàn)類:RequestMappingHandlerMapping 和 BeanNameUrlHandlerMapping。那么我們就分別來看一下這兩個(gè)類的類圖關(guān)系:
可以看到,這兩個(gè)實(shí)現(xiàn)類的抽象父類正好對應(yīng)了 AbstractHandlerMapping 的兩個(gè)子類,所以這時(shí)候具體看哪個(gè)方法,那就看我們想看哪種類型了。
其實(shí)除了這兩種 HandlerMapping 之外,Spring 中還有其他一些 HandllerMapping,如 SimpleUrlHandlerMapping 等。
提到的這幾種 HandlerMapping,對我們來說最常用,最熟悉的那肯定就是 RequestMappingHandlerMapping ,在這里我們就以這個(gè)為例來進(jìn)行分析,所以我們應(yīng)該
這個(gè)方法本身也沒有什么邏輯,其主要的核心查找 Handler 邏輯在 lookupHandlerMethod 方法中,這個(gè)方法主要是為了獲取一個(gè) HandlerMethod 對象,前面的方法都是 Object,而到這里變成了 HandlerMethod 類型,這是因?yàn)?Handler 有各種類型,目前我們已經(jīng)基本跟到了具體類型之下,所以類型就變成了具體類型,而如果我們看的的另一條分支線,那么返回的就會是其他對象,正是因?yàn)橹С侄喾N不同類型的 HandlerMapping 對象,所以最終為了統(tǒng)一執(zhí)行,才會需要在獲得 Hanlder 之后,DispatcherServlet 中會再次通過調(diào)用 getHandlerAdapter 方法來進(jìn)一步封裝成 HandlerAdapter 對象,才能進(jìn)行方法的調(diào)用
這個(gè)方法主要會從 mappingRegistry 中獲取命中的方法,獲取之后還會經(jīng)過一系列的判斷比較判斷比較,因?yàn)橛行?url 會對應(yīng)多個(gè)方法,而方法的請求類型不同,比如一個(gè) GET 方法,一個(gè) POST 方法,或者其他一些屬性不相同等等,都會導(dǎo)致最終命中到不同的方法,這些邏輯主要都是在 addMatchingMappings 方法去進(jìn)一步實(shí)現(xiàn),并最終將命中的結(jié)果加入到 matches 集合內(nèi)。
在這個(gè)方法中,有一個(gè)對象非常關(guān)鍵,那就是 mappingRegistry,因?yàn)樽罱K我們根據(jù) url 到這里獲取到對應(yīng)的 HandlerMtthod,所以這個(gè)對象很關(guān)鍵:
看這個(gè)對象其實(shí)很明顯可以看出來,這個(gè)對象其實(shí)只是維護(hù)了一些 Map 對象,所以我們可以很容易猜測到,一定在某一個(gè)地方,將 url 和 HandlerMapping 或者 HandlerMethod 的映射關(guān)系存進(jìn)來了,這時(shí)候其實(shí)我們可以根據(jù) getMappingsByUrl 方法來進(jìn)行反推,看看 urlLookup 這個(gè) Map 是什么時(shí)候被存入的,結(jié)合上面的類圖關(guān)系,一路反推,很容易就可以找到這個(gè) Map 中的映射關(guān)系是 AbstractHandlerMethodMapping 對象的 afterPropertiesSet 方法實(shí)現(xiàn)的(AbstractHandlerMethodMapping 實(shí)現(xiàn)了 InitializingBean 接口),也就是當(dāng)這個(gè)對象初始化完成之后,我們的 url 和 Handler 映射關(guān)系已經(jīng)存入了 MappingRegistry 對象中的集合 Map 中。
afterPropertiesSet 方法中并沒有任何邏輯,而是直接調(diào)用了 initHandlerMethods。
initHandlerMethods 方法中,首先還是會從 Spring 的上下文中獲取所有的 Bean,然后會進(jìn)一步從帶有 RequestMapping 注解和 Controller 注解中的 Bean 去解析并獲得 HandlerMethod。
這個(gè)方法中,其實(shí)就是通過反射獲取到 Controller 中的所有方法,然后調(diào)用 registerHandlerMethod 方法將相關(guān)信息注冊到 MappingRegistry 對象中的各種 Map 集合之內(nèi):
registerHandlerMethod 方法中會直接調(diào)用 AbstractHandlerMethodMapping 對象持有的 mappingRegistry 對象中的 regidter方法,這里會對 Controller 中方法上的一些元信息進(jìn)行各種解析,比如參數(shù),路徑,請求方式等等,然后會將各種信息注冊到對應(yīng)的 Map 集合中,最終完成了整個(gè)初始化。
本文重點(diǎn)以 RequestMappingHandlerMapping 為例子分析了在 Spring 當(dāng)中如何初始化 HandlerMethod,并最終在調(diào)用的時(shí)候又是如何根據(jù) url 獲取到對應(yīng)的方法并進(jìn)行執(zhí)行最終完成整個(gè)流程。
模型-視圖-控制器(MVC 是一個(gè)眾所周知的以設(shè)計(jì)界面應(yīng)用程序?yàn)榛A(chǔ)的設(shè)計(jì)模式。它主要通過分離模型、視圖及控制器在應(yīng)用程序中的角色將業(yè)務(wù)邏輯從界面中解耦。通常,模型負(fù)責(zé)封裝應(yīng)用程序數(shù)據(jù)在視圖層展示。視圖僅僅只是展示這些數(shù)據(jù),不包含任何業(yè)務(wù)邏輯。控制器負(fù)責(zé)接收來自用戶的請求,并調(diào)用后臺服務(wù)(manager或者dao)來處理業(yè)務(wù)邏輯。處理后,后臺業(yè)務(wù)層可能會返回了一些數(shù)據(jù)在視圖層展示。控制器收集這些數(shù)據(jù)及準(zhǔn)備模型在視圖層展示。MVC模式的核心思想是將業(yè)務(wù)邏輯從界面中分離出來,允許它們單獨(dú)改變而不會相互影響。
1.Spring Web MVC是一種基于Java的實(shí)現(xiàn)了Web MVC設(shè)計(jì)模式的請求驅(qū)動(dòng)類型的輕量級Web框架
2.使用了MVC架構(gòu)模式的思想,將web層進(jìn)行職責(zé)解耦
3.基于請求驅(qū)動(dòng)指的就是使用請求-響應(yīng)模型
4.框架的目的就是幫助我們簡化開發(fā),
Spring Web MVC也是要簡化我們?nèi)粘eb開發(fā)的。
1.性能比struts2好
2.簡單、便捷,易學(xué)
3.和spring無縫銜接【IOC,AOP】
4.使用約定優(yōu)于配置
5.支持Restful
6.異常處理,國際化,數(shù)據(jù)驗(yàn)證,類型轉(zhuǎn)換等
7.使用的人多,使用的公司多
普通web項(xiàng)目
在src目錄下創(chuàng)建一個(gè) spring-mvc.xml文件,名稱可以自定義。內(nèi)容就是spring的schema內(nèi)容
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
1234567
在spring-mvc.xml中添加
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 處理器映射器 將bean的name作為url進(jìn)行查找 ,
需要在配置Handler時(shí)指定beanname(就是url) 所有的映射器都實(shí)現(xiàn)
HandlerMapping接口。
-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
<!-- 配置 Controller適配器 -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
</beans>
123456789101112131415
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>test</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!-- 配置前端控制器 -->
<!-- contextConfigLocation配置springmvc加載的配置文件(配置處理器映射器、適配器等等)
如果不配置contextConfigLocation,
默認(rèn)加載的是/WEB-INF/servlet名稱-serlvet.xml(springmvc-servlet.xml)
-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
12345678910111213141516171819202122232425262728293031
/**
* 自定義控制器
* 必須實(shí)現(xiàn)Controller接口
* @author dpb【波波烤鴨】
*
*/
public class UserController implements Controller{
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("本方法被調(diào)用了...");
ModelAndView view = new ModelAndView();
view.setViewName("/index.jsp");
return view;
}
}
12345678910111213141516
通過上一個(gè)普通實(shí)現(xiàn)的方式大家會發(fā)現(xiàn)其實(shí)現(xiàn)步驟比較繁瑣,而且自定義controller也只有一個(gè)默認(rèn)被調(diào)用的方法。不是很方便,這時(shí)我們可以使用SpringMVC基于注解的使用方式來實(shí)現(xiàn),步驟如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 開啟注解 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 開啟掃描 -->
<context:component-scan base-package="com.dpb.controller"></context:component-scan>
</beans>
12345678910111213
/**
* 自定義controller
* @author dpb【波波烤鴨】
*
*/
@Controller // 交給Spring容器管理
@RequestMapping("/user") // 設(shè)置請求的路徑
public class UserController {
/**
* 查詢方法
* 請求地址是:
* http://localhost:8080/SpringMVC-03-hellowordAnnation/user/query
* @return
*/
@RequestMapping("/query")
public ModelAndView query(){
System.out.println("波波烤鴨:query");
ModelAndView mv = new ModelAndView();
mv.setViewName("/index.jsp");
return mv;
}
/**
* 添加方法
* 請求地址是:
* http://localhost:8080/SpringMVC-03-hellowordAnnation/user/add
* @return
*/
@RequestMapping("/add")
public ModelAndView add(){
System.out.println("波波烤鴨:add");
ModelAndView mv = new ModelAndView();
mv.setViewName("/index.jsp");
return mv;
}
}
12345678910111213141516171819202122232425262728293031323334353637
1.用戶向服務(wù)器發(fā)送請求,請求被Spring 前端控制Servelt DispatcherServlet捕獲;
2.DispatcherServlet對請求URL進(jìn)行解析,得到請求資源標(biāo)識符(URI)。然后根據(jù)該URI,調(diào)用HandlerMapping獲得該Handler配置的所有相關(guān)的對象(包括Handler對象以及Handler對象對應(yīng)的攔截器),最后以HandlerExecutionChain對象的形式返回;
3.DispatcherServlet 根據(jù)獲得的Handler,選擇一個(gè)合適的HandlerAdapter。(附注:如果成功獲得HandlerAdapter后,此時(shí)將開始執(zhí)行攔截器的preHandler(…)方法)
4.提取Request中的模型數(shù)據(jù),填充Handler入?yún)ⅲ_始執(zhí)行Handler(Controller)。 在填充Handler的入?yún)⑦^程中,根據(jù)你的配置,Spring將幫你做一些額外的工作:
HttpMessageConveter: 將請求消息(如Json、xml等數(shù)據(jù))轉(zhuǎn)換成一個(gè)對象,將對象轉(zhuǎn)換為指定的響應(yīng)信息
數(shù)據(jù)轉(zhuǎn)換:對請求消息進(jìn)行數(shù)據(jù)轉(zhuǎn)換。如String轉(zhuǎn)換成Integer、Double等
數(shù)據(jù)格式化:對請求消息進(jìn)行數(shù)據(jù)格式化。 如將字符串轉(zhuǎn)換成格式化數(shù)字或格式化日期等
數(shù)據(jù)驗(yàn)證: 驗(yàn)證數(shù)據(jù)的有效性(長度、格式等),驗(yàn)證結(jié)果存儲到BindingResult或Error中
5.Handler執(zhí)行完成后,向DispatcherServlet 返回一個(gè)ModelAndView對象;
6.根據(jù)返回的ModelAndView,選擇一個(gè)適合的ViewResolver(必須是已經(jīng)注冊到Spring容器中的ViewResolver)返回給DispatcherServlet ;
7.ViewResolver 結(jié)合Model和View,來渲染視圖
8.將渲染結(jié)果返回給客戶端。
*請認(rèn)真填寫需求信息,我們會在24小時(shí)內(nèi)與您取得聯(lián)系。