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 亚洲在线电影,成人久久影院,影音先锋国产资源

          整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          Spring MVC的工作原理,我們來看看其源碼實現(xiàn)

          源:https://www.cnblogs.com/youzhibing/p/10695012.html

          作者:youzhibing2904

          遺留問題

          在關(guān)于利用maven搭建ssm的博客,我們一起來探討下問的最多的問題中,我遺留了一個問題:Spring mvc是何時、何地、如何將Model中的屬性綁定到哪個作用域,這里的作用域指的是Servlet的四大作用域;不了解問題背景的可以回過頭去看看我的上篇博文。

          明確的解答我會放到最后,在解答問題之前,我先和大家一起來捋一捋Spring mvc的工作原理。廢話不多說,開始我們神秘的探險之旅!

          應(yīng)用示例

          在講工作原理之前,我們先看一個簡單的spring mvc(ssm)示例,以及實現(xiàn)的效果

          工程代碼地址:ssm-web 

          工程結(jié)構(gòu)與效果如上所示,我們不做過多的探究,我們打起精神往下看本篇的重點

          工作原理

          準備 - 資源的加載與初始化

          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策略接口的默認實現(xiàn),后續(xù)DispatcherServlet初始化策略的時候會用到

          2、interceptor定義的加載

          spring啟動過程中會調(diào)用InterceptorsBeanDefinitionParser的parse方法來解析出我們自定義的interceptor定義,封裝成MappedInterceptor類型的bean定義,并放到spring容器中;我們可以簡單的認為spring容器中已經(jīng)存在了我們自定義的interceptor的bean定義

          3、DispatcherServlet初始化策略:initStrategies

          DispatcherServlet的繼承圖如下


          DispatcherServlet是一個Servlet,tomcat啟動過程中會調(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);

          }

          實例化步驟1中的默認實現(xiàn),并填充到DispatcherServlet各個屬性值中

          4、DefaultAnnotationHandlerMapping的攔截器初始化

          DispatcherServlet.properties種指定了兩個默認的HandlerMapping:BeanNameUrlHandlerMapping、DefaultAnnotationHandlerMapping,這兩者的類繼承圖如下(我們暫時只關(guān)注DefaultAnnotationHandlerMapping)


          DefaultAnnotationHandlerMapping間接實現(xiàn)了ApplicationContextAware,那么在DefaultAnnotationHandlerMapping實例初始化過程中,會調(diào)用setApplicationContext(ApplicationContext applicationContext)方法,一串調(diào)用后,會來到AbstractUrlHandlerMapping的initApplicationContext()

          @Override
          protected void initApplicationContext() throws BeansException {
           extendInterceptors(this.interceptors);
           detectMappedInterceptors(this.mappedInterceptors);
           initInterceptors();
          }
          

          初始化了DefaultAnnotationHandlerMapping的攔截器:interceptor

          我們來看下具體的初始化過程,看看上面的順序是否只是我個人的臆想?

          可以看到,初始化順序就是我們上面說的,不是我個人的意淫;此時的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. 決定哪個handler來處理當前的請求
           // mappedHandler是由handler和interceptor集合組成的一個執(zhí)行鏈,有點類似FilterChain
           mappedHandler = getHandler(processedRequest);
           if (mappedHandler == null || mappedHandler.getHandler() == null) {
           noHandlerFound(processedRequest, response);
           return;
           }
           // Determine handler adapter for the current request. 決定哪個adapter來處理當前的請求
           // 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)用適配當前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)用適配當前url的interceptor的postHandler方法
           mappedHandler.applyPostHandle(processedRequest, response, mv);
           }
           catch (Exception ex) {
           dispatchException = ex;
           }
           // 處理handler返回的結(jié)果,會調(diào)用適配當前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具體如何找到匹配當前url的handler(一般而言就是我們的controller)、handlerAdapter具體如何回調(diào)真正的handler,有興趣的可以自行去跟下,我就不跟了。我們具體看下processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 這個與我們最初的疑問有關(guān)

          processDispatchResult

          可以看到model中的persons會被設(shè)置到request的attributes中,然后轉(zhuǎn)發(fā)請求到show_person.jsp,轉(zhuǎn)發(fā)過程中request作用域的變量仍然有效,所以show_person.jsp中的jstl標簽和el表達式能夠取到persons變量,最后將show_person.jsp中的內(nèi)容填充好之后的靜態(tài)內(nèi)容返回給請求者;至此就完成了一次請求的響應(yīng)

          問題解答

          回到我們開篇的疑問:Spring mvc是何時、何地、如何將Model中的屬性綁定到哪個作用域?想必大家已經(jīng)知道答案了

          Controller中的model、ModelMap的注入由spring mvc完成,這個不是請求傳入的參數(shù),用于綁定變量到Servlet作用域;默認情況下,在DispatcherServlet調(diào)用了真正的handler之后,將結(jié)果返回給請求者的過程中,將model、modelMap中的變量設(shè)置到了request的attributes中,轉(zhuǎn)發(fā)的過程中,request中的變量仍然有效,所以show_person.jsp中能取到persons這個變量,自此疑問得到解答

          總結(jié)

          1、Spring MVC工作原理圖

          圖是用的別人的,具體是誰的我也不記得了(捂臉)

          2、DefaultAnnotationHandlerMapping在spring3.2中被廢棄,替換成了RequestMappingHandlerMapping


          對于 Web 應(yīng)用程序而言,我們從瀏覽器發(fā)起一個請求,請求經(jīng)過一系列的分發(fā)和處理,最終會進入到我們指定的方法之中,這一系列的的具體流程到底是怎么樣的呢?

          Spring MVC 請求流程

          記得在初入職場的時候,面試前經(jīng)常會背一背 Spring MVC 流程,印象最深的就是一個請求最先會經(jīng)過 DispatcherServlet 進行分發(fā)處理,DispatcherServlet 就是我們 Spring MVC 的入口類,下面就是一個請求的大致流轉(zhuǎn)流程(圖片參考自 Spring In Action):

          1. 一個請求過來之后會到達 DispatcherServlet,但是 DispatcherServlet 也并不知道這個請求要去哪里。
          2. DispatcherServlet 收到請求之后會去查詢處理器映射(HandlerMapping),從而根據(jù)瀏覽器發(fā)送過來的 URL 解析出請求最終應(yīng)該調(diào)用哪個控制器。
          3. 到達對應(yīng)控制器(Controller)之后,會完成一些邏輯處理,而且在處理完成之后會生成一些返回信息,也就是 Model,然后還需要選擇對應(yīng)的視圖名。
          4. 將模型(Model)和視圖(View)傳遞給對應(yīng)的視圖解析器(View Resolver),視圖解析器會將模型和視圖進行結(jié)合。
          5. 模型和視圖結(jié)合之后就會得到一個完整的視圖,最終將視圖返回前端。

          上面就是一個傳統(tǒng)的完整的 Spring MVC 流程,為什么要說這是傳統(tǒng)的流程呢?因為這個流程是用于前后端沒有分離的時候,后臺直接返回頁面給瀏覽器進行渲染,而現(xiàn)在大部分應(yīng)用都是前后端分離,后臺直接生成一個 Json 字符串就直接返回前端,不需要經(jīng)過視圖解析器進行處理,也就是說前后端分離之后,流程就簡化成了 1-2-3-4-7(其中第四步返回的一般是 Json 格式數(shù)據(jù))。

          Spring MVC 兩大階段

          Spring MVC主要可以分為兩大過程,一是初始化,二就是處理請求。初始化的過程主要就是將我們定義好的 RequestMapping 映射路徑和 Controller 中的方法進行一一映射存儲,這樣當收到請求之后就可以處理請求調(diào)用對應(yīng)的方法,從而響應(yīng)請求。

          初始化

          初始化過程的入口方法是 DispatchServlet 的 init() 方法,而實際上 DispatchServlet 中并沒有這個方法,所以我們就繼續(xù)尋找父類,會發(fā)現(xiàn) init 方法在其父類(FrameworkServlet)的父類 HttpServletBean 中。

          HttpServletBean#init()

          在這個方法中,首先會去家在一些 Servlet 相關(guān)配置(web.xml),然后會調(diào)用 initServletBean() 方法,這個方法是一個空的模板方法,業(yè)務(wù)邏輯由子類 FrameworkServlet 來實現(xiàn)。

          FrameworkServlet#initServletBean

          這個方法本身沒有什么業(yè)務(wù)邏輯,主要是初始化 WebApplicationContext 對象,WebApplicationContext 繼承自 ApplicationContext,主要是用來處理 web 應(yīng)用的上下文。

          FrameworkServlet#initWebApplicationContext

          initWebApplicationContext() 方法主要就是為了找到一個上下文,找不到就會創(chuàng)建一個上下文,創(chuàng)建之后,最終會調(diào)用方法 configureAndRefreshWebApplicationContext(cwac) 方法,而這個方法最終在設(shè)置一些基本容器標識信息之后會去調(diào)用 refresh()方法,也就是初始化 ioc 容器。

          當調(diào)用 refresh() 方法初始化 ioc 容器之后,最終會調(diào)用方法 onRefresh(),這個方法也是一個模板鉤子方法,由子類實現(xiàn),也就是回到了我們 Spring MVC 的入口類 DispatcherServlet。

          DispatchServlet#onRefresh

          onRefresh() 方法就是 Spring MVC 初始化的最后一個步驟,在這個步驟當中會初始化 Spring MVC 流程中可能需要使用到的九大組件。

          Spring MVC 九大組件

          MultipartResolver

          這個組件比較熟悉,主要就是用來處理文件上傳請求,通過將普通的 Request 對象包裝成 MultipartHttpServletRequest 對象來進行處理。

          LocaleResolver

          LocaleResolver 用于初始化本地語言環(huán)境,其從 Request 對象中解析出當前所處的語言環(huán)境,如中國大陸則會解析出 zh-CN 等等,模板解析以及國際化的時候都會用到本地語言環(huán)境。

          ThemeResolver

          這個主要是用戶主題解析,在 Spring MVC 中,一套主題對應(yīng)一個 .properties 文件,可以存放和當前主題相關(guān)的所有資源,如圖片,css樣式等。

          HandlerMapping

          用于查找處理器(Handler),比如我們 Controller 中的方法,這個其實最主要就是用來存儲 url 和 調(diào)用方法的映射關(guān)系,存儲好映射關(guān)系之后,后續(xù)有請求進來,就可以知道調(diào)用哪個 Controller 中的哪個方法,以及方法的參數(shù)是哪些。

          HandlerAdapter

          這是一個適配器,因為 Spring MVC 中支持很多種 Handler,但是最終將請求交給 Servlet 時,只能是 doService(req,resp) 形式,所以 HandlerAdapter 就是用來適配轉(zhuǎn)換格式的。

          HandlerExceptionResolver

          這個組件主要是用來處理異常,不過看名字也很明顯,這個只會對處理 Handler 時產(chǎn)生的異常進行處理,然后會根據(jù)異常設(shè)置對應(yīng)的 ModelAndView,然后交給 Render 渲染成頁面。

          RequestToViewNameTranslator

          這個主鍵主要是從 Request 中獲取到視圖名稱。

          ViewResolver

          這個組件會依賴于 RequestToViewNameTranslator 組件獲取到的視圖名稱,因為視圖名稱是字符串格式,所以這里會將字符串格式的視圖名稱轉(zhuǎn)換成為 View 類型視圖,最終經(jīng)過一系列解析和變量替換等操作返回一個頁面到前端。

          FlashMapManager

          這個主鍵主要是用來管理 FlashMap,那么 FlashMap 又有什么用呢?要明白這個那就不得不提到重定向了,有時候我們提交一個請求的時候會需要重定向,那么假如參數(shù)過多或者說我們不想把參數(shù)拼接到 url 上(比如敏感數(shù)據(jù)之類的),這時候怎么辦呢?因為參數(shù)不拼接在 url 上重定向是無法攜帶參數(shù)的。

          FlashMap 就是為了解決這個問題,我們可以在請求發(fā)生重定向之前,將參數(shù)寫入 request 的屬性 OUTPUT_FLASH_MAP_ATTRIBUTE 中,這樣在重定向之后的 handler 中,Spring 會自動將其設(shè)置到 Model 中,這樣就可以從 Model 中取到我們傳遞的參數(shù)了。

          處理請求

          在九大組件初始化完成之后,Spring MVC 的初始化就完成了,接下來就是接收并處理請求了,那么處理請求的入口在哪里呢?處理請求的入口方法就是 DispatcherServlet 中的 doService 方法,而 doService 方法又會調(diào)用 doDispatch 方法。

          DispatcherServlet#doDispatch

          這個方法最關(guān)鍵的就是調(diào)用了 getHandler 方法,這個方法就是會獲取到前面九大組件中的 HandlerMapping,然后進行反射調(diào)用對應(yīng)的方法完成請求,完成請求之后后續(xù)還會經(jīng)過視圖轉(zhuǎn)換之類的一些操作,最終返回 ModelAndView,不過現(xiàn)在都是前后端分離,基本也不需要用到視圖模型,在這里我們就不分析后續(xù)過程,主要就是分析 HandlerMapping 的初始化和查詢過程。

          DispatcherServlet#getHandler

          這個方法里面會遍歷 handllerMappings,這個 handllerMappings 是一個 List 集合,因為 HandlerMapping 有多重實現(xiàn),也就是 HandlerMapping 不止一個實現(xiàn),其最常用的兩個實現(xiàn)為 RequestMappingHandlerMapping 和 BeanNameUrlHandlerMapping。

          AbstractHandlerMapping#getHandler

          AbstractHandlerMapping 是一個抽象類,其 getHandlerInternal 這個方法也是一個模板方法:

          getHandlerInternal 方法最終其會調(diào)用子類實現(xiàn),而這里的子類實現(xiàn)會有多個,其中最主要的就是 AbstractHandlerMethodMapping 和 AbstractUrlHandlerMapping 兩個抽象類,那么最終到底會調(diào)用哪個實現(xiàn)類呢?

          這時候如果拿捏不準我們就可以看一下類圖,上面我們提到,HandlerMapper 有兩個非常主要的實現(xiàn)類:RequestMappingHandlerMapping 和 BeanNameUrlHandlerMapping。那么我們就分別來看一下這兩個類的類圖關(guān)系:

          可以看到,這兩個實現(xiàn)類的抽象父類正好對應(yīng)了 AbstractHandlerMapping 的兩個子類,所以這時候具體看哪個方法,那就看我們想看哪種類型了。

          • RequestMappingHandlerMapping:主要用來存儲 RequestMapping 注解相關(guān)的控制器和 url 的映射關(guān)系。
          • BeanNameUrlHandlerMapping:主要用來處理 Bean name 直接以 / 開頭的控制器和 url 的映射關(guān)系。

          其實除了這兩種 HandlerMapping 之外,Spring 中還有其他一些 HandllerMapping,如 SimpleUrlHandlerMapping 等。

          提到的這幾種 HandlerMapping,對我們來說最常用,最熟悉的那肯定就是 RequestMappingHandlerMapping ,在這里我們就以這個為例來進行分析,所以我們應(yīng)該

          AbstractHandlerMethodMapping#getHandlerInternal

          這個方法本身也沒有什么邏輯,其主要的核心查找 Handler 邏輯在 lookupHandlerMethod 方法中,這個方法主要是為了獲取一個 HandlerMethod 對象,前面的方法都是 Object,而到這里變成了 HandlerMethod 類型,這是因為 Handler 有各種類型,目前我們已經(jīng)基本跟到了具體類型之下,所以類型就變成了具體類型,而如果我們看的的另一條分支線,那么返回的就會是其他對象,正是因為支持多種不同類型的 HandlerMapping 對象,所以最終為了統(tǒng)一執(zhí)行,才會需要在獲得 Hanlder 之后,DispatcherServlet 中會再次通過調(diào)用 getHandlerAdapter 方法來進一步封裝成 HandlerAdapter 對象,才能進行方法的調(diào)用

          AbstractHandlerMethodMapping#lookupHandlerMethod

          這個方法主要會從 mappingRegistry 中獲取命中的方法,獲取之后還會經(jīng)過一系列的判斷比較判斷比較,因為有些 url 會對應(yīng)多個方法,而方法的請求類型不同,比如一個 GET 方法,一個 POST 方法,或者其他一些屬性不相同等等,都會導(dǎo)致最終命中到不同的方法,這些邏輯主要都是在 addMatchingMappings 方法去進一步實現(xiàn),并最終將命中的結(jié)果加入到 matches 集合內(nèi)。

          在這個方法中,有一個對象非常關(guān)鍵,那就是 mappingRegistry,因為最終我們根據(jù) url 到這里獲取到對應(yīng)的 HandlerMtthod,所以這個對象很關(guān)鍵:

          看這個對象其實很明顯可以看出來,這個對象其實只是維護了一些 Map 對象,所以我們可以很容易猜測到,一定在某一個地方,將 url 和 HandlerMapping 或者 HandlerMethod 的映射關(guān)系存進來了,這時候其實我們可以根據(jù) getMappingsByUrl 方法來進行反推,看看 urlLookup 這個 Map 是什么時候被存入的,結(jié)合上面的類圖關(guān)系,一路反推,很容易就可以找到這個 Map 中的映射關(guān)系是 AbstractHandlerMethodMapping 對象的 afterPropertiesSet 方法實現(xiàn)的(AbstractHandlerMethodMapping 實現(xiàn)了 InitializingBean 接口),也就是當這個對象初始化完成之后,我們的 url 和 Handler 映射關(guān)系已經(jīng)存入了 MappingRegistry 對象中的集合 Map 中。

          AbstractHandlerMethodMapping 的初始化

          afterPropertiesSet 方法中并沒有任何邏輯,而是直接調(diào)用了 initHandlerMethods。

          AbstractHandlerMethodMapping#initHandlerMethods

          initHandlerMethods 方法中,首先還是會從 Spring 的上下文中獲取所有的 Bean,然后會進一步從帶有 RequestMapping 注解和 Controller 注解中的 Bean 去解析并獲得 HandlerMethod。

          AbstractHandlerMethodMapping#detectHandlerMethods

          這個方法中,其實就是通過反射獲取到 Controller 中的所有方法,然后調(diào)用 registerHandlerMethod 方法將相關(guān)信息注冊到 MappingRegistry 對象中的各種 Map 集合之內(nèi):

          AbstractHandlerMethodMapping#register

          registerHandlerMethod 方法中會直接調(diào)用 AbstractHandlerMethodMapping 對象持有的 mappingRegistry 對象中的 regidter方法,這里會對 Controller 中方法上的一些元信息進行各種解析,比如參數(shù),路徑,請求方式等等,然后會將各種信息注冊到對應(yīng)的 Map 集合中,最終完成了整個初始化。

          總結(jié)

          本文重點以 RequestMappingHandlerMapping 為例子分析了在 Spring 當中如何初始化 HandlerMethod,并最終在調(diào)用的時候又是如何根據(jù) url 獲取到對應(yīng)的方法并進行執(zhí)行最終完成整個流程。

          、SpringMVC介紹

          1.MVC介紹

          模型-視圖-控制器(MVC 是一個眾所周知的以設(shè)計界面應(yīng)用程序為基礎(chǔ)的設(shè)計模式。它主要通過分離模型、視圖及控制器在應(yīng)用程序中的角色將業(yè)務(wù)邏輯從界面中解耦。通常,模型負責封裝應(yīng)用程序數(shù)據(jù)在視圖層展示。視圖僅僅只是展示這些數(shù)據(jù),不包含任何業(yè)務(wù)邏輯。控制器負責接收來自用戶的請求,并調(diào)用后臺服務(wù)(manager或者dao)來處理業(yè)務(wù)邏輯。處理后,后臺業(yè)務(wù)層可能會返回了一些數(shù)據(jù)在視圖層展示。控制器收集這些數(shù)據(jù)及準備模型在視圖層展示。MVC模式的核心思想是將業(yè)務(wù)邏輯從界面中分離出來,允許它們單獨改變而不會相互影響。

          springmvc介紹

          概念

          1.Spring Web MVC是一種基于Java的實現(xiàn)了Web MVC設(shè)計模式的請求驅(qū)動類型的輕量級Web框架
          2.使用了MVC架構(gòu)模式的思想,將web層進行職責解耦
          3.基于請求驅(qū)動指的就是使用請求-響應(yīng)模型
          4.框架的目的就是幫助我們簡化開發(fā),
          Spring Web MVC也是要簡化我們?nèi)粘eb開發(fā)的。

          優(yōu)點

          1.性能比struts2好
          2.簡單、便捷,易學(xué)
          3.和spring無縫銜接【IOC,AOP】
          4.使用約定優(yōu)于配置
          5.支持Restful
          6.異常處理,國際化,數(shù)據(jù)驗證,類型轉(zhuǎn)換等
          7.使用的人多,使用的公司多

          二、第一個案例HelloWorld

          1.創(chuàng)建web項目

          普通web項目

          2.導(dǎo)入相關(guān)jar包

          3.創(chuàng)建配置文件

          在src目錄下創(chuàng)建一個 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

          4.設(shè)置處理器和映射器

          在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進行查找 ,
          	              需要在配置Handler時指定beanname(就是url) 所有的映射器都實現(xiàn) 
          		 HandlerMapping接口。
          	 -->
          	<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
          
          	<!-- 配置 Controller適配器 -->
          	<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
          	
          </beans>
          123456789101112131415

          5.配置前端控制器

          <?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,
            	默認加載的是/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

          6.創(chuàng)建自定義的Controller

          /**
           * 自定義控制器
           * 必須實現(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

          7.測試效果

          三、注解方式的使用

          通過上一個普通實現(xiàn)的方式大家會發(fā)現(xiàn)其實現(xiàn)步驟比較繁瑣,而且自定義controller也只有一個默認被調(diào)用的方法。不是很方便,這時我們可以使用SpringMVC基于注解的使用方式來實現(xiàn),步驟如下:

          1.修改配置文件開啟注解方式

          <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

          2.controller中通過注解實現(xiàn)

          /**
           * 自定義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

          3.測試

          四、SpringMVC工作原理的介紹

          1.原理圖

          2.流程文字說明

          1.用戶向服務(wù)器發(fā)送請求,請求被Spring 前端控制Servelt DispatcherServlet捕獲;
          2.DispatcherServlet對請求URL進行解析,得到請求資源標識符(URI)。然后根據(jù)該URI,調(diào)用HandlerMapping獲得該Handler配置的所有相關(guān)的對象(包括Handler對象以及Handler對象對應(yīng)的攔截器),最后以HandlerExecutionChain對象的形式返回;
          3.DispatcherServlet 根據(jù)獲得的Handler,選擇一個合適的HandlerAdapter。(附注:如果成功獲得HandlerAdapter后,此時將開始執(zhí)行攔截器的preHandler(…)方法)
          4.提取Request中的模型數(shù)據(jù),填充Handler入?yún)ⅲ_始執(zhí)行Handler(Controller)。 在填充Handler的入?yún)⑦^程中,根據(jù)你的配置,Spring將幫你做一些額外的工作:
          HttpMessageConveter: 將請求消息(如Json、xml等數(shù)據(jù))轉(zhuǎn)換成一個對象,將對象轉(zhuǎn)換為指定的響應(yīng)信息
          數(shù)據(jù)轉(zhuǎn)換:對請求消息進行數(shù)據(jù)轉(zhuǎn)換。如String轉(zhuǎn)換成Integer、Double等
          數(shù)據(jù)格式化:對請求消息進行數(shù)據(jù)格式化。 如將字符串轉(zhuǎn)換成格式化數(shù)字或格式化日期等
          數(shù)據(jù)驗證: 驗證數(shù)據(jù)的有效性(長度、格式等),驗證結(jié)果存儲到BindingResult或Error中
          5.Handler執(zhí)行完成后,向DispatcherServlet 返回一個ModelAndView對象;
          6.根據(jù)返回的ModelAndView,選擇一個適合的ViewResolver(必須是已經(jīng)注冊到Spring容器中的ViewResolver)返回給DispatcherServlet ;
          7.ViewResolver 結(jié)合Model和View,來渲染視圖
          8.將渲染結(jié)果返回給客戶端。

          3.組件說明


          主站蜘蛛池模板: 亚洲av无码一区二区乱子伦as| 韩国精品一区视频在线播放| 免费日本一区二区| 91麻豆精品国产自产在线观看一区| 日本一区二区三区高清| 91亚洲一区二区在线观看不卡| 红杏亚洲影院一区二区三区| 亚洲AV无码国产一区二区三区 | 亚洲日本一区二区| 亚洲成av人片一区二区三区| 日本一区二区三区精品视频| 伊人久久精品一区二区三区| 亚洲国产精品一区二区成人片国内 | 鲁大师成人一区二区三区| 亚洲av成人一区二区三区在线播放| 无码人妻精品一区二区三| 亚洲欧洲一区二区| 日本内射精品一区二区视频 | 国产视频福利一区| 国产乱码精品一区二区三区四川人| 97精品一区二区视频在线观看| 国产成人精品一区二区秒拍| 国产自产V一区二区三区C| 国产午夜精品一区理论片| 高清国产AV一区二区三区| 日韩一区二区在线观看视频| 无码人妻一区二区三区免费看| 国产伦精品一区二区三区| 日本一区二区不卡视频| 亚洲av成人一区二区三区| 国产精品夜色一区二区三区 | 欧美日韩国产免费一区二区三区| 3D动漫精品啪啪一区二区下载| 无码人妻精品一区二区蜜桃网站| 无码国产精品一区二区免费式影视 | 国产一区二区三区免费观在线| 欧洲精品无码一区二区三区在线播放| 熟妇人妻一区二区三区四区 | 国产一区二区精品久久岳| 久久精品一区二区三区不卡| 亚洲av无码一区二区乱子伦as|