整合營銷服務(wù)商

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

          免費(fèi)咨詢熱線:

          SpringMVC概述和基礎(chǔ)知識詳解

          SpringMVC概述和基礎(chǔ)知識詳解

          . SpringMVC概述及原理

          1. SpringMVC是什么

          Spring MVC屬于SpringFrameWork的后續(xù)產(chǎn)品,已經(jīng)融合在Spring Web Flow里面。Spring 框架提供了構(gòu)建 Web 應(yīng)用程序的全功能 MVC 模塊。使用 Spring 可插入的 MVC 架構(gòu),從而在使用Spring進(jìn)行WEB開發(fā)時,可以選擇使用Spring的Spring MVC框架或集成其他MVC開發(fā)框架,如Struts1(現(xiàn)在一般不用),[Struts 2](https://baike.baidu.com/item/Struts 2/2187934)(一般老項目使用)等。

          SpringMVC 已經(jīng)成為目前最主流的 MVC 框架之一, 從 Spring3.0 的發(fā)布, 就已全面超越 Struts2,成為最優(yōu)秀的 MVC 框架。它通過一套注解,讓一個簡單的 Java 類成為處理請求的控制器,而無須實現(xiàn)任何接口。同時它還支持RESTful 編程風(fēng)格的請求。

          2. MVC和三層架構(gòu)

          MVC模式(Model-View-Controller)是軟件工程中的一種軟件架構(gòu)模式,把軟件系統(tǒng)分為三個基本部分:模型(Model)、視圖(View)和控制器(Controller)。


          控制器(Controller):Servlet,控制器主要處理用戶的請求

          視圖(View):HTML, JSP, 前端框架

          模型(Model):邏輯業(yè)務(wù)程序(后臺的功能程序), Service, Dao, JavaBean

          JavaWEB發(fā)展史

          Model1

          所有的業(yè)務(wù)邏輯交給jsp單獨(dú)處理完成,一個web項目只存在DB層和JSP層,所有的東西都耦合在一起,對后期的維護(hù)和擴(kuò)展極為不利。

          Model2 第一代

          JSP Model2有所改進(jìn),把業(yè)務(wù)邏輯的內(nèi)容放到了JavaBean中,而JSP頁面負(fù)責(zé)顯示以及請求調(diào)度的工作。雖然第二代比第一代好了些,但JSP還是把view和controller的業(yè)務(wù)耦合在一起。依然很不理想。

          Model2 第二代(三層架構(gòu))

          Model2第二代就是現(xiàn)在大力推廣的和使用的mvc,將一個項目劃分為三個模塊,各司其事互不干擾,既解決了jsp所形成的耦合性,又增加了邏輯性、業(yè)務(wù)性以及復(fù)用性和維護(hù)性

          表示層(web層):包含JSP,Servlet等web相關(guān)的內(nèi)容

          業(yè)務(wù)邏輯層(Service):處理業(yè)務(wù),不允許出現(xiàn)servlet中的request、response。

          數(shù)據(jù)層(Data Access Object):也叫持久層,封裝了對數(shù)據(jù)庫的訪問細(xì)節(jié)。

          其中 web層相當(dāng)于mvc中的view+controller,Service層和dao層相當(dāng)于mvc中的model。

          3. SpringMVC 在三層架構(gòu)的位置

          MVC模式:(Model-View-Controller):為了解決頁面代碼和后臺代碼的分離.


          二. 入門示例

          1. 配置流程-基于XML的配置

          1.1.搭建普通Maven項目



          使用插件將項目轉(zhuǎn)換為web項目

          轉(zhuǎn)換成功:

          查看是否生成webapp目錄和maven項目打包方式是否變?yōu)閣ar

          添加SpringMVC依賴

          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-webmvc</artifactId>
              <version>5.0.6.RELEASE</version>
          </dependency>

          查看關(guān)系依賴圖

          1.2.在web.xml配置核心控制器

          <?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_2_5.xsd"
          	version="2.5">
          
          	<!--spring核心(前端控制器)-->
          	<servlet>
          		<servlet-name>dispatcher</servlet-name>
          		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          	</servlet>
          	<servlet-mapping>		<!--只有*.form后綴的請求才會進(jìn)入springmvc-->
          		<servlet-name>dispatcher</servlet-name>
          		<url-pattern>*.form</url-pattern>
          	</servlet-mapping>
          </web-app>

          1.3.創(chuàng)建一個Spring的配置文件

          <?xml version="1.0" encoding="UTF-8"?>
          <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/beans
                 http://www.springframework.org/schema/beans/spring-beans.xsd
                 http://www.springframework.org/schema/context
                 http://www.springframework.org/schema/context/spring-context.xsd
                 http://www.springframework.org/schema/mvc
                 http://www.springframework.org/schema/mvc/spring-mvc.xsd">
          
              <context:component-scan base-package="com.dfbz"/>
          
              <!--開啟springmvc注解支持-->
              <mvc:annotation-driven/>
          </beans>

          mvc:annotation-driven 說明

          在 SpringMVC 的各個組件中,處理器映射器、處理器適配器、視圖解析器稱為 SpringMVC 的三大組件。

          在springmvc早期版本中需要我們自己加載springmvc的三大組件(現(xiàn)在我們使用的版本5.0.6會自動加載這三大組件)

          **處理器映射器:RequestMappingHandlerMapping **

          處理器適配器:RequestMappingHandlerAdapter

          處理器解析器:ExceptionHandlerExceptionResolver

          在早期的版本中使用 <mvc:annotation-driven> 自動加載這三大組件,但是高版本的不需要<mvc:annotation-driven>來加載

          同時它還提供了:數(shù)據(jù)綁定支持,@NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,讀寫XML的支持(JAXB,讀寫JSON的支持(Jackson)。我們處理響應(yīng)ajax請求時,就使用到了對json的支持(配置之后,在加入了jackson的core和mapper包之后,不寫配置文件也能自動轉(zhuǎn)換成json)

          springmvc配置文件說明

          注意:默認(rèn)的Spring配置文件放在WEB-INF下,名為{servlet-name}-servlet.xml

          {servlet-name}指的是,核心控制器配置的名字

          如:dispatcherServlet-servlet.xml

          當(dāng)請求被springmvc處理時,springmvc會去默認(rèn)路徑下加載xxxx-servlet.xml核心配置文件

          但是我們在開發(fā)中一般都是把配置文件寫在classes下的,我們可以在web.xml中設(shè)置springmvc配置文件的路徑

          <!--spring核心(前端控制器)-->
          <servlet>
          	<servlet-name>dispatcher</servlet-name>
          	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          	<!--配置springmvc配置文件的位置-->
          	<init-param>
          		<param-name>contextConfigLocation</param-name>
          		<param-value>classpath:dispatcher-servlet.xml</param-value>
          	</init-param>
          </servlet>
          <servlet-mapping>		<!--只有*.form后綴的請求才會進(jìn)入springmvc-->
          	<servlet-name>dispatcher</servlet-name>
          	<url-pattern>*.form</url-pattern>
          </servlet-mapping>

          1.4.創(chuàng)建一個業(yè)務(wù)控制器

          package com.dfbz.controller;
          
          import org.springframework.stereotype.Controller;
          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.servlet.ModelAndView;
          
          @Controller
          public class HelloController{
          
              //代表此方法的訪問路徑為/hello.form
              @RequestMapping("/hello.form")
              public ModelAndView hello(){
                  
                  ModelAndView mav=new ModelAndView();
                  mav.addObject("msg","小標(biāo)");
                  mav.setViewName("/hello.jsp");
          
                  return mav;
              }
          
          }

          1.5.創(chuàng)建一個返回的視圖頁面

          <%@ page contentType="text/html;charset=UTF-8" language="java" %>
          <html>
          <head>
             <title>Title</title>
          </head>
          <body>
             ${msg },歡迎您!
          </body>
          </html>

          1.7. SpringMVC的工作流程

          1、客戶端發(fā)送請求給前端控制器(DispatcherServlet)

          2、dispatcherServlet接收到請求調(diào)用HandlerMapping處理器映射器

          3、處理器映射器根據(jù)請求的url找對應(yīng)的處理器,生成處理器對象(handler)返回

          4、dispatchServlet將handler傳入處理器適配器執(zhí)行

          5、處理器適配器執(zhí)行handler

          6、執(zhí)行完成最終封裝一個ModelAndView

          7、將ModelAndView返回給前端控制器

          8、前端控制器將請求的路徑交給視圖解析器進(jìn)行解析

          9、最終封裝一個View對象給dispatcherServlet,此View對象封裝了響應(yīng)參數(shù)

          10、JSP頁面渲染數(shù)據(jù)

          11、響應(yīng)客戶端

          1.8 SpringMVC源碼分析

          我們知道SpringMVC實質(zhì)上是對servlet進(jìn)行封裝,讓我們的開發(fā)更加簡便

          1. 準(zhǔn)備工作

          我們知道springmvc在工作開始之前會加載默認(rèn)的處理器映射器、處理器適配器、處理器解析器等

          可以在spring-webmvc-5.0.6.RELEASE.jar源碼包下查看DispatcherServlet.properties文件看有哪些處理器是springmvc默認(rèn)加載的

          2. 查看DispatcherServlet的繼承體系:

          我們發(fā)現(xiàn)DispatcherServlet最終還是繼承與HttpServlet,那么我們就直接找service方法吧!

          經(jīng)打斷點(diǎn)發(fā)現(xiàn),最終會走向DispacherServlet的doDispacher方法!

          此時請求進(jìn)入DispatcherServlet,按照我們畫圖分析的結(jié)果應(yīng)該是把請求交給處理器映射器HandlerMapping最終返回一個Handler

          3. 查看HandlerMapping接口:

          4. 尋找HandlerMapping實現(xiàn)類:


          接下來進(jìn)入處理器適配器HandlerAdapter執(zhí)行handler最終返回一個ModelAndView

          5. 查看HandlerAdapter接口:

          6. 查看HandlerAdapter實現(xiàn)類:


          然后請求交給視圖解析器進(jìn)行解析最終返回一個View對象

          7. 查看View接口:

          8. 查看View實現(xiàn)類:


          9. 查看View信息:

          1.9.核心控制器

          SpringMVC自帶了攔截器請求的核心控制器.所以就可以在請求過來的時候,直接啟動Spring框架

          默認(rèn)情況下,Spring容器是在核心控制器DispatcherServlet獲得請求后才啟動的.

          能不能網(wǎng)站啟動的時候,Spring容器就立刻啟動.

          <servlet>
              <servlet-name>dispatcher</servlet-name>
              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
              <load-on-startup>1</load-on-startup><!-- 啟動服務(wù)器的時候,Servlet就創(chuàng)建實例 --> 
          </servlet>

          三. Controller方法返回值

          返回值


          返回String

          可以返回視圖字符串,解析器會自動解析

          @RequestMapping("/demo3_1")
          public String demo3_1(){
          
              return "/success.jsp";           //直接返回字符串
          }

          返回ModelAndView

          ModelAndView是SpringMVC幫我們提供的,即"模型和視圖"

          @RequestMapping("/demo3_2")
          public ModelAndView demo3_2(){          //返回ModelAndView
              ModelAndView mav=new ModelAndView();
              mav.setViewName("success.jsp");
              mav.addObject("username","東方標(biāo)準(zhǔn)");
              return mav;
          }

          返回void

          一般用于使用原生的Servlet對象或者ajax請求

          @RequestMapping("/demo3_3")		//返回void(一般用于ajax)
          public void demo3_2(HttpServletResponse response) throws IOException {          
          
              response.setContentType("text/html;charset=utf8");
              response.getWriter().write("東方標(biāo)準(zhǔn)");
          
          }

          轉(zhuǎn)發(fā)和重定向


          SpringMVC提供了一個 String 類型返回值之后會自動解析forward、redirect等特殊字符串

          視圖解析器配置的前綴和后綴解析不支持forward、redirect

          :forward:代表轉(zhuǎn)發(fā)request.getRequestDispatcher(url).forward(request,response)

          :redirect:代表重定向response.sendRedirect(url)

          @RequestMapping("/demo3_4")
          public String demo3_4(Model model)  {          //轉(zhuǎn)發(fā)
          
              System.out.println("執(zhí)行啦!");
              model.addAttribute("username","東方標(biāo)準(zhǔn)");
              return "forward:/success.jsp";
          }
          @RequestMapping("/demo3_5")
          public String demo3_5(HttpSession session)  {          //重定向
              session.setAttribute("password","admin");
              System.out.println("執(zhí)行啦!");
          
              return "redirect:/success.jsp";
          }

          四. 映射路徑-@RequestMapping

          1. 探究RequestMapping

          注解式處理器映射器,對類中標(biāo)記了@ResquestMapping的方法進(jìn)行映射。根據(jù)@ResquestMapping定義的url匹配@ResquestMapping標(biāo)記的方法,匹配成功返回HandlerMethod對象給前端控制器。HandlerMethod對象中封裝url對應(yīng)的方法Method。

          @Target({ElementType.METHOD, ElementType.TYPE})
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Mapping
          public @interface RequestMapping {
          }

          命名空間

          按照我們傳統(tǒng)的url寫法不能很好的規(guī)定請求路徑,即不能按照業(yè)務(wù)來區(qū)分請求

          例如現(xiàn)在user需要定義一個findById,goods也需要定義一個findById,此時就會有沖突,針對這種現(xiàn)象我們可以在類上定義一個命名空間,即在類上使用@RequestMapping注解,類似于一級目錄,以后訪問此類下的任意資源都需要加上此目錄

          類上

          請求 URL 的第一級訪問目錄。此處不寫的話,就相當(dāng)于應(yīng)用的根目錄。 寫的話需要以/開頭。它出現(xiàn)的目的是為了使我們的 URL 可以按照模塊化管理:

          例如:

          user模塊:

          /user/register

          /user/update

          /user/findById

          goods模塊:

          /goods/add

          /goods/update

          /goods/findById

          映射路徑的有三種:標(biāo)準(zhǔn)的映射路徑,帶通配符的映射路徑,帶路徑變量的映射路徑

          方法上

          請求 URL 的第二級訪問目錄。

          屬性:

          • value:用于指定請求的 URL。 它和 path 屬性的作用是一樣的。
          • method:用于指定請求的方式。
          • params:用于指定限制請求參數(shù)的條件。 它支持簡單的表達(dá)式。 要求請求參數(shù)的 key 和 value 必須和配置的一模一樣。

          params={“userId”},表示請求參數(shù)必須有 userId,區(qū)分大小寫

          params={“userId!=20”},表示請求參數(shù)中 id不能是 20。可以不攜帶userId參數(shù),區(qū)分大小寫

          headers:用于指定限制請求消息頭的條件。

          package com.dfbz.controller;
          
          import org.springframework.stereotype.Controller;
          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.bind.annotation.RequestMethod;
          import org.springframework.web.servlet.ModelAndView;
          
          @Controller
          public class HelloController{
          
              public HelloController(){
                  System.out.println("Hello創(chuàng)建了");
              }
              /*
              	代表此方法的訪問路徑為/hello.form,如果攜帶userId參數(shù)那么參數(shù)必須不能等于1
              	并且提交方式一定要為POST
              */
              @RequestMapping(value="/hello.form",params="userId!=1",method=RequestMethod.POST)
              public ModelAndView hello(){
          
                  ModelAndView mav=new ModelAndView();
                  mav.addObject("msg","小標(biāo)");
                  mav.setViewName("/hello.jsp");
          
                  return mav;
              }
          
          }

          小結(jié):

          1.參數(shù)必須包括:params={“username”,“password”}

          2.參數(shù)不能包括:params={"!userid"}

          3參數(shù)值必須是指定的值:params={“username=zhangsan”})

          4.參數(shù)值必須不是指定的值:params={“userid!=123”})

          2.3. RESTFUL

          所謂的路徑變量,就是將參數(shù)放在路徑里面,而不是放在?的后面

          如:原get請求方法 /login.mvc?username=’zhangsan’&pwd=’123456’

          路徑變量寫法:

          /zhangsan/123456/login.form

          2.3.1 什么是RESTFUL

          REST(英文:Representational State Transfer,簡稱REST)RESTful是一種軟件架構(gòu)風(fēng)格、設(shè)計風(fēng)格,而不是標(biāo)準(zhǔn),只是提供了一組設(shè)計原則和約束條件。它主要用于客戶端和服務(wù)器交互類的軟件。基于這個風(fēng)格設(shè)計的軟件可以更簡潔,更有層次,更易于實現(xiàn)緩存等機(jī)制。

          2.3.2 RESTFUL示例:


          請求方式有幾種?7種

          jsp、html,只支持get、post。

          基于restful風(fēng)格的url:

          添加

          http://localhost:8080/SpringMVC_01/user

          提交方式: post

          修改

          http://localhost:8080/SpringMVC_01/user

          提交方式:put

          刪除

          http://localhost:8080/SpringMVC_01/user/101

          提交方式:delete

          查詢

          http://localhost:8080/SpringMVC_01/user/102

          提交方式:get

          五. 數(shù)據(jù)綁定

          1. 數(shù)據(jù)綁定是什么

          SpringMVC里面,所謂的數(shù)據(jù)綁定就是將請求帶過來的表單數(shù)據(jù)綁定到執(zhí)行方法的參數(shù)變量中,或?qū)⒎?wù)器數(shù)據(jù)綁定到內(nèi)置對象,傳遞到頁面

          2. 自動綁定的數(shù)據(jù)類型

          2.1 自動綁定數(shù)據(jù)類型

          • 基本數(shù)據(jù)類型:基本數(shù)據(jù)類型+String+包裝類
          • 包裝數(shù)據(jù)類型(POJO):包裝實體類
          • 數(shù)組和集合類型:List、Map、Set、數(shù)組等數(shù)據(jù)類型

          2.2 內(nèi)置綁定數(shù)據(jù)自動綁定:

          ServletAPI:

          HttpServletRequest

          HttpServletResponse

          HttpSession

          SpringMVC內(nèi)置對象

          Model

          ModelMap

          ModelAndView

          Model和ModelMap默認(rèn)都是存儲了Request請求作用域的數(shù)據(jù)的對象

          這個兩個對象的作用是一樣.就將數(shù)據(jù)返回到頁面.

          測試頁面:

          <%@ page contentType="text/html;charset=UTF-8" language="java" %>
          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <h3>測試ServletAPI綁定</h3>
          <p><a href="/demo1.form"></a></p>
          <h3>測試參數(shù)類型綁定</h3>
          <p><a href="/demo2.form?username=zhangsan">屬性參數(shù)綁定</a></p>
          
          <h3>測試對象參數(shù)綁定</h3>
          <form action="/demo3.form" method="post">
              <input type="text" name="username">
              <input type="text" name="password">
              <input type="text" name="address">
          
              <input type="submit" value="對象參數(shù)綁定">
          
          </form>
          
          <h3>測試數(shù)組類型綁定</h3>
          <form action="/demo4.form" method="post">
              <input type="checkbox" value="1" name="ids">
              <input type="checkbox" value="2" name="ids">
              <input type="checkbox" value="3" name="ids">
          
              <input type="submit" value="數(shù)組類型綁定">
          </form>
          
          <h3>測試Pojo對象數(shù)據(jù)綁定</h3>
          <hr>
          <form action="/demo6.form" method="post">
          
              <h3>Pojo封裝數(shù)組</h3>
              <input type="checkbox" value="1" name="ids">
              <input type="checkbox" value="2" name="ids">
              <input type="checkbox" value="3" name="ids">
          
              <br>
          
              <h3>Pojo封裝user</h3>
              <input type="text" name="user.username">
              <input type="text" name="user.password">
              <input type="text" name="user.address">
              <br>
          
              <h3>Pojo封裝list</h3>
              <input type="text" name="userList[1].name">
              <input type="text" name="userList[1].password">
              <input type="text" name="userList[1].address">
              <input type="text" name="userList[0].name">
              <input type="text" name="userList[0].password">
              <input type="text" name="userList[0].address">
          
              <br>
          
              <h3>Pojo封裝map</h3>
              <input type="text" name="map['username']"><br>
              <input type="text" name="map['password']"><br>
              <input type="text" name="map['age']"><br>
              <input type="text" name="map['address']"><br>
              <input type="submit" value="Pojo對象參數(shù)綁定"><br>
          
          </form>
          </body>
          </html>

          測試Controller:

          package com.dfbz.controller;
          
          import com.dfbz.entity.Pojo;
          import com.dfbz.entity.User;
          import org.springframework.stereotype.Controller;
          import org.springframework.web.bind.annotation.RequestMapping;
          
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
          import javax.servlet.http.HttpSession;
          import java.util.Arrays;
          import java.util.List;
          
          @Controller
          public class DemoController {
          
              @RequestMapping("/demo1")            //自定義屬性綁定
              public String demo1(HttpServletRequest request, HttpServletResponse response, HttpSession session){
          
                  request.setAttribute("username","zhangsan");
          
                  return "/success.jsp";
              }
          
              @RequestMapping("/demo2")            //屬性參數(shù)綁定
              public String demo2(String username){
          
                  System.out.println(username);
          
                  return "/success.jsp";
              }
          
              @RequestMapping("/demo3")            //對象參數(shù)綁定
              public String demo3(User user){
          
                  System.out.println(user);
          
                  return "/success.jsp";
              }
          
          
              @RequestMapping("/demo4")            //數(shù)組參數(shù)綁定
              public String demo4(String[] ids){
          
                  System.out.println(Arrays.toString(ids));
                  return "/success.jsp";
              }
          
              @RequestMapping("/demo5")            //Pojo對象參數(shù)綁定
              public String demo5(Pojo pojo){
          
                  System.out.println(pojo);
          
                  return "/success.jsp";
              }
          }

          3. Post提交方式亂碼解決

          測試GET/POST提交中文數(shù)據(jù)

          頁面:

          <h3>測試Post亂碼</h3>
          <form action="/demo" method="post">
              <input type="text" name="username">
              <input type="text" name="password">
              <input type="text" name="address">
          
              <input type="submit">
          </form>

          controller:

          @RequestMapping("/demo7.form")            //Pojo對象參數(shù)綁定
          public String demo7(User user){
          
              System.out.println(user);
          
              return "/success.jsp";
          }

          發(fā)現(xiàn)POST亂碼

          解決post提交亂碼我們可以配置spring提供的過濾器

          <!--解決Post提交中文亂碼-->
          <filter>
              <filter-name>characterEncoding</filter-name>
              <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
              <init-param>
                  <!--要使用的字符集,一般我們使用UTF-8(保險起見UTF-8最好)-->
                  <param-name>encoding</param-name>
                  <param-value>UTF-8</param-value>
              </init-param>
          </filter>
          <filter-mapping>
              <filter-name>characterEncoding</filter-name>
              <!--代表所有請求都經(jīng)過編碼處理-->
              <url-pattern>/*</url-pattern>
          </filter-mapping>

          4. SpringMVC常用注解

          SpringMVC有一些數(shù)據(jù)是不能自動綁定,需要我們使用它提供的注解強(qiáng)制綁定.

          遇到需要強(qiáng)制綁定的幾種情況

          a. 默認(rèn)參數(shù)綁定的是表單數(shù)據(jù),如果數(shù)據(jù)不是來自表單(如restful),那么必須需要強(qiáng)制綁定

          b. 數(shù)據(jù)是來自表單的,但是參數(shù)名不匹配,那么也需要強(qiáng)制綁定

          c. 數(shù)據(jù)是來自表單的,但是需要將數(shù)據(jù)綁定在Map對象里面,需要強(qiáng)制綁定

          4.1. @PathVariable:綁定路徑參數(shù)

          這個注解是綁定路徑參數(shù)的.

          /**
           * http://localhost:8080/20/zhangsan/demo9.form
           * @param id
           * @param username
           * @return
           */
          @RequestMapping("/{id}/{username}/demo9")            //@PathVariable綁定restful請求
          public String demo9(
              @PathVariable("id") Integer id,@PathVariable("username") String username
              ){
          
              System.out.println(id);
              System.out.println(username);
          
              return "/success.jsp";
          }
          <a href="/20/zhangsan/demo9.form">測試restful風(fēng)個綁定</a>

          4.2. 通過@RequestParam綁定表單數(shù)據(jù)

          接收的參數(shù)的變量名與表單的name屬性不一樣

          /**
           * http://localhost:8080/demo8.form?name=zhangsan
           * @param username
           * @return
           */
          @RequestMapping("/demo8")            //@RequestParam強(qiáng)制數(shù)據(jù)類型綁定
          public String demo8(@RequestParam("name") String username) {
          
              System.out.println(username);
          
              return "/success.jsp";
          }
          <a href="/demo8.form?name=zhangsan">測試強(qiáng)制類型綁定</a>

          4.3. @CookieValue獲得Cookie值的注解

          /**
          * 獲得JSP 頁面,JSESSIOINID這個Cookie值
          * @param cookevalue
          */
          @RequestMapping("/demo10")
          public void getcookie(@CookieValue(value="JSESSIONID") String cookevalue){
              //輸出Cookie
              System.out.println(cookevalue);
          }
          <a href="/demo10.form">測試@CookieValue注解</a>

          4.4. @RequestHeader獲得指定請求頭的值

          @RequestMapping("/demo11")
          //獲取請求頭host的值封裝到value中
          public void demo10(@RequestHeader("host")String value){
          
              System.out.println(value);
          }
          <a href="/demo11.form">測試@RequestHeader注解</a>

          4.5 @SessionAttributes 注解

          把Model和ModelMap中的指定的key或者指定的屬性的值也存入一份進(jìn)session域

          package com.dfbz.controller;
          
          import com.dfbz.entity.User;
          import org.springframework.stereotype.Controller;
          import org.springframework.ui.Model;
          import org.springframework.ui.ModelMap;
          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.bind.annotation.SessionAttribute;
          import org.springframework.web.bind.annotation.SessionAttributes;
          
          import javax.servlet.http.HttpServletRequest;
          
          @Controller
          /**
           * names: 代表此類中model/modelMap的username屬性將會添加到一份進(jìn)入session
           * types: 此類中指定的類型會添加一份到session中
           */
          @SessionAttributes(names={"username"},types={User.class,String.class,Integer.class} )
          public class Demo2Controller {
          
              @RequestMapping("/demo12.form")
              public String demo12(Model modelMap) {
                  modelMap.addAttribute("username","zhangsan");
                  modelMap.addAttribute("password","admin");
                  modelMap.addAttribute("age",20);
                  User user=new User();
                  user.setUsername("xiaodong");
                  modelMap.addAttribute("user",user);
                  return "/success.jsp";
              }
          }
          <a href="/demo12.form">測試@SessionAttribute注解</a>

          @SessionAttribute注解:

          從session中獲取一個值封裝到參數(shù)中

          /**
           * @SessionAttribute:從session中獲取一個值
           * @param username
           * @param user
           * @return
           */
          @RequestMapping(value="/demo6")
          public String demo5(@SessionAttribute("password") String username,@SessionAttribute("user") User user){
              System.out.println(username);
          
              return "/success.jsp";
          }

          5. 格式化參數(shù)類型

          SpringMVC之所以能夠幫我們實現(xiàn)自動數(shù)據(jù)類型轉(zhuǎn)換是因為SpringMVC提供了非常多的轉(zhuǎn)換器(Converter)

          例如:


          發(fā)現(xiàn)他們都實現(xiàn)Converter接口,如果我們需要之定義轉(zhuǎn)換器必須實現(xiàn)Converter接口

          案例:

          實現(xiàn)日期的轉(zhuǎn)換

          實體類:

          public class User {
              private Integer id;
              private String username;
              private String password;
              private String address;
              private Date birthday;
          }

          表單:

          <%@ page contentType="text/html;charset=UTF-8" language="java" %>
          <html>
          <head>
              <title>Title</title>
          </head>
          <body>
          
          <form action="/testConverter.form">
              <input type="text" name="username">
              <input type="text" name="password">
              <input type="text" name="address">
              <input type="text" name="birthday">
          
              <input type="submit">
          </form>
          
          </body>
          </html>

          controller:

          @RequestMapping("/testConverter.form")
          public String testConverter(User user) {
              System.out.println(user);
              return "/success.jsp";
          }

          轉(zhuǎn)換器類:

          package com.dfbz.converter;
          
          import org.springframework.core.convert.converter.Converter;
          
          import java.text.ParseException;
          import java.text.SimpleDateFormat;
          import java.util.Date;
          
          /**
           * Converter<傳入進(jìn)來的類型,轉(zhuǎn)換之后的類型>
           */
          public class MyConverter implements Converter<String,Date> {
              @Override
              public Date convert(String str) {
          
                  SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
                  try {
                      Date date=sdf.parse(str);
                      return date;
          
                  } catch (ParseException e) {
                      e.printStackTrace();
                  }
          
                  return null;
              }
          }

          dispatcher-servlet.xml配置

          <!--在SpringMVC配置的轉(zhuǎn)換器中添加我們自定義的轉(zhuǎn)換器-->
          <bean id="myConverters" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
              <!-- 配置多個轉(zhuǎn)換器 -->
              <property name="converters">
                  <set>
                      <!-- 配置轉(zhuǎn)換器(自定義轉(zhuǎn)化器類) -->
                      <bean class="com.dfbz.converter.MyConverter"></bean>
                  </set>
              </property>
          </bean>
          
          <!--開啟springmvc注解支持并重新指定轉(zhuǎn)換器-->
          <mvc:annotation-driven conversion-service="myConverters"/>

          六. Controller的生命周期

          Spring框架默認(rèn)創(chuàng)建的對象是單例.所以業(yè)務(wù)控制器是一個單例對象.

          SpringMVC提供了,request,session ,globalsession三個生命周期

          • request:每次新的請求,創(chuàng)建一個新的實例.
          • session:每次會話創(chuàng)建一個新的實例. 就是同一個瀏覽器,就使用同一個實例
          • globalsession:基于集群的session

          每個Session創(chuàng)建一個實例

          迎關(guān)注頭條號:java小馬哥

          周一至周日早九點(diǎn)半!下午三點(diǎn)半!精品技術(shù)文章準(zhǔn)時送上!!!

          精品學(xué)習(xí)資料獲取通道,參見文末

          1、回顧servlet 與jsp 執(zhí)行過程

          2、Spring MVC請求處理流程

          3,mvc 體系結(jié)構(gòu)詳解

          URL映射

          表單參數(shù)映射

          調(diào)用目標(biāo)Control

          數(shù)據(jù)模型映射

          視圖解析

          異常處理

          DispatcherServlet

          DispatcherServlet它的作用有點(diǎn)兒類似于網(wǎng)關(guān),DispatcherServlet負(fù)責(zé)將請求轉(zhuǎn)發(fā)到不同的Controller上去。

          配置DispatcherServlet

          / 后面不能寫成/*否則容易出問題。

          編寫Controller,繼承controller類。實現(xiàn)handlerRequest接口。指定ModelAndView。這是一種方式。

          第二種方式 繼承 HttpRequestHandler類。那么DispatcherServlet是如何去匹配不同的controller的呢?這就需要了解SpringMVC的體系結(jié)構(gòu)。整個體系結(jié)構(gòu)都搞懂才行。下面就從一個全局的視角去看Spring MVC的體系結(jié)構(gòu)。

          SpringMVC的體系結(jié)構(gòu)圖

          DispatcherServlet是通過HandlerMapping的一組映射關(guān)系去找到我們的目標(biāo)Controller

          通過上面我們知道Controller存在的形式是多種多樣的。可以通過Controller接口的形式,也可以通過@Controller注解的形式存在。等等等。。。有這么多不同的Controller形式DispatcherServlet是如何去執(zhí)行的呢?這里面就涉及到一個HandlerAdapter控制器適配器。找到數(shù)據(jù)模型的一個映射。然后試圖解析是通過ViewResolver這個東東。他支持各種各樣的試圖解析。view用于具體的試圖解析。HandlerExceptionResolver異常攔截解析器。

          下面就圍繞這張圖去逐一進(jìn)行源碼分析。

          一,HandlerMapping源碼分析

          HandlerMapping是一個接口,他有兩個實現(xiàn)類,MatchableHandlerMapping和AbstractHandlerMapping。HandlerMapping在getHandler里面并沒有返回handler(我們的目標(biāo)執(zhí)行器,也就是我們的controller)而是返回一個執(zhí)行鏈條。HandlerExecutionChain。

          根據(jù)下圖可知HandlerExecutionChain里面有一個getHandler方法,這里面返回了Handler。但是具體的是哪個一個Handler是不確定的,因為他是Object類型的。所以通過動態(tài)代理的方式是不能實現(xiàn)的。只能通過這個執(zhí)行鏈條去返回目標(biāo)Handler。后續(xù)的文章我們會對執(zhí)行鏈條做深入的講解。

          然后看上面的類繼承關(guān)系圖。左面繼承了AbstractUrlHandlerMapping其中AbstractDetectingUrlHandlerMapping起到自動發(fā)現(xiàn)的作用,他是根據(jù)Bean的名稱,而且是必須以/開頭。比如這樣

          <bean name="/hello.do" class="com.tuling.control.SimpleControl"/>

          然后SimpleUrlHandlerMapping。如果不需要自動發(fā)現(xiàn)功能,則使用這個。SimpleUrlHandlerMapping是自己配置URL和controller之間的一個關(guān)系。所以他們之間的區(qū)別就是一個自動發(fā)現(xiàn),一個手動配置。AbstractHandlerMethodMapping是對方法進(jìn)行映射RequestMappingInfoHandlerMapping這個大家比較熟悉了,就是通過@RequestMapping進(jìn)行映射的。如果配置了SimpleUrlHandlerMapping或者BeanNameUrlHandlerMapping那么默認(rèn)的就會失效。SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping可以同時配置,都可以映射到我們的Controller控制器。

          SimpleUrlHandlerMapping的初始化執(zhí)行流程

          對initApplicationContext()方法進(jìn)行調(diào)用。其中super.initApplicationContext();這里是初始化一些攔截器。比如自動發(fā)現(xiàn)攔截器啊等等等。this.registerHandlers(this.urlMap);開始進(jìn)行Url的匹配點(diǎn)進(jìn)去看一下可知如果urlMap不為空才進(jìn)行匹配。第一步是!url.startsWith("/")如果不是/開頭的。就會把你的URL加上/然后獲取Handler,就是我們的控制器。然后判斷handler是不是String類型的,。如果是進(jìn)行去除空格的處理。然后執(zhí)行this.registerHandler(url, handler);點(diǎn)進(jìn)去發(fā)現(xiàn),將handler轉(zhuǎn)化為Object類型。然后判斷是否是懶加載并且是String類型。然后將Handler轉(zhuǎn)化成String的字符串。然后獲取一個ApplicationContext對象。判斷handler是都是單例。如果是通過ApplicationContext.getBean(handlerName)獲取到一個Object對象。然后獲取handlerMap,判斷是否是空,如果不為空說明你配置了兩個name屬性此時會拋出異常。接著判斷你的url是否等于/如果是設(shè)置一個根目錄的Handler通過localhost:8080/即可訪問我們的handler。然后判斷是否等于/*如果是就設(shè)置一個默認(rèn)的和Handler。也就是說你永遠(yuǎn)都不會擔(dān)心找不到這個url的異常。

          最后如果以上情況都不是,則通過this.handlerMap.put(urlPath, resolvedHandler);將url和handler綁定在一起。如果說handler是懶加載那么此時map中存儲的value就是beanName。這就是SimpleUrlHandlerMapping的初始化流程。

          SimpleUrlHandlerMapping的訪問執(zhí)行流程

          我們知道最終執(zhí)行調(diào)用的一定是DispatcherServlet。所以我們從這里開始入手。找到getHandler

          首先通過Iterator var2=this.handlerMappings.iterator();或取到所有的Mapping然后遍歷。遍歷一次取出一個handlerMapping然后調(diào)用handlerMapping。點(diǎn)進(jìn)去看看。第一行g(shù)etHandlerInternal這個方法很重要。點(diǎn)進(jìn)去看看,在這個方法里第一行通過getUrlPathHelper().getLookupPathForRequest(request);去解析我們地址欄上的URL。比如或取到的是/hello.do。第二行通過Object handler=lookupHandler(lookupPath, request);去查找對應(yīng)的handler。點(diǎn)進(jìn)去發(fā)現(xiàn)就是從上面put進(jìn)去的handlerMap找到對應(yīng)的handler。在最后return一個buildPathExposingHandler。點(diǎn)進(jìn)去發(fā)現(xiàn)HandlerExecutionChain這里面配置了一個攔截器鏈,那么返回的并不是我們最終的handler。

          那么getHandlerInternal方法的第二行也執(zhí)行完了。最終拿到得是一個攔截器鏈,如果說攔截器鏈為空則判斷第一行的url解析器拿到得/hello.do是否等于/如果是Object rawHandler=getRootHandler()設(shè)置為根節(jié)點(diǎn)的handler,然后判斷rawHandler是否等于空,如果是則調(diào)用默認(rèn)的handler。如果不等于空呢,則直接通過beanName從Spring容器找到Handler。 再調(diào)用buildPathExposingHandler。還是獲取一個攔截器鏈。最終,將攔截器鏈返回。getHandlerInternal方法執(zhí)行結(jié)束。返回到getHandler方法里。那么如果說,沒有獲取到攔截器鏈,則獲取默認(rèn)的handler。如果不為空,則直接通過beanName從容器中獲取到handler。然后,去創(chuàng)建一個攔截器鏈條,又加入了一個攔截器。最后進(jìn)一步獲取handler。然后返回HandlerExecutionChain。最后通過HandlerAdapter ha=this.getHandlerAdapter(mappedHandler.getHandler());獲取到的才是我們的handler。SimpleUrlHandlerMapping執(zhí)行流程到此介紹完畢,說實話SpringMVC的代碼寫的跟IOC\aop差著一大塊兒的水平感覺。

          Controller 接口:

          HttpRequestHandler 接口:

          HttpServlet 接口:

          @RequestMapping方法注解

          可以看出 Handler 沒有統(tǒng)一的接口,當(dāng)dispatchServlet獲取當(dāng)對應(yīng)的Handler之后如何調(diào)用呢?調(diào)用其哪個方法?這里有兩種解決辦法,一是用instanceof 判斷Handler 類型然后調(diào)用相關(guān)方法 。二是通過引入適配器實現(xiàn),每個適配器實現(xiàn)對指定Handler的調(diào)用。spring 采用后者。

          HandlerAdapter詳解

          HandlerAdapter中有三個接口

          1supports接口,主要的作用是傳入一個handler看看是否可以被執(zhí)行。如果可以返回true。

          2handle處理接口,處理完返回一個ModelAndView。

          3getLastModified,用作緩存處理,獲取最后一次修改時間。

          我們知道HandlerAdapter對應(yīng)適配了4種handler關(guān)系圖如下。

          舉個例子,如果說我們寫了一個Servlet,然后繼承HttpServlet此時如果不配置SimpleServletHandlerAdapter適配器,那么就會報錯,報500的異常。但是如果配置了這個適配器,那么SpringMVC給我們配置的默認(rèn)適配器SimpleControllerHandlerAdapter就會失效。所以需要手動的配置一下。下面看一下源碼即可驗證這個說法。上面講到返回一個攔截器鏈條。然后下面的代碼就是獲取適配器如下圖。

          點(diǎn)進(jìn)getHandlerAdapter方法。這個方法里做了一個do-while循環(huán)。通過support方法判斷是否是可用的適配器。

          在下圖的方法里判斷是否是我們配置的適配器,如果是則返回true。 拿到適配器以后就開始了真正的調(diào)用。

          此時適配器找到了以后就開始真正的調(diào)用handler也就是我們servlet或者是controller。然后通過一個叫ViewResolver的接口類去解析。其中有一個接口叫resolveViewName。他返回一個View。然后View里面有一個render接口。通過里面的model進(jìn)行處理,封裝HTML。通過response進(jìn)行寫入。所以它是先有ViewResolver才有的View。

          然后看看View有哪些實現(xiàn)類如下圖。

          第一個AbstractTemplateViewResolver里面有以下兩種視圖支持,比如我們常用的FreeMarkerViewReslover。

          第二個BeanNameViewResolver可以通過這種方式實現(xiàn)自定的視圖解析器,生明一個自定義View類。然后實現(xiàn)View接口。在render里面去做返回。然后controller里面通過ModelAndView視圖解析器的構(gòu)造器去加載我們的自定義View類型。

          第三個就是InternalResourceViewResolver。這個就是我們最常用的,通過配置前綴,后綴等信息去實現(xiàn)視圖解析。

          下圖是通過BeanName的形式做的視圖解析

          最后附上一張整體的流程圖

          封面圖源網(wǎng)絡(luò),侵權(quán)刪除)

          私信頭條號,發(fā)送:“資料”,獲取更多“秘制” 精品學(xué)習(xí)資料

          如有收獲,請幫忙轉(zhuǎn)發(fā),您的鼓勵是作者最大的動力,謝謝!

          一大波微服務(wù)、分布式、高并發(fā)、高可用的原創(chuàng)系列文章正在路上,

          歡迎關(guān)注頭條號:java小馬哥

          周一至周日早九點(diǎn)半!下午三點(diǎn)半!精品技術(shù)文章準(zhǔn)時送上!!!

          十余年BAT架構(gòu)經(jīng)驗傾囊相授


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

          Spring MVC 請求流程

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

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

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

          Spring MVC 兩大階段

          Spring MVC主要可以分為兩大過程,一是初始化,二就是處理請求。初始化的過程主要就是將我們定義好的 RequestMapping 映射路徑和 Controller 中的方法進(jìn)行一一映射存儲,這樣當(dāng)收到請求之后就可以處理請求調(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è)置一些基本容器標(biāo)識信息之后會去調(diào)用 refresh()方法,也就是初始化 ioc 容器。

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

          DispatchServlet#onRefresh

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

          Spring MVC 九大組件

          MultipartResolver

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

          LocaleResolver

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

          ThemeResolver

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

          HandlerMapping

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

          HandlerAdapter

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

          HandlerExceptionResolver

          這個組件主要是用來處理異常,不過看名字也很明顯,這個只會對處理 Handler 時產(chǎn)生的異常進(jì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,然后進(jìn)行反射調(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)類呢?

          這時候如果拿捏不準(zhǔ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 ,在這里我們就以這個為例來進(jìn)行分析,所以我們應(yīng)該

          AbstractHandlerMethodMapping#getHandlerInternal

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

          AbstractHandlerMethodMapping#lookupHandlerMethod

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

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

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

          AbstractHandlerMethodMapping 的初始化

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

          AbstractHandlerMethodMapping#initHandlerMethods

          initHandlerMethods 方法中,首先還是會從 Spring 的上下文中獲取所有的 Bean,然后會進(jìn)一步從帶有 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 中方法上的一些元信息進(jìn)行各種解析,比如參數(shù),路徑,請求方式等等,然后會將各種信息注冊到對應(yīng)的 Map 集合中,最終完成了整個初始化。

          總結(jié)

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


          主站蜘蛛池模板: 亚洲国产日韩一区高清在线| 日韩AV无码一区二区三区不卡| 国产福利电影一区二区三区,免费久久久久久久精 | 日本精品一区二区久久久 | 精品一区二区三区在线视频观看 | 国产一区二区三区播放心情潘金莲 | 久久99精品国产一区二区三区| 国产亚洲福利精品一区| 亚洲一区二区三区国产精品无码| 日韩av片无码一区二区不卡电影| 美女视频一区二区三区| 99久久国产精品免费一区二区| 一区二区三区日韩精品| 午夜性色一区二区三区不卡视频| 波霸影院一区二区| 日韩精品一区二区三区四区| 亚洲AV噜噜一区二区三区| 亚洲第一区在线观看| 精品视频一区二区三三区四区 | 国产一区二区三区日韩精品| 女人和拘做受全程看视频日本综合a一区二区视频 | 无码人妻久久一区二区三区免费丨| 国产AV午夜精品一区二区三| 一区二区三区午夜| 国产一区二区三区不卡在线看| 亚洲一区二区三区成人网站| 精品女同一区二区三区免费站| 久久青草精品一区二区三区| 亚洲av无码片vr一区二区三区| 香蕉久久一区二区不卡无毒影院| 国产在线无码视频一区二区三区| 国产在线观看一区二区三区精品| 福利视频一区二区牛牛| 精品无码中出一区二区| 内射少妇一区27P| 亚洲一区二区精品视频| 午夜福利国产一区二区| 亚洲高清成人一区二区三区| 亚洲日本中文字幕一区二区三区| 亚洲国产专区一区| 中文国产成人精品久久一区|