整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          SpringMVC流程及源碼分析

          SpringMVC流程及源碼分析

          學了一遍SpringMVC以后,想著做一個總結,復習一下。復習寫下面的總結的時候才發現,其實自己學得并不徹底、牢固、也沒有學全,視頻跟書本是要結合起來一起,每一位老師的視頻可能提到的東西都不一致,也導致也不是很全面,書本上會講的筆記系統、全面。同時我自己也是一個初學者,下面總結的可能并不完善、正確,希望看到的大神給我指出,在此非常感謝。


          目錄

          • SpringMVC流程及源碼分析
          • 一 、Spring核心模塊
            • 1、核心模塊
            • 2、Spring版本命名規則(補充)
          • 二、SpringMVC流程及原理
            • 1、執行流程
              • 1.1、執行流程
              • 1.2、執行流程說明:
                • 1.2.1、第02、03說明
                • 1.2.2、第04說明
                • 1.2.2、SpringMVC組件說明
                • 1.2.3、SpringMVC詳細流程圖
          • 二、源碼分析
            • 1、初始化
              • 1.1、ApplicationContext
            • 2、前端控制器(中央處理器)DistepcherServlet
              • 2.1、查找處理器映射器HandlerMapping
              • 2.2、根據處理器映射器HandlerMapping返回結果調用處理器適配器HandlerAdapter
              • 2.3、檢查攔截器Interceptor
              • 2.3、處理器適配器HandlerAdapter執行Handler(Controller)返回ModelAndView
              • 2.4、視圖解析器ViewResolver
              • 2.5、視圖View
                • 2.5.1、視圖對象的作用
                • 2.5.2、View接口圖
                • 2.5.3、View的實現類圖
                • 2.5.4、View的UML圖
                • 2.5.5、常用的View視圖類
              • 2.6、其他重要的點
                • 2.6.1、DispatcherServlet.properties
          • 三、引用參考資料
            • 1、引用資料
            • 2、參考資料

          一 、Spring核心模塊

          1、核心模塊

          Spring Web MVC (下文簡稱為 SpringMVC )是 Spring 提供 Web 應用的框架設計,屬于表現層的框架。SpringMVC是Spring框架的一部分。
          Spring框架包括大致六大模塊,核心容器(Core Container)、AOP和設備支持、數據訪問及集成、Web、報文發送、Test

          圖片來源于Spring官網5.0.0.M5:

          ? https://docs.spring.io/spring-framework/docs/5.0.0.M5/spring-framework-reference/html/overview.html#overview-modules

          對于Spring5模塊圖,有2點疑問:
          1、不清楚為什么在Spring官網上5.0版本以后,Release版(穩定版)的都未找到模塊圖,但是在M(里程碑版)版找到 了,如果有人在5.0以后的Release版(穩定版)找到,麻煩給我留個言,謝謝。
          2、在其他博文中看到Spring5模塊結構圖是這樣的:

          挺奇怪這個圖是哪里來的?(路過的大神請指點)

          對于問題2,我在Spring5.2.13.RELEASE GA中,找到了如下所示信息:

          拷貝以上信息:

          Spring Framework Documentation

          Version 5.2.13.RELEASE

          What’s New, Upgrade Notes, Supported Versions, and other topics, independent of release cadence, are maintained externally on the project’s Github Wiki.

          Overview

          history, design philosophy, feedback, getting started.

          Core

          IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP.

          Testing

          Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient.

          Data Access

          Transactions, DAO Support, JDBC, O/R Mapping, XML Marshalling.

          Web Servlet

          Spring MVC, WebSocket, SockJS, STOMP Messaging.

          Web Reactive

          Spring WebFlux, WebClient, WebSocket.

          Integration

          Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Caching.

          Languages

          Kotlin, Groovy, Dynamic Languages.

          按照以上信息的Web Servlet、Web Reactive已經是分屬于不同的模塊了。

          • Web Servlet:Spring MVC, WebSocket, SockJS, STOMP Messaging.
          • Web Reactive:Spring WebFlux, WebClient, WebSocket.

          Spring官方文檔:https://spring.io/projects/spring-framework#learn/

          2、Spring版本命名規則(補充)

          上面提到了Spring有不同的版本,在此記錄一下各個版本的意義。

          描述方式

          說明

          含義

          Snapshot

          快照版

          尚不穩定,仍處于開發中的版本

          Release

          穩定版

          功能相對穩定,可以對外發行,但有時間限制

          GA

          正式版

          代表廣泛可用的穩定版(General Availability)

          M

          里程碑版

          (M是Milestone的意思)具有一些全新的功能或是有意義的版本

          RC

          終測版

          Release Candidate(最終測試),即將作為正式版發布

          二、SpringMVC流程及原理

          1、執行流程

          SpringMVC執行流程圖

          圖片來源:三、引用參考資料

          1.1、執行流程

          • 01、用戶發送出請求到前端控制器(中央處理器)DispatcherServlet進行處理。
          • 02、前端控制器DispatcherServlet收到請求后,調用處理器映射器HandlerMapping。
          • 03、處理器映射器HandlerMapping(處理器映射器)根據request請求的URL等信息查找能夠進行處理的Handler,以及相關攔截器interceptor,并構造HandlerExecutionChain執行鏈,然后將構造好的HandlerExecutionChain執行鏈對象返回給前端控制器DispatcherServlet。
          • 04、前端控制器DispatcherServlet根據處理器映射器HandlerMapping的
          • 05、處理器適配器HandlerAdapter經過適配調用具體的處理器(Handler/Controller),即業務中自己寫的Controller。
          • 06、Controller處理完后返回ModelAndView(springmvc的封裝對象,將model和view封裝在一起)給處理器適配器HandlerAdapter;
          • 07、處理器適配器HandlerAdapter將Controller執行結果ModelAndView返回給前端控制器DispatcherServlet。
          • 08、前端控制器DispatcherServlet調用視圖解析器ViewReslover處理ModelAndView。
          • 09、視圖解析器ViewReslover解析后根據邏輯視圖名解析成物理視圖名即具體的頁面地址,生成并返回具體對象View(springmvc封裝對象,是一個接口)。
          • 10、前端控制器DispatcherServlet根據對象View進行視圖渲染,填充Model。
          • 11、前端控制器DispatcherServlet向用戶返回響應

          1.2、執行流程說明:

          1.2.1、第02、03說明

          (1) 處理器映射器:springmvc框架中的一種對象,框架把實現了HandlerMapping接口的類都叫做映射器(多個);

          (2) 處理器映射器作用:根據請求,從springmvc容器對象中獲取處理器對象(MyController controller=ctx.getBean("some")

          (3) 框架把找到的處理器對象放到一個叫做處理器執行鏈(HandlerExecutionChain)的類保存

          (4) HandlerExecutionchain:類中保存著
          ?a:處理器對象(MyController);
          ?b:項目中的所有的攔截器List

          (5) 方法調用:HandlerExecutionChain mappedHandler - getHandler (processedRequest);

          1.2.2、第04說明

          (1) HandlerExecutionChain執行鏈找到對應的處理器映射器HandlerAdapter。
          (2) 處理器適配器:springmvc框架中的對象,需要實現HandlerAdapter接口,
          (3) 處理器適配器作用:執行處理器方法(調用MyController.doSome()得到返回值ModelAndView )
          (4) 前端控制器中調用適配器:HandlerAdapter ha=getHandlerAdapter (mappedHandler.getHandler());
          (5) 執行處理器方法:mv=ha.handle (processedRequest, response, mappedHandler.getHandler());

          第08說明:
          (1) 視圖解析器:springmvc中的對象,需要實現ViewResoler接口(可以有多個)
          (2) 視圖解析器作用:組成視圖完整路徑,使用前綴,后綴。并創建View對象。
          (3) view是一個接口,表示視圖的,在框架中jsp,htm1不是string表示,而是使用view和他的實現類表示視圖。

          InternalResourceview:視圖類,表示jsp文件,視圖解析器會創建InternalResourceView類對象。 這個對象的里面,有一個屬性url-/WEB-INF/view/show.jsp

          1.2.2、SpringMVC組件說明

          • (1). 前端控制器(DispatcherServlet):接收請求,響應結果,相當于電腦的CPU。
          • (2). 處理器映射器(HandlerMapping):根據URL去查找處理器.
          • (3). 處理器(Handler):(需要程序員去寫代碼處理邏輯的).
          • (4). 處理器適配器(HandlerAdapter):會把處理器包裝成適配器,這樣就可以支持多種類型的處理器,類比筆記本的適配器(適配器模式的應用).
          • (5). 視圖解析器(ViewResovler):進行視圖解析,多返回的字符串,進行處理,可以解析成對應的頁面.

          1.2.3、SpringMVC詳細流程圖

          綜上所述,總結下SpringMVC的詳細流程圖:


          圖片來源:三、引用參考資料

          二、源碼分析

          以下源碼來源jar包:spring-webmvc-5.25.RELEASE.jar

          1、初始化

          1.1、ApplicationContext

          ? ApplicationContext初始化入口類:ApplicationObjectSupport的setApplicationContext方法,setApplicationContext方法中核心部分就是初始化容器initApplicationContext(context),子類AbstractDetectingUrlHandlerMapping實現了該方法。
          類圖:


          UML圖:


          ? RequestMappingHandlerMapping ,用于注解@Controller,@RequestMapping來定義controller.
          初始化時,3個類的大致分工如下:

          • AbstractHandlerMethodMapping定義整個算法流程;
          • RequestMappingInfoHandlerMapping提供匹配條件RequestMappingInfo的解析處理;
          • RequestMappingHandlerMapping根據@RequestMapping注解生成 RequestMappingInfo,同時提供isHandler實現

          2、前端控制器(中央處理器)DistepcherServlet

          ? 從上面的流程圖可以看到前端控制器(中央處理器)DistepcherServlet是SpringMVC核心,查看DistepcherServlet類的繼承情況。
          UML圖:
          ![2021022601-08-DispatcherServlet UML圖](https://gitee.com/chuchq/blogs-gallery/raw/master/images / 2021/2021022601-08-DispatcherServlet UML圖.png)
          從繼承關系看出:
          ? DistepcherServlet ---> FrameworkServlet ---> HttpServletBean---> HttpServlet
          ? 那就說明DistepcherServlet 類也是一個Servlet類,那最終核心的方法就是service()方法,即Servlet的核心方法。
          ? 那就找service()方法,在DistepcherServlet中沒有servic()方法,在父類FrameworkServlet有service()方法,源碼如下:
          來源:

          org.springframework.web.servlet.FrameworkServlet.service(HttpServletRequest request, HttpServletResponse response)

          /**
          	 * Override the parent class implementation in order to intercept PATCH requests.
          	 */
          	@Override
          	protected void service(HttpServletRequest request, HttpServletResponse response)
          			throws ServletException, IOException {
          		HttpMethod httpMethod=HttpMethod.resolve(request.getMethod());
          		if (httpMethod==HttpMethod.PATCH || httpMethod==null) {
          			processRequest(request, response);
          		}
          		else {
          			super.service(request, response);
          		}
          	}
          

          可以看到:
          FrameworkServlet.service(HttpServletRequest request, HttpServletResponse response)拿到request請求,判斷當前請求是否是PATCH請求,不是的就調用父類的servic()方法,調用父類中的service方法就是去調用該類中doPost(),doGet()方法,根據不同的請求方式然后走doPost()或者doGet(),調用中以doGet()為例,
          FrameworkServlet類的doGet()源碼:

          /**
          	 * Delegate GET requests to processRequest/doService.
          	 * <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
          	 * with a {@code NoBodyResponse} that just captures the content length.
          	 * @see #doService
          	 * @see #doHead
          	 */
          	@Override
          	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
          			throws ServletException, IOException {
          		processRequest(request, response);
          	}
          

          ? doGet()又調用FrameworkServlet類中的processRequest(request, response);

          /**
          	 * Process this request, publishing an event regardless of the outcome.
          	 * <p>The actual event handling is performed by the abstract
          	 * {@link #doService} template method.
          	 */
          	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
          			throws ServletException, IOException {
          
          		long startTime=System.currentTimeMillis();
          		Throwable failureCause=null;
          
          		LocaleContext previousLocaleContext=LocaleContextHolder.getLocaleContext();
          		LocaleContext localeContext=buildLocaleContext(request);
          
          		RequestAttributes previousAttributes=RequestContextHolder.getRequestAttributes();
          		ServletRequestAttributes requestAttributes=buildRequestAttributes(request, response, previousAttributes);
          
          		WebAsyncManager asyncManager=WebAsyncUtils.getAsyncManager(request);
          		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
          
          		initContextHolders(request, localeContext, requestAttributes);
          
          		try {
          			doService(request, response);
          		}
          		catch (ServletException | IOException ex) {
          			failureCause=ex;
          			throw ex;
          		}
          		catch (Throwable ex) {
          			failureCause=ex;
          			throw new NestedServletException("Request processing failed", ex);
          		}
          
          		finally {
          			resetContextHolders(request, previousLocaleContext, previousAttributes);
          			if (requestAttributes !=null) {
          				requestAttributes.requestCompleted();
          			}
          			logResult(request, response, failureCause, asyncManager);
          			publishRequestHandledEvent(request, response, startTime, failureCause);
          		}
          	}
          

          ? processRequest(request, response)方法中最關鍵的又調用了doService(request, response);查看FrameworkServlet類中的doService(request, response),或者是調試跟蹤可知,doService(request, response)由子類DispatcherServlet實現。

          源碼來源:

          org.springframework.web.servlet.FrameworkServlet.doService(HttpServletRequest request, HttpServletResponse response)

          /**
          	 * Subclasses must implement this method to do the work of request handling,
          	 * receiving a centralized callback for GET, POST, PUT and DELETE.
          	 * <p>The contract is essentially the same as that for the commonly overridden
          	 * {@code doGet} or {@code doPost} methods of HttpServlet.
          	 * <p>This class intercepts calls to ensure that exception handling and
          	 * event publication takes place.
          	 * @param request current HTTP request
          	 * @param response current HTTP response
          	 * @throws Exception in case of any kind of processing failure
          	 * @see javax.servlet.http.HttpServlet#doGet
          	 * @see javax.servlet.http.HttpServlet#doPost
          	 */
          	protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
          			throws Exception;
          

          ? 查看DispatcherServlet中的doService(HttpServletRequest request, HttpServletResponse response)方法

          /**
          	 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
          	 * for the actual dispatching.
          	 */
          	@Override
          	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
          		logRequest(request);
          
          		// Keep a snapshot of the request attributes in case of an include,
          		// to be able to restore the original attributes after the include.
          		Map<String, Object> attributesSnapshot=null;
          		if (WebUtils.isIncludeRequest(request)) {
          			attributesSnapshot=new HashMap<>();
          			Enumeration<?> attrNames=request.getAttributeNames();
          			while (attrNames.hasMoreElements()) {
          				String attrName=(String) attrNames.nextElement();
          				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
          					attributesSnapshot.put(attrName, request.getAttribute(attrName));
          				}
          			}
          		}
          
          		// Make framework objects available to handlers and view objects.
          		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
          		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
          		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
          		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
          
          		if (this.flashMapManager !=null) {
          			FlashMap inputFlashMap=this.flashMapManager.retrieveAndUpdate(request, response);
          			if (inputFlashMap !=null) {
          				request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
          			}
          			request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
          			request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
          		}
          
          		try {
          			doDispatch(request, response);
          		}
          		finally {
          			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
          				// Restore the original attribute snapshot, in case of an include.
          				if (attributesSnapshot !=null) {
          					restoreAttributesAfterInclude(request, attributesSnapshot);
          				}
          			}
          		}
          	}
          

          ? DispatcherServlet的doService()方法中最終調用doDispatch(request, response),查看源碼如下:
          org.springframework.web.servlet.DispatcherServlet.doDispatch()

          /**
          	 * Process the actual dispatching to the handler.
          	 * <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);
          				// 取得處理當前請求的controller,這里也稱為hanlder處理器,第一個步驟的意義就在這里體現了.這里并不是直接返回controller,而是返回的HandlerExecutionChain請求處理器鏈對象,該對象封裝了handler和攔截器interceptors.
          				// Determine handler for the current request.
          				mappedHandler=getHandler(processedRequest);
          				// 如果handler為空,則返回404
          				if (mappedHandler==null) {
          					noHandlerFound(processedRequest, response);
          					return;
          				}
          				//3. 獲取處理request的處理器適配器HandlerAdapter
          				// Determine handler adapter for the current request.
          				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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
          						return;
          					}
          				}
          				//處理器適配器執行之前,檢查攔截器的方法
          				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          					return;
          				}
          				//處理器適配器根據找到,執行handler,返回ModelAndView
          				// Actually invoke the handler.
          				mv=ha.handle(processedRequest, response, mappedHandler.getHandler());
          
          				if (asyncManager.isConcurrentHandlingStarted()) {
          					return;
          				}
          
          				applyDefaultViewName(processedRequest, mv);
          				mappedHandler.applyPostHandle(processedRequest, response, mv);
          			}
          			catch (Exception ex) {
          				dispatchException=ex;
          			}
          			catch (Throwable err) {
          				// As of 4.3, we're processing Errors thrown from handler methods as well,
          				// making them available for @ExceptionHandler methods and other scenarios.
          				dispatchException=new NestedServletException("Handler dispatch failed", err);
          			}
          			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
          		}
          		catch (Exception ex) {
          			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
          		}
          		catch (Throwable err) {
          			triggerAfterCompletion(processedRequest, response, mappedHandler,
          					new NestedServletException("Handler processing failed", err));
          		}
          		finally {
          			if (asyncManager.isConcurrentHandlingStarted()) {
          				// Instead of postHandle and afterCompletion
          				if (mappedHandler !=null) {
          					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
          				}
          			}
          			else {
          				// Clean up any resources used by a multipart request.
          				if (multipartRequestParsed) {
          					cleanupMultipart(processedRequest);
          				}
          			}
          		}
          	}
          

          ? 可以看出doDispatch()就是SpringMVC的核心代碼了,分析doDispatch():

          2.1、查找處理器映射器HandlerMapping

          ? 首先看下處理器映射器HandlerMapping類圖:

          doDispatch()關鍵代碼:

          HandlerExecutionChain mappedHandler=null;
          
          mappedHandler=getHandler(processedRequest);
          

          ? mappedHandler是一個執行鏈HandlerExecutionChain 對象,這里封裝了handler和攔截器interceptors,getHandler(processedRequest)方法就是從處理器映射器HandlerMapping中找到url和controller的對應關系,并返回給前端控制器DispatchServlet。
          查看getHandler(processedRequest);源碼:

          /**
          	 * Return the HandlerExecutionChain for this request.
          	 * <p>Tries all handler mappings in order.
          	 * @param request current HTTP request
          	 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
          	 */
          	@Nullable
          	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
          		if (this.handlerMappings !=null) {
          			for (HandlerMapping mapping : this.handlerMappings) {
          				HandlerExecutionChain handler=mapping.getHandler(request);
          				if (handler !=null) {
          					return handler;
          				}
          			}
          		}
          		return null;
          	}
          

          調試代碼如下:


          從代碼調試中可以看到handlerMapping中有三個對象:

          this.handlerMappings={ArrayList@4662}  size=3
           0={BeanNameUrlHandlerMapping@4791} 
           1={RequestMappingHandlerMapping@4792} 
           2={RouterFunctionMapping@4793} 
          
          • BeanNameUrlHandlerMapping:初始化時會將urlpath做映射存儲(xml);
          • RequestMappingHandlerMapping:初始化時會將Controller中配置@RequestMapping注解的方法做映射存儲(注解);
          • RouterFunctionMapping:
            (這個對象不是太理解)
            這也就是為什么要去HandlerMapping找一個Handler了,因為處理器映射器HandlerMapping有不同的實現:
          • 1、xml方式
          • 2、注解方式

          接著看getHandler(HttpServletRequest request)方法,先遍歷HandlerMappers,查找控制器找到之后就返回執行鏈HandlerExecutionChain類型的Handler。

          可以看到返回的Handler中,拿到的就是我們自己編碼的Controller類,以及攔截器(演示項目中未編寫,所以調試匯總返回的Handler最后是0 interceptors)
          HandlerExecutionChain with [com.bjpowernode.controller.MyController#doSome()] and 0 interceptors


          將正在調試的idea打開自己編寫的Controller來對照,發現一致:

          2.2、根據處理器映射器HandlerMapping返回結果調用處理器適配器HandlerAdapter

          doDispatch()里面的關鍵代碼:

          HandlerAdapter ha=getHandlerAdapter(mappedHandler.getHandler());
          

          源碼如下:

          /**
          	 * Return the HandlerAdapter for this handler object.
          	 * @param handler the handler object to find an adapter for
          	 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
          	 */
          	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
          		if (this.handlerAdapters !=null) {
          			for (HandlerAdapter adapter : this.handlerAdapters) {
          				if (adapter.supports(handler)) {
          					return adapter;
          				}
          			}
          		}
          		throw new ServletException("No adapter for handler [" + handler +
          				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
          	}
          

          為什么還要獲取處理器適配器HandlerAdapter:與獲取處理器映射器HandlerMapping一樣,Spring提供了不通的處理器適配器。
          調試如下:


          查看DEBUG調試模式中getHandlerAdapter()方法在中的:
          handler、adapter、this.handlerAdapters


          以下是拷貝的結果:
          handler

          handler={HandlerMethod@4792} "com.bjpowernode.controller.MyController#doSome()"
           logger={LogAdapter$JavaUtilLog@4858} 
           bean={MyController@4859} 
           beanFactory={DefaultListableBeanFactory@4847} "org.springframework.beans.factory.support.DefaultListableBeanFactory@56b5a4c3: defining beans [myController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; root of factory hierarchy"
           beanType={Class@3782} "class com.bjpowernode.controller.MyController"
           method={Method@4860} "public org.springframework.web.servlet.ModelAndView com.bjpowernode.controller.MyController.doSome()"
           bridgedMethod={Method@4860} "public org.springframework.web.servlet.ModelAndView com.bjpowernode.controller.MyController.doSome()"
           parameters={MethodParameter[0]@4861} 
           responseStatus=null
           responseStatusReason=null
           resolvedFromHandlerMethod={HandlerMethod@4863} "com.bjpowernode.controller.MyController#doSome()"
           interfaceParameterAnnotations=null
           description="com.bjpowernode.controller.MyController#doSome()"
          

          adapter

          adapter={RequestMappingHandlerAdapter@4827} 
           customArgumentResolvers=null
           argumentResolvers={HandlerMethodArgumentResolverComposite@4833} 
           initBinderArgumentResolvers={HandlerMethodArgumentResolverComposite@4834} 
           customReturnValueHandlers=null
           returnValueHandlers={HandlerMethodReturnValueHandlerComposite@4835} 
           modelAndViewResolvers=null
           contentNegotiationManager={ContentNegotiationManager@4836} 
           messageConverters={ArrayList@4837}  size=4
           requestResponseBodyAdvice={ArrayList@4838}  size=0
           webBindingInitializer=null
           taskExecutor={SimpleAsyncTaskExecutor@4839} 
           asyncRequestTimeout=null
           callableInterceptors={CallableProcessingInterceptor[0]@4840} 
           deferredResultInterceptors={DeferredResultProcessingInterceptor[0]@4842} 
           reactiveAdapterRegistry={ReactiveAdapterRegistry@4844} 
           ignoreDefaultModelOnRedirect=false
           cacheSecondsForSessionAttributeHandlers=0
           synchronizeOnSession=false
           sessionAttributeStore={DefaultSessionAttributeStore@4845} 
           parameterNameDiscoverer={DefaultParameterNameDiscoverer@4846} 
           beanFactory={DefaultListableBeanFactory@4847} "org.springframework.beans.factory.support.DefaultListableBeanFactory@56b5a4c3: defining beans [myController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; root of factory hierarchy"
           sessionAttributesHandlerCache={ConcurrentHashMap@4848}  size=0
           initBinderCache={ConcurrentHashMap@4849}  size=0
           initBinderAdviceCache={LinkedHashMap@4850}  size=0
           modelAttributeCache={ConcurrentHashMap@4851}  size=0
           modelAttributeAdviceCache={LinkedHashMap@4852}  size=0
           order=2147483647
           supportedMethods=null
           allowHeader="GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"
           requireSession=false
           cacheControl=null
           cacheSeconds=-1
           varyByRequestHeaders=null
           useExpiresHeader=false
           useCacheControlHeader=true
           useCacheControlNoStore=true
           alwaysMustRevalidate=false
           servletContext={ApplicationContextFacade@4754} 
           logger={LogAdapter$JavaUtilLog@4854} 
           applicationContext={XmlWebApplicationContext@4665} "WebApplicationContext for namespace 'myweb-servlet', started on Tue Mar 02 23:25:35 CST 2021"
           messageSourceAccessor={MessageSourceAccessor@4855} 
          

          this.handlerAdapters

          this.handlerAdapters={ArrayList@4658}  size=4
           0={HttpRequestHandlerAdapter@4810} 
           1={SimpleControllerHandlerAdapter@4820} //XML方式
           2={RequestMappingHandlerAdapter@4827} //注解方式
           3={HandlerFunctionAdapter@4832} 
          

          可以看到找到4個處理器適配器。通過DEBUG模式可以看到,此次取到的處理器適配器HandlerAdapter是:RequestMappingHandlerAdapter

          ha={RequestMappingHandlerAdapter@4827} 
          

          2.3、檢查攔截器Interceptor

          doDispatch()中的關鍵代碼:

          if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          					return;
          				}
          org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
          

          applyPreHandle(processedRequest, response)源碼:

          /**
          	 * Apply preHandle methods of registered interceptors.
          	 * @return {@code true} if the execution chain should proceed with the
          	 * next interceptor or the handler itself. Else, DispatcherServlet assumes
          	 * that this interceptor has already dealt with the response itself.
          	 */
          	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
          		HandlerInterceptor[] interceptors=getInterceptors();
          		if (!ObjectUtils.isEmpty(interceptors)) {
          			for (int i=0; i < interceptors.length; i++) {
          				HandlerInterceptor interceptor=interceptors[i];
          				if (!interceptor.preHandle(request, response, this.handler)) {
          					triggerAfterCompletion(request, response, null);
          					return false;
          				}
          				this.interceptorIndex=i;
          			}
          		}
          		return true;
          	}
          

          2.3、處理器適配器HandlerAdapter執行Handler(Controller)返回ModelAndView

          doDispatch()中的關鍵代碼:

          mv=ha.handle(processedRequest, response, mappedHandler.getHandler());
          

          DEBUG模式調試,是調到了:
          org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
          源碼如下:

          /**
          	 * This implementation expects the handler to be an {@link HandlerMethod}.
          	 */
          	@Override
          	@Nullable
          	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
          			throws Exception {
          
          		return handleInternal(request, response, (HandlerMethod) handler);
          	}
          

          再往下看handleInternal(request, response, (HandlerMethod) handler)方法,
          org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal

          @Override
          	protected ModelAndView handleInternal(HttpServletRequest request,
          			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
          
          		ModelAndView mav;
          		checkRequest(request);
          
          		// Execute invokeHandlerMethod in synchronized block if required.
          		if (this.synchronizeOnSession) {
          			HttpSession session=request.getSession(false);
          			if (session !=null) {
          				Object mutex=WebUtils.getSessionMutex(session);
          				synchronized (mutex) {
          					mav=invokeHandlerMethod(request, response, handlerMethod);
          				}
          			}
          			else {
          				// No HttpSession available -> no mutex necessary
          				mav=invokeHandlerMethod(request, response, handlerMethod);
          			}
          		}
          		else {
          			// No synchronization on session demanded at all...
          			mav=invokeHandlerMethod(request, response, handlerMethod);
          		}
          
          		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
          			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
          				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
          			}
          			else {
          				prepareResponse(response);
          			}
          		}
          
          		return mav;
          	}
          

          注意,handleInternal(request, response, (HandlerMethod) handler)方法的返回值是ModelAndView ,這里就完成了處理器適配器HandlerAdapter執行Handler(Controller)并將結果ModelAndView返回給前端控制器DistepchServlet

          2.4、視圖解析器ViewResolver

          ??接上2.3:前端控制器DistepchServlet接收到處理器適配器HandlerAdapter返回的ModelAndView以后,這里分2種情況:

          • (1)、如果ModelAndView里面是邏輯視圖
            前端控制器DistepchServlet調用視圖解析器ViewResolver通過邏輯視圖查找真正的視圖對象View,并返回給前端控制器DistepchServlet。
          • (2)、如果ModelAndView里面是非邏輯視圖:
            如:MappingJackson2JsonView(把當前數據轉為為JSON數據,并不需要對視圖邏輯名稱進行轉換)

          總結一下:
          視圖解析器ViewResolver接口主要作用是解析前端控制器DispatcherServlet傳遞的邏輯視圖名,并將解析結果的真正的視圖對象View傳回給前端控制器DispatcherServlet

          ViewResolverd的實現類:


          ViewResolver的UML:

          2.5、視圖View

          2.5.1、視圖對象的作用

          • (1)、將控制器返回的數據處理渲染,最終返回客戶端展示給用戶,主要就是完成轉發或者是重定向的操作.。
          • (2)、為了實現視圖模型和具體實現技術的解耦(指的是Spring在org.springframework.web.servlet包中定義的抽象View接口),詳見2.5.2View接口圖。
          • (3)、視圖對象View由視圖解析器負責實例化。由于視圖是無狀態(每一次請求都會創建一個新的view對象)的,所以不會有線程安全的問題.

          2.5.2、View接口圖

          2.5.3、View的實現類圖

          2.5.4、View的UML圖

          ![2021022601-20-01-View-uml(hierarchic group layout)](https://gitee.com/chuchq/blogs-gallery/raw/master/images / 2021/2021022601-20-01-View-uml(hierarchic group layout).png)

          2.5.5、常用的View視圖類

          視圖類型

          簡介

          URL視圖資源圖

          InternalResourceView

          將JSP或其他資源封裝成一個視圖。被視圖解析器InternalResourceViewResolver默認使用。

          JstlView

          InternalResourceView的子類。如果JSP中使用了JSTL的國際化標簽,就需要使用該視圖類。

          文檔視圖

          AbstractExcelView

          Excel文檔視圖的抽象類。

          AbstractPdfView

          PDF文檔視圖的抽象類

          報表視圖

          ConfigurableJasperReportsView

          常用的JasperReports報表視圖

          JasperReportsHtmlView

          JasperReportsPdfView

          JasperReportsXlsView

          JSON視圖

          MappingJackson2JsonView

          將數據通過Jackson框架的ObjectMapper對象,以JSON方式輸出

          2.6、其他重要的點

          2.6.1、DispatcherServlet.properties

          DispatcherServlet.properties文件是在SpringMVC架包中:


          DispatcherServlet.properties內容:

          # 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.method.annotation.RequestMappingHandlerMapping,\
          	org.springframework.web.servlet.function.support.RouterFunctionMapping
          
          org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
          	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
          	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
          	org.springframework.web.servlet.function.support.HandlerFunctionAdapter
          
          
          org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
          	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
          

          SpringMVC為什么能加載不同處理器映射器HandlerMapping、處理器適配器handlerAdapter,就是因為框架配置了這個DispatcherServlet.properties文件。

          轉載于:https://www.cnblogs.com/chuchq/p/14489716.html

          一篇[79、Spring MVC]

          下一篇[80、使用 Spring 安全進行測試]

          . 為啥要學 SpringMVC?

          1.1 SpringMVC 簡介

          在學習 SpringMVC 之前我們先看看在使用 Servlet 的時候我們是如何處理用戶請求的:

          1. 配置web.xml
          <?xml version="1.0" encoding="UTF-8"?>
          <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
                   version="4.0">
          
              <!--將 Servlet接口實現類交給Tomcat管理-->
              <servlet>
                  <servlet-name>userServlet</servlet-name> <!--Servlet接口實現類名稱-->
                  <servlet-class>com.xxl.controller.UserServlet</servlet-class><!--聲明servlet接口實現類類路徑-->
              </servlet>
              <servlet-mapping>
                  <servlet-name>userServlet</servlet-name>
                  <url-pattern>/user</url-pattern>  <!--設置Servlet類的請求路徑,必須以 / 開頭-->
              </servlet-mapping>
          </web-app>
          1. 繼承 HttpServlet,實現 doGet 和 doPost 方法
          public class UserServlet extends HttpServlet {
              @Override
              protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                 // 這里用來處理用戶的 get 請求
                 System.out.println("哈哈哈哈哈哈我頭上有注解");
              }
          
              @Override
              protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                  // 這里用來處理用戶的 post 請求
                  System.out.println("接收到用戶的 post 請求");
              }
          }
          1. 獲取請求參數
          
          String name=request.getParameter("name");
          
          String age=request.getParameter("age");
          ....

          一頓操作下來,我們發現用 Servlet 處理用戶的請求也太麻煩了吧。

          每個 Servlet 都要繼承 HttpServlet、重寫兩個方法,我們需要寫一堆 getParameter() 方法來獲取請求參數,而且還要做數據類型的轉換。

          那有沒有一個別人封裝好的工具或者是框架讓我少寫這些重復性的代碼呢?

          SpringMVC閃亮登場。

          SpringMVC 是一種輕量級的、基于 MVC 的 Web 層應用框架,它屬于 Spring 框架的一部分。SpringMVC 說白了就是對 Servlet 進行了封裝,方便大家使用。

          1.2 SpringMVC 優點

          • 天生與 Spring 集成
          • 支持 Restful 風格開發
          • 便于與其他視圖技術集成,例如 theamleaf、freemarker等
          • 強大的異常處理
          • 對靜態資源的支持

          總之就是好用!


          2. HelloWorld

          這里我們先來開發一個基于 SpringMVC 的程序,感受一下 SpringMVC 的迷人特性。

          • 開發工具:IDEA
          • 構建工具:Maven

          2.1 新建基于 Maven 的 web 項目


          2.2 加入依賴

          <!-- servlet -->
          <dependency>
              <groupId>javax.servlet</groupId>
              <artifactId>javax.servlet-api</artifactId>
              <version>4.0.1</version>
          </dependency>
          <!--springmvc -->
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-webmvc</artifactId>
              <version>5.2.8.RELEASE</version>
          </dependency>
          <!-- junit -->
          <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>4.11</version>
              <scope>test</scope>
          </dependency>

          2.3 創建中央調度器

          DispatcherServlet 是 SpringMVC 的中央調度器,它主要負責加載 SpringMVC 的配置。

          從它的名字來看,他也屬于一個 Servlet,遵守 Servlet 規范。所以我們需要在 web.xml 中創建 DispatcherServlet。

          web.xml

          <!DOCTYPE web-app PUBLIC
                  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
                  "http://java.sun.com/dtd/web-app_2_3.dtd" >
          <web-app>
              <!--servlet: DispatcherServlet-->
              <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:springmvc.xml</param-value>
                  </init-param>
                  <load-on-startup>1</load-on-startup>
              </servlet>
              <servlet-mapping>
                  <servlet-name>SpringMVC</servlet-name>
                  <url-pattern>/</url-pattern>
              </servlet-mapping>
          </web-app>


          2.4 創建 SpringMVC 的配置文件

          這里我們在 src/resources 資源目錄下創建 SpringMVC的配置文件 springmvc.xml,該文件名字可以任意命名。

          springmvc.xml:

          <?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>


          2.5 創建處理請求的處理器

          TestController:

          /**
          * @Desc: 處理器
          * @Author: 知否技術
          * @date: 下午7:39 2022/4/29
          */
          @Controller
          public class TestController {
            @RequestMapping("/hello")
            public ModelAndView sayHello() {
                ModelAndView mv=new ModelAndView();
                mv.addObject("msg", "你好啊,李銀河,我是王小波。");
                mv.setViewName("/hello.jsp");
                return mv;
            }
          }

          2.6 聲明組件掃描器

          我們在 springmvc.xml 中注冊組件掃描器,


          <!-- 掃描組件,將加上@Controller注解的類作為SpringMVC的控制層 -->
          <context:component-scan base-package="com.zhifou"></context:component-scan>


          2.7 創建 jsp 頁面


          2.8 配置視圖解析器

          我們需要在 springmvc.xml 中配置請求文件的路徑和文件后綴。

          <!-- 
              配置視圖解析器
              作用:將prefix + 視圖名稱 + suffix 確定最終要跳轉的頁面
             -->
          <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/view/"></property>
            <property name="suffix" value=".jsp"></property>
          </bean>


          2.9 修改處理器請求文件路徑

          因為我們指定了請求文件的后綴是 .jsp,所以這里可以省略。


          2.10 配置 tomcat,啟動項目測試






          3. 請求

          3.1 @RequestMapping

          @RequestMapping 注解用來指定處理哪些 URL 請求。

          3.1.1 注解常用屬性

          1. value

          value 用來表示請求的 url,可以省略不寫

          @RequestMapping(value="/hello")

          簡寫

          @RequestMapping("/hello")
          1. method

          method 用來表示請求方式,不寫的話默認是 GET 請求。常用的請求方式:

          POST、GET、PUT、DELETE

          如果使用 method 屬性,不能省略 value 屬性。

          @RequestMapping(value="/hello",method=RequestMethod.GET)
            public ModelAndView sayHello() {
                ModelAndView mv=new ModelAndView();
                mv.addObject("message", "你好啊,李銀河,我是王小波。");
                mv.setViewName("/hello");
                return mv;
          }

          3.1.2 標記位置

          1. 標記在類上面

          一個系統包含很多模塊,例如用戶、商品、訂單等模塊。

          我們需要為不同的模塊定義不同的類,然后在類上面添加 @RequestMapping 注解,表示這個模塊下面統一的請求路徑:例如:

          // 用戶操作控制器
          @Controller
          @RequestMapping("/user")
          public class UserController {
          
          }
          // 訂單操作控制器
          @Controller
          @RequestMapping("/order")
          public class OrderController {
          
          }
          ...
          1. 標記在方法上面

          每個模塊都有很多方法,例如增刪改查等。所以我們一般會在相應的方法上面添加 @RequestMapping 注解,表示請求這個模塊下的某個方法,例如:

          @Controller
          @RequestMapping("/user")
          public class UserController {
           @RequestMapping("/list")
              public Object list() {
                return null;
              }
              @RequestMapping(value="/add",method=RequestMethod.POST)
              public Object add() {
                  return null;
              }
              @RequestMapping(value="/update",method=RequestMethod.POST)
              public Object update() {
                  return null;
              }
              @RequestMapping(value="/delete",method=RequestMethod.DELETE)
              public Object delete() {
                  return null;
              }
          }

          所以當我們獲取用戶列表信息時,我們請求的后臺接口的 url 就是:

          ip地址:端口號/項目名/uset/list
          //例如:
          localhost:8080/ems/user/list

          3.1.3 @RequestMapping 的縮寫注解

          方法上關于不同請求方式的注解都比較長,例如:

           @RequestMapping(value="/add",method=RequestMethod.POST)

          SpringMVC 為我們提供了簡化寫法:

          GET請求:

          @GetMapping("/list")

          POST 請求:

          @PostMapping("/login")

          DELETE 請求:

          @DeleteMapping("/delete/{id}")

          PUT 請求

          @PutMapping("/update")

          3.2 接收請求參數

          3.2.1 接收多個參數

          @PostMapping("/login") public Result<User> login(String username,String password) { User user=userService.login(username, password); return Result.success(user); }

          3.2.2 實體類作為參數

          Spring MVC 會按請求參數名和 POJO 屬性名進行自動匹配,自動為該對象填充屬性值。

          @PostMapping("/login") public Result<User> login(User user){ User user=userService.login(user.getUsername(), user.getPassword); return Result.success(user); }

          3.2.3 @RequestParam 注解

          使用 @RequestParam 可以把請求參數傳遞給請求方法。

          屬性:

          1. value:參數名
          2. required:是否必須。默認為 true, 表示請求參數中必須包含對應的參數,若不存在,將拋出異常
          3. defaultValue: 默認值,當沒有傳遞參數時使用該值
          @PostMapping("/login")
          public Result<User> login(@RequestParam String username, @RequestParam String password) {
              User user=userService.login(username, password);
              return Result.success(user);
          }

          3.2.4 @PathVariable 注解

          通過 @PathVariable 可以將 URL 中占位符參數綁定到控制器處理方法的入參中。

          URL 中的 {xxx} 占位符可以通過 @PathVariable("xxx")綁定到操作方法的入參中。

          @DeleteMapping(value="/delete/{id}")
          public Result<?> delete(@PathVariable("id") int id) {
              userService.remove(id);
              return Result.success();
          }

          3.3 解決中文亂碼

          請求參數如果含有中文,會出現中文亂碼問題。我們可以通過在 web.xml 中配置字符過濾器來解決中文亂碼問題。

          <filter>
             <filter-name>encodingFilter</filter-name>
             <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
             <init-param>
                      <param-name>encoding</param-name>
                      <param-value>UTF-8</param-value>
             </init-param>
             <init-param>
                      <param-name>forceEncoding</param-name>
                      <param-value>true</param-value>
             </init-param>
          </filter>
          <filter-mapping>
             <filter-name>encodingFilter</filter-name>
             <url-pattern>/*</url-pattern>
          </filter-mapping>

          注:filter 標簽要在 servlet 標簽之上

          4. 響應

          SpringMVC 提供了以下幾種方式處理響應數據。

          4.1 返回 String

          1. SpringMVC 的視圖解析器將返回的字符串轉換為視圖(頁面)。
          @GetMapping("/hello")
          public String toHello() {
              return "/hello";
          }
          1. 重定向:

          使用 redirect 關鍵字可以重定向到其他頁面。

          @GetMapping("/hello")
          public String toHello() {
              return "redirect:/hello";
          }

          4.2 返回 ModelAndView

          控制器在處理完用戶的請求之后,如果既想跳轉頁面,又想傳遞數據,可以使用 ModelAndView 對象。

          @RequestMapping(value="/hello",method=RequestMethod.GET)
            public ModelAndView sayHello() {
                ModelAndView mv=new ModelAndView();
                mv.addObject("message", "你好啊,李銀河,我是王小波。");
                mv.setViewName("/hello");
                return mv;
          }

          4.3 返回 Model

          Mode 對象也可以在跳轉頁面的時候傳遞數據。

          @GetMapping("/print")
          public String print(Model model) {
              model.addAttribute("msg", "祝大家五一快樂!");
              return "/hello";
          }

          4.4 返回 json

          1. 控制器的方法也可以返回 Object 對象,但返回的對象不是作為視圖出現的,而是作為頁面上顯示的數據。

          返回對象,需要使用 @ResponseBody 注解將對象轉換為 json 格式的數據響應給瀏覽器。

          @GetMapping(value="/print", produces="application/json;charset=utf8")
          @ResponseBody
          public Object print() {
              User user=new User(11111L, "張無忌", 12);
              String json=JSONUtil.toJsonStr(user);
              return json;
          }



          如果遇到中文亂碼的問題,我們可以在 @GetMapping 注解里面設置 produces 屬性。

          1. @RestController 注解

          @RestController 注解是 @Controller 和 @ResponseBody 注解的組合注解,表示這個控制器中所有的方法全都返回 json 格式的數據。

          4.5 返回 void

          有時候我們不需要跳轉頁面,也不需要轉發數據,我們只是希望瀏覽器能夠渲染數據(例如后臺向前臺輸出驗證碼)。這時候可以使用 HttpServletResponse 向瀏覽器輸出數據。

          @GetMapping("/print")
          public void print(HttpServletResponse response) throws IOException {
              /*解決響應數據中文亂碼問題*/
              response.setContentType("text/html;charset=utf8");
              PrintWriter writer=response.getWriter();
              writer.print("祝大家五一快樂");
              writer.flush();
              writer.close();
          }

          5. 訪問靜態

          我們在 web.xml 中配置 url-pattern 為 /,當我們請求項目的靜態資源的時候,SpringMVC 的中央調度器會先根據處理器映射器尋找相應的處理器,結果沒找到,所以請求靜態資源會報 404。

          我們可以使用 mvc:resources 標簽來解決無法訪問靜態資源的問題。

          但是 DispatcherServlet 的映射路徑為 /,而 Tomcat 默認的 Servlet 的映射路徑也為/,所以 DispatcherServlet 會覆蓋 Tomcat 中默認的 Servlet,去處理除 jsp 之外的所有資源,導致靜態資源無法被訪問。

          所以這里還需要結合另外一個標簽使用:

          <!-- static/**表示該目錄下所有文件-->
          <mvc:resources mapping="/static/**" location="/static/"/>
          <mvc:annotation-driven/>

          這里我們測試訪問項目中的照片:

          @GetMapping("/hello")
          public String toHello() {
              return "/hello";
          }



          6. 全局異常處理

          在開發過程中我們會遇到各種各樣的異常,我們會有兩種處理方式:

          1. try-catch 捕獲
          2. throws 拋出異常

          但是用戶不希望看到一堆異常信息:

          即便是程序出錯了,他們也希望看到一些看得懂的錯誤信息,例如:

          {
            code: 500,
            message: '該商品信息不存在,請聯系管理員'
          }

          所以我們需要搞一個全局異常處理器先捕獲這些錯誤信息,然后返回給用戶統一的結果信息。

          1. 創建全局異常處理器
          @RestControllerAdvice
          public class GlobalExceptionHandler {
              //自定義異常處理器
              @ExceptionHandler(CustomException.class)
              public Result CustomExceptionHandler(Exception e) {
                  
                  return Result.fail(500,e.getMessage());
              }
          }

          @RestControllerAdvice:表示這個類是全局異常處理器,返回的格式是 json 格式。

          @ExceptionHandler:捕獲的異常類型

          1. 創建自定義異常
          /**
           * @Desc: 自定義異常
           * @Author: 知否技術
           * @date: 下午8:05 2022/5/3
           */
          public class CustomException  extends  RuntimeException{
          
              public CustomException(String message) {
                  super(message);
              }
          }

          因為大部分異常是運行時異常,所以這里自定義的異常繼承了運行時異常。

          1. 封裝返回的統一結果類
          /**
           * @Desc: 統一結果類
           * @Author: 知否技術
           * @date: 下午9:02 2022/5/3
           */
          public class Result implements Serializable {
          
              private int code;
          
              private String message;
          
              private Object data;
          
              private Result(String message) {
                  this.code=200;
                  this.message=message;
              }
          
              private Result(String message, Object data) {
                  this.code=200;
                  this.message=message;
                  this.data=data;
              }
          
              private Result(int code, String message) {
                  this.code=code;
                  this.message=message;
              }
          
              public static Result success(String message) {
                  return new Result(message);
              }
          
              public static Result success(String message, Object data) {
                  return new Result(message, data);
              }
          
              public static Result fail(int code, String message) {
                  return new Result(code, message);
              }
          
              public int getCode() {
                  return code;
              }
          
              public void setCode(int code) {
                  this.code=code;
              }
          
              public String getMessage() {
                  return message;
              }
          
              public void setMessage(String message) {
                  this.message=message;
              }
          
              public Object getData() {
                  return data;
              }
          
              public void setData(Object data) {
                  this.data=data;
              }
          }

          4.測試

          @GetMapping("/hello")
          @ResponseBody
          public Result toHello() {
              throw new CustomException("臥槽!!出錯了");
          }

          SpringMVC 中的攔截器主要是用來攔截用戶的請求,并進行相關的處理。

          實現攔截器:

          1. 創建攔截器
          /**
           * @Desc:
           * @Author: 知否技術
           * @date: 下午2:09 2022/5/3
           */
          public class MyInterceptor implements HandlerInterceptor {
          
              // 在處理器中方法執行之前執行
              @Override
              public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                 
                  return false;
              }
              // 在處理器中方法執行之后執行
              @Override
              public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
          
              }
          }
          1. 配置攔截器
          <!-- 配置攔截器 -->
          <mvc:interceptors>
              <mvc:interceptor>
                  <!--攔截所有請求-->
                  <mvc:mapping path="/**"/>
                  <!--排除請求-->
                  <mvc:exclude-mapping path="/user/login"/>
                  <bean class="com.zhifou.interceptor.MyInterceptor"></bean>
              </mvc:interceptor>
          </mvc:interceptors>

          測試:


          8. SpringMVC 執行流程


          1.Tomcat 服務器啟動的時候會立即創建 DispatcherServlet(中央調度器),同時會創建 SpringMVC 容器。

          2.SpringMVC 容器初始化的時候會先根據配置文件中的組件掃描器先掃描一下哪些類上面有 @Controller 注解,并將這些類作為處理器類。

          然后通過 @RequestMapping 注解生成對應的映射關系。這些對應關系由處理器映射器管理。

          3.當收到用戶的請求,中央調度器將請求轉發給處理器映射器。

          4.處理器映射器根據用戶請求的 URL 從映射關系中找到處理該請求的處理器,然后封裝成處理器執行鏈返回給中央處理器。

          5.中央調度器根據處理器執行鏈中的處理器,找到能夠執行該處理器的處理器適配器,處理器適配器調用執行處理器。

          6.處理器將處理結果及要跳轉的視圖封裝到 ModelAndView 中,并將其返回給處理器適配器。

          7.處理器適配器直接將結果返回給中央調度器,中央調度器調用視圖解析器,將 ModelAndView 中的視圖名稱封裝為視圖對象。

          8.視圖解析器將封裝了的視圖對象返回給中央調度器,中央調度器調用視圖對象,填充數據,生成響應對象。

          9.中央調度器將結果響應給瀏覽器。

          原文鏈接:https://juejin.cn/post/7093670296888016933


          主站蜘蛛池模板: 亚洲精品色播一区二区| 国产婷婷色一区二区三区| 中文字幕在线看视频一区二区三区 | 国产传媒一区二区三区呀| 日韩一区二区a片免费观看| 理论亚洲区美一区二区三区| 久久无码一区二区三区少妇| 亚洲不卡av不卡一区二区| 福利电影一区二区| 色国产精品一区在线观看| 韩国一区二区三区| 末成年女A∨片一区二区| 日韩十八禁一区二区久久| 亚洲国产精品一区二区三区在线观看| 激情爆乳一区二区三区| 秋霞无码一区二区| 国产亚洲情侣一区二区无| 欧美日韩精品一区二区在线视频 | 国产在线精品观看一区| 波多野结衣免费一区视频| 精品国产一区二区三区四区| 国产一区二区视频免费| 日本免费一区二区在线观看 | 国产一区二区三区免费观在线| 亚洲日韩AV无码一区二区三区人| 国产一区二区三区在线影院 | 中文字幕日韩丝袜一区| 亚洲永久无码3D动漫一区| 蜜臀AV无码一区二区三区| 人妻激情偷乱视频一区二区三区| 精品国产一区二区三区久久蜜臀 | 精品亚洲综合在线第一区| 亚洲AV永久无码精品一区二区国产| 亲子乱av一区区三区40岁| 久久er99热精品一区二区 | 99精品一区二区免费视频| 久久久久久免费一区二区三区| 亚洲一区二区三区在线观看精品中文| 中文字幕在线一区二区在线| 久久AAAA片一区二区| 国产aⅴ精品一区二区三区久久 |