SpringMVC 乾貨系列:從零搭建 SpringMVC+mybatis(二):springMVC 原理解析及常用註解 | 掘金技術徵文

嘟嘟MD發表於2019-03-04

原本地址:springMVC乾貨系列:從零搭建springMVC+mybatis(二):springMVC原理解析及常用註解
部落格地址:tengj.top/

前言

上篇文章介紹了maven WEB 專案的搭建,基本的配置檔案也都貼出來了,今天就來介紹下SpringMVC的工作原理以及工作中常用的註解。為以後開發打下堅實的基礎。

正文

SpringMVC框架介紹

SpringMVC就是通過DispatcherServlet將一堆元件串聯起來的Web框架。

  • Spring MVC屬於SpringFrameWork的後續產品,已經融合在Spring Web Flow裡面。

Spring 框架提供了構建 Web 應用程式的全功能 MVC 模組。使用 Spring 可插入的 MVC 架構,可以選擇是使用內建的 Spring Web 框架還是 Struts 這樣的 Web 框架。通過策略介面,Spring 框架是高度可配置的,而且包含多種檢視技術,例如 JavaServer Pages(JSP)技術、Velocity、Tiles、iText 和 POI。Spring MVC 框架並不知道使用的檢視,所以不會強迫您只使用 JSP 技術。
Spring MVC 分離了控制器、模型物件、分派器以及處理程式物件的角色,這種分離讓它們更容易進行定製

  • Spring的MVC框架主要由DispatcherServlet、處理器對映、處理器(控制器)、檢視解析器、檢視組成。

SpringMVC原理圖

SpringMVC 乾貨系列:從零搭建 SpringMVC+mybatis(二):springMVC 原理解析及常用註解 | 掘金技術徵文

SpringMVC介面解釋

DispatcherServlet介面
Spring提供的前端控制器,所有的請求都有經過它來統一分發。在DispatcherServlet將請求分發給Spring Controller之前,需要藉助於Spring提供的HandlerMapping定位到具體的Controller。

HandlerMapping介面:
能夠完成客戶請求到Controller對映。

Controller介面:
需要為併發使用者處理上述請求,因此實現Controller介面時,必須保證執行緒安全並且可重用。

Controller將處理使用者請求,這和Struts Action扮演的角色是一致的。一旦Controller處理完使用者請求,則返回ModelAndView物件給DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和檢視(View)。

從巨集觀角度考慮,DispatcherServlet是整個Web應用的控制器;從微觀考慮,Controller是單個Http請求處理過程中的控制器,而ModelAndView是Http請求過程中返回的模型(Model)和檢視(View)。

ViewResolver介面:
Spring提供的檢視解析器(ViewResolver)在Web應用中查詢View物件,從而將相應結果渲染給客戶。

SpringMVC執行原理

  1. 客戶端請求提交到DispatcherServlet

  2. 由DispatcherServlet控制器查詢一個或多個HandlerMapping,找到處理請求的Controller

  3. DispatcherServlet將請求提交到Controller

  4. Controller呼叫業務邏輯處理後,返回ModelAndView

  5. DispatcherServlet查詢一個或多個ViewResoler檢視解析器,找到ModelAndView指定的檢視

  6. 檢視負責將結果顯示到客戶端

DispatcherServlet是整個Spring MVC的核心。它負責接收HTTP請求組織協調Spring MVC的各個組成部分。其主要工作有以下三項:

  1. 截獲符合特定格式的URL請求。
  2. 初始化DispatcherServlet上下文對應的WebApplicationContext,並將其與業務層、持久化層的WebApplicationContext建立關聯。
  3. 初始化Spring MVC的各個組成元件,並裝配到DispatcherServlet中。

結合專案理解:
1.大家由上面原理也看明白了,DispatcherServlet是整個Spring MVC的核心,SpringMVC所有的請求都會通過這個前端控制器。它配置的地方是在web.xml裡面,配置如下:

<servlet>
        <servlet-name>springmvctouchbaidu</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>複製程式碼

配置的時候還指明瞭contextConfigLocation,這樣就會去載入這個applicationContext.xml了。

2.原理第2點中由DispatcherServlet控制器查詢一個或多個HandlerMapping,找到處理請求的Controller。這裡其實是通過在applicationContext-mvc.xml配置了掃描路徑以及開啟了註解驅動來實現的。
applicationContext-mvc.xml中的配置:

<context:component-scan base-package="com.tengj.demo"/>複製程式碼

context:component-scan說明了要掃描com.tengj.demo這個包下所有的類。這裡要注意一下,大家以後開發中有用到註解的類一定都要在這個demo包下,不然就會拋異常的。

載入了掃描路徑後,還要開啟註解驅動,這樣才能認到程式碼中使用到的註解,比如@Controller這個註解。

<mvc:annotation-driven />複製程式碼

3.ViewResoler檢視解析器對應配置裡面的

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>複製程式碼

這樣,當controller中方法返回的是

return "index";複製程式碼

的時候,實際是指向了/WEB-INF/pages/index.jsp這個頁面。

常用到的註解

當我們使用了自動掃描+註解的方式後,就不需要在applicationContext-mvc.xml裡面配置類的bean了,要引用類直接在成員變數上面加行註解,set/get方法也省了。超級方便,下面就列出常規開發中常用的註解。

@Component

@Component
是所有受Spring 管理元件的通用形式,@Component註解可以放在類的頭上,@Component不推薦使用。

使用@Controller定義一個Controller控制器

@Controller對應表現層的Bean,也就是Action,例如:

@Controller
public class UserController {
 ……
}複製程式碼

使用@Controller註解標識UserController之後,就表示要把UserController交給Spring容器管理,在Spring容器中會存在一個名字為"userController"的action,這個名字是根據UserController類名來取的。注意:如果@Controller不指定其value【@Controller】,則預設的bean名字為這個類的類名首字母小寫,如果指定value【@Controller(value="UserController")】或者【@Controller("UserController")】,則使用value作為bean的名字。

使用@Service定義一個業務層Bean

@Service對應的是業務層Bean,例如:

@Service("userService")
public class UserServiceImpl implements UserService{
………
}複製程式碼

@Service("userService")註解是告訴Spring,當Spring要建立UserServiceImpl的的例項時,bean的名字必須叫做"userService",這樣當Action需要使用UserServiceImpl的的例項時,就可以由Spring建立好的"userService",然後注入給Action:在Action只需要宣告一個名字叫“userService”的變數來接收由Spring注入的"userService"即可,具體程式碼如下:

//注入userService
@Resource(name="userService")
UserService userService;複製程式碼

注意:在UserController宣告的“userService”變數的型別必須是“UserServiceImpl”或者是其父類“UserService”,否則由於型別不一致而無法注入,由於UserController中的宣告的“userService”變數使用了@Resource註解去標註,並且指明瞭其name = "userService",這就等於告訴Spring,說我UserController要例項化一個“userService”,你Spring快點幫我例項化好,然後給我,當Spring看到userService變數上的@Resource的註解時,根據其指明的name屬性可以知道,UserController中需要用到一個UserServiceImpl的例項,此時Spring就會把自己建立好的名字叫做"userService"的UserServiceImpl的例項注入給UserController中的“userService”變數,幫助UserController完成userService的例項化,這樣在UserController中就不用通過“UserService userService = new UserServiceImpl();”這種最原始的方式去例項化userService了。

如果沒有Spring,那麼當UserController需要使用UserServiceImpl時,必須通過“UserService userService = new UserServiceImpl();”主動去建立例項物件,但使用了Spring之後,UserController要使用UserServiceImpl時,就不用主動去建立UserServiceImpl的例項了,建立UserServiceImpl例項已經交給Spring來做了,Spring把建立好的UserServiceImpl例項給UserController,UserController拿到就可以直接用了。

UserController由原來的主動建立UserServiceImpl例項後就可以馬上使用,變成了被動等待由Spring建立好UserServiceImpl例項之後再注入給UserController,UserController才能夠使用。這說明UserController對“UserServiceImpl”類的“控制權”已經被“反轉”了,原來主動權在自己手上,自己要使用“UserServiceImpl”類的例項,自己主動去new一個出來馬上就可以使用了,但現在自己不能主動去new“UserServiceImpl”類的例項,new“UserServiceImpl”類的例項的權力已經被Spring拿走了,只有Spring才能夠new“UserServiceImpl”類的例項,而UserController只能等Spring建立好“UserServiceImpl”類的例項後,再“懇求”Spring把建立好的“UserServiceImpl”類的例項給他,這樣他才能夠使用“UserServiceImpl”,這就是Spring核心思想“控制反轉”,也叫“依賴注入”。

“依賴注入”也很好理解,UserController需要使用UserServiceImpl幹活,那麼就是對UserServiceImpl產生了依賴,Spring把Acion需要依賴的UserServiceImpl注入(也就是“給”)給UserController,這就是所謂的“依賴注入”。對UserController而言,UserController依賴什麼東西,就請求Spring注入給他,對Spring而言,UserController需要什麼,Spring就主動注入給他。

使用@Repository定義一個資料訪問層Bean

@Repository對應資料訪問層Bean ,例如:

@Repository(value="userDao")
 public class UserDao {
 ………
 }複製程式碼

@Repository(value="userDao")註解是告訴Spring,讓Spring建立一個名字叫“userDao”的UserDao例項。

當Service需要使用Spring建立的名字叫“userDao”的UserDao例項時,就可以使用@Resource(name = "userDao")註解告訴Spring,Spring把建立好的userDao注入給Service即可。

// 注入userDao
@Resource(name = "userDao")
private UserDao userDao;複製程式碼

@Resource跟@Autowired比較

上面介紹中Controller中注入userService或者 Service層裡面注入dao都是用@Resource標籤,其實也可以使用@Autowired來替代,但是建議使用@Resource。下面說說這2者的區別:

  1. @Autowired和@Resource都可以用來裝配bean,都可以寫在欄位上,或者方法上。
  2. @Autowired屬於Spring的;@Resource為JSR-250標準的註釋,屬於J2EE的。
  3. @Autowired預設按型別裝配,預設情況下必須要求依賴物件必須存在,如果要允許null值,可以設定它的required屬性為false,例如:@Autowired(required=false) ,如果我們想使用名稱裝配可以結合@Qualifier註解進行使用
    例如:
    @Autowired
    @Qualifier("baseDao")
    private BaseDao baseDao;複製程式碼
  4. @Resource,預設按照名稱進行裝配,名稱可以通過name屬性進行指定,如果沒有指定name屬性,當註解寫在欄位上時,預設取欄位名進行安裝名稱查詢,如果註解寫在setter方法上預設取屬性名進行裝配。當找不到與名稱匹配的bean時才按照型別進行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進行裝配。
    例如:
    @Resource(name="baseDao")
    private BaseDao baseDao;複製程式碼
    5.之所以推薦使用@Resource,因為這個註解是屬於J2EE的,減少了與spring的耦合。這樣程式碼看起就比較優雅。

使用@RequestMapping來對映Request請求與處理器

SpringMVC使用@RequestMapping註解為控制器制定可以處理哪些URL請求
在控制器的類定義及方法定義處都可以標註

  • 類定義處:提供初步的請求對映資訊。相對於WEB應用的根目錄
  • 方法處:提供進一步的細分對映資訊。相對於類定義處的URL,若類定義處未標註@RequestMapping,則方法處標記的URL相對於WEB應用的根目錄。
    舉個列子:
    @Controller
    @RequestMapping(value="/test")
    public class UserController{
      @RequestMapping(value="/view",method = RequestMethod.GET)
      public String index(){
          System.out.println("進來了");
          return "index";
      }
    }複製程式碼
    上面這樣,只要地址訪問http://localhost:8080/SpringMVCMybatis/test/view 就能進入這個index方法了,其中使用method屬性來指定請求是get還是post。

(一)使用帶佔位符URI@PathVariable

帶佔位符的URL是Spring3.0新增的功能,該功能在SpringMVC向REST目標挺進發展過程中具有里程碑的意義

通過@PathVariable可以將URL中佔位符引數繫結到控制器處理方法的入參中:URL中的{xxx}佔位符可以通過@PathVariable("xxx")繫結到操作方法入參中。
例子:

/**
 * @RequestMapping 可以來對映URL中的佔位符到目標方法的引數中
 * @param id
 * @return
 */
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") String id){
    System.out.println("testPathVariable id="+id);
    return "index";
}複製程式碼

(二)使用@RequestParam繫結HttpServletRequest請求引數到控制器方法引數

@RequestMapping ( "requestParam" )
   public String testRequestParam( @RequestParam(required=false) String name, @RequestParam ( "age" ) int age) {
       return "requestParam" ;
}複製程式碼

在上面程式碼中利用@RequestParam 從HttpServletRequest 中繫結了引數name 到控制器方法引數name ,繫結了引數age 到控制器方法引數age 。值得注意的是和@PathVariable 一樣,當你沒有明確指定從request 中取哪個引數時,Spring 在程式碼是debug 編譯的情況下會預設取更方法引數同名的引數,如果不是debug 編譯的就會報錯。此外,當需要從request 中繫結的引數和方法的引數名不相同的時候,也需要在@RequestParam 中明確指出是要繫結哪個引數。在上面的程式碼中如果我訪問/requestParam.do?name=hello&age=1 則Spring 將會把request請求引數name 的值hello 賦給對應的處理方法引數name ,把引數age 的值1 賦給對應的處理方法引數age 。

在@RequestParam 中除了指定繫結哪個引數的屬性value 之外,還有一個屬性required ,它表示所指定的引數是否必須在request 屬性中存在,預設是true ,表示必須存在,當不存在時就會報錯。在上面程式碼中我們指定了引數name 的required 的屬性為false ,而沒有指定age 的required 屬性,這時候如果我們訪問/requestParam.do而沒有傳遞引數的時候,系統就會丟擲異常,因為age 引數是必須存在的,而我們沒有指定。而如果我們訪問/requestParam.do?age=1 的時候就可以正常訪問,因為我們傳遞了必須的引數age ,而引數name 是非必須的,不傳遞也可以。

@RequestMapping ( "cookieValue" )
    public String testCookieValue( @CookieValue ( "hello" ) String cookieValue, @CookieValue String hello) {
       System. out .println(cookieValue + "-----------" + hello);
       return "cookieValue" ;
    }複製程式碼

在上面的程式碼中我們使用@CookieValue 繫結了cookie 的值到方法引數上。上面一共繫結了兩個引數,一個是明確指定要繫結的是名稱為hello 的cookie 的值,一個是沒有指定。使用沒有指定的形式的規則和@PathVariable、@RequestParam 的規則是一樣的,即在debug 編譯模式下將自動獲取跟方法引數名同名的cookie 值。

(四)使用@RequestHeader註解繫結 HttpServletRequest頭資訊到Controller方法引數

@RequestMapping ( "testRequestHeader" )
public String testRequestHeader( @RequestHeader ( "Host" ) String hostAddr, @RequestHeader String Host, @RequestHeader String host ) {
    System. out .println(hostAddr + "-----" + Host + "-----" + host );
    return "requestHeader" ;
}複製程式碼

在上面的程式碼中我們使用了 @RequestHeader 繫結了 HttpServletRequest 請求頭 host 到Controller 的方法引數。上面方法的三個引數都將會賦予同一個值,由此我們可以知道在繫結請求頭引數到方法引數的時候規則和 @PathVariable 、 @RequestParam 以及 @CookieValue 是一樣的,即沒有指定繫結哪個引數到方法引數的時候,在 debug 編譯模式下將使用方法引數名作為需要繫結的引數。但是有一點 @RequestHeader 跟另外三種繫結方式是不一樣的,那就是在使用 @RequestHeader 的時候是大小寫不敏感的,即 @RequestHeader(“Host”) 和 @RequestHeader(“host”) 繫結的都是 Host 頭資訊。記住在 @PathVariable 、 @RequestParam 和 @CookieValue 中都是大小寫敏感的。

(五)@RequestMapping的一些高階應用

在RequestMapping 中除了指定請求路徑value 屬性外,還有其他的屬性可以指定,如params 、method 和headers 。這樣屬性都可以用於縮小請求的對映範圍。
1.params屬性

@RequestMapping (value= "testParams" , params={ "param1=value1" , "param2" , "!param3" })
    public String testParams() {
       System. out .println( "test Params..........." );
       return "testParams" ;
    }複製程式碼

在上面的程式碼中我們用@RequestMapping 的params 屬性指定了三個引數,這些引數都是針對請求引數而言的,它們分別表示引數param1 的值必須等於value1 ,引數param2 必須存在,值無所謂,引數param3 必須不存在,只有當請求/testParams.do 並且滿足指定的三個引數條件的時候才能訪問到該方法。所以當請求/testParams.do?param1=value1&param2=value2 的時候能夠正確訪問到該testParams 方法,當請求/testParams.do?param1=value1&param2=value2&param3=value3 的時候就不能夠正常的訪問到該方法,因為在@RequestMapping 的params 引數裡面指定了引數param3 是不能存在的。

2.method屬性

@RequestMapping (value= "testMethod" , method={RequestMethod. GET , RequestMethod. DELETE })
    public String testMethod() {
       return "method" ;
    }複製程式碼

在上面的程式碼中就使用method 引數限制了以GET 或DELETE 方法請求/testMethod.do 的時候才能訪問到該Controller 的testMethod 方法。

3.headers屬性

@RequestMapping (value= "testHeaders" , headers={ "host=localhost" , "Accept" })
    public String testHeaders() {
       return "headers" ;
    }複製程式碼

headers 屬性的用法和功能與params 屬性相似。在上面的程式碼中當請求/testHeaders.do 的時候只有當請求頭包含Accept 資訊,且請求的host 為localhost 的時候才能正確的訪問到testHeaders 方法。

(六)以@RequestMapping標記的處理器方法支援的方法引數和返回型別

1. 支援的方法引數型別
  1. HttpServlet 物件,主要包括HttpServletRequest 、HttpServletResponse 和HttpSession 物件。 這些引數Spring 在呼叫處理器方法的時候會自動給它們賦值,所以當在處理器方法中需要使用到這些物件的時候,可以直接在方法上給定一個方法引數的申明,然後在方法體裡面直接用就可以了。但是有一點需要注意的是在使用HttpSession 物件的時候,如果此時HttpSession 物件還沒有建立起來的話就會有問題。

  2. Spring 自己的WebRequest 物件。 使用該物件可以訪問到存放在HttpServletRequest 和HttpSession 中的屬性值。

  3. InputStream 、OutputStream 、Reader 和Writer 。 InputStream 和Reader 是針對HttpServletRequest 而言的,可以從裡面取資料;OutputStream 和Writer 是針對HttpServletResponse 而言的,可以往裡面寫資料。

  4. 使用@PathVariable 、@RequestParam 、@CookieValue 和@RequestHeader 標記的引數。

  5. 使用@ModelAttribute 標記的引數。

  6. java.util.Map 、Spring 封裝的Model 和ModelMap 。 這些都可以用來封裝模型資料,用來給檢視做展示。

  7. 實體類。 可以用來接收上傳的引數。

  8. Spring 封裝的MultipartFile 。 用來接收上傳檔案的。

  9. Spring 封裝的Errors 和BindingResult 物件。 這兩個物件引數必須緊接在需要驗證的實體物件引數之後,它裡面包含了實體物件的驗證結果。

2. 支援的返回型別
  1. 一個包含模型和檢視的ModelAndView 物件。

  2. 一個模型物件,這主要包括Spring 封裝好的Model 和ModelMap ,以及java.util.Map ,當沒有檢視返回的時候檢視名稱將由RequestToViewNameTranslator 來決定。

  3. 一個View 物件。這個時候如果在渲染檢視的過程中模型的話就可以給處理器方法定義一個模型引數,然後在方法體裡面往模型中新增值。

  4. 一個String 字串。這往往代表的是一個檢視名稱。這個時候如果需要在渲染檢視的過程中需要模型的話就可以給處理器方法一個模型引數,然後在方法體裡面往模型中新增值就可以了。

  5. 返回值是void 。這種情況一般是我們直接把返回結果寫到HttpServletResponse 中了,如果沒有寫的話,那麼Spring 將會利用RequestToViewNameTranslator 來返回一個對應的檢視名稱。如果檢視中需要模型的話,處理方法與返回字串的情況相同。

  6. 如果處理器方法被註解@ResponseBody 標記的話,那麼處理器方法的任何返回型別都會通過HttpMessageConverters 轉換之後寫到HttpServletResponse 中,而不會像上面的那些情況一樣當做檢視或者模型來處理。

  7. 除以上幾種情況之外的其他任何返回型別都會被當做模型中的一個屬性來處理,而返回的檢視還是由RequestToViewNameTranslator 來決定,新增到模型中的屬性名稱可以在該方法上用@ModelAttribute(“attributeName”) 來定義,否則將使用返回型別的類名稱的首字母小寫形式來表示。使用@ModelAttribute 標記的方法會在@RequestMapping 標記的方法執行之前執行。

(七)使用 @ModelAttribute 和 @SessionAttributes 傳遞和儲存資料

SpringMVC 支援使用 @ModelAttribute 和 @SessionAttributes 在不同的模型和控制器之間共享資料。 @ModelAttribute 主要有兩種使用方式,一種是標註在方法上,一種是標註在 Controller 方法引數上。

當 @ModelAttribute 標記在方法上的時候,該方法將在處理器方法執行之前執行,然後把返回的物件存放在 session 或模型屬性中,屬性名稱可以使用 @ModelAttribute(“attributeName”) 在標記方法的時候指定,若未指定,則使用返回型別的類名稱(首字母小寫)作為屬性名稱。關於 @ModelAttribute 標記在方法上時對應的屬性是存放在 session 中還是存放在模型中,我們來做一個實驗,看下面一段程式碼。

@Controller
@RequestMapping ( "/myTest" )
public class MyController {

    @ModelAttribute ( "hello" )
    public String getModel() {
       System. out .println( "-------------Hello---------" );
       return "world" ;
    }

    @ModelAttribute ( "intValue" )
    public int getInteger() {
       System. out .println( "-------------intValue---------------" );
       return 10;
    }

    @RequestMapping ( "sayHello" )
    public void sayHello( @ModelAttribute ( "hello" ) String hello, @ModelAttribute ( "intValue" ) int num, @ModelAttribute ( "user2" ) User user, Writer writer, HttpSession session) throws IOException {
       writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);
       writer.write( "\r" );
       Enumeration enume = session.getAttributeNames();
       while (enume.hasMoreElements())
           writer.write(enume.nextElement() + "\r" );
    }

    @ModelAttribute ( "user2" )
    public User getUser() {
       System. out .println( "---------getUser-------------" );
       return new User(3, "user2" );
    }
}複製程式碼

當我們請求 /myTest/sayHello.do 的時候使用 @ModelAttribute 標記的方法會先執行,然後把它們返回的物件存放到模型中。最終訪問到 sayHello 方法的時候,使用 @ModelAttribute 標記的方法引數都能被正確的注入值。執行結果如下所示:

Hello world,Hello user210複製程式碼

由執行結果我們可以看出來,此時 session 中沒有包含任何屬性,也就是說上面的那些物件都是存放在模型屬性中,而不是存放在 session 屬性中。那要如何才能存放在 session 屬性中呢?這個時候我們先引入一個新的概念 @SessionAttributes ,它的用法會在講完 @ModelAttribute 之後介紹,這裡我們就先拿來用一下。我們在 MyController 類上加上 @SessionAttributes 屬性標記哪些是需要存放到 session 中的。看下面的程式碼:

@Controller
@RequestMapping ( "/myTest" )
@SessionAttributes (value={ "intValue" , "stringValue" }, types={User. class })
public class MyController {

    @ModelAttribute ( "hello" )
    public String getModel() {
       System. out .println( "-------------Hello---------" );
       return "world" ;
    }

    @ModelAttribute ( "intValue" )
    public int getInteger() {
       System. out .println( "-------------intValue---------------" );
       return 10;
    }

    @RequestMapping ( "sayHello" )
    public void sayHello(Map<String, Object> map, @ModelAttribute ( "hello" ) String hello, @ModelAttribute ( "intValue" ) int num, @ModelAttribute ( "user2" ) User user, Writer writer, HttpServletRequest request) throws IOException {
       map.put( "stringValue" , "String" );
       writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);
       writer.write( "\r" );
       HttpSession session = request.getSession();
       Enumeration enume = session.getAttributeNames();
       while (enume.hasMoreElements())
           writer.write(enume.nextElement() + "\r" );
       System. out .println(session);
    }

    @ModelAttribute ( "user2" )
    public User getUser() {
       System. out .println( "---------getUser-------------" );
       return new User(3, "user2" );
    }
}複製程式碼

在上面程式碼中我們指定了屬性為 intValue 或 stringValue 或者型別為 User 的都會放到 Session中,利用上面的程式碼當我們訪問 /myTest/sayHello.do 的時候,結果如下:

Hello world,Hello user210複製程式碼

仍然沒有列印出任何 session 屬性,這是怎麼回事呢?怎麼定義了把模型中屬性名為 intValue 的物件和型別為 User 的物件存到 session 中,而實際上沒有加進去呢?難道我們錯啦?我們當然沒有錯,只是在第一次訪問 /myTest/sayHello.do 的時候 @SessionAttributes 定義了需要存放到 session 中的屬性,而且這個模型中也有對應的屬性,但是這個時候還沒有加到 session 中,所以 session 中不會有任何屬性,等處理器方法執行完成後 Spring 才會把模型中對應的屬性新增到 session 中。所以當請求第二次的時候就會出現如下結果:

Hello world,Hello user210
user2
intValue
stringValue複製程式碼

當 @ModelAttribute 標記在處理器方法引數上的時候,表示該引數的值將從模型或者 Session 中取對應名稱的屬性值,該名稱可以通過 @ModelAttribute(“attributeName”) 來指定,若未指定,則使用引數型別的類名稱(首字母小寫)作為屬性名稱。

總結

到此,SpringMVC的原理以及常用註解就介紹的差不多了,平時開發這些就夠用了,如果你還想深入學習SpringMVC知識點,可以關注我個人公眾號,裡面資源貼有全套的視訊教程。

參考

Spring常用註解
@AUTOWIRED與@RESOURCE的區別
SpringMVC Controller介紹及常用註解


一直覺得自己寫的不是技術,而是情懷,一篇篇文章是自己這一路走來的痕跡。靠專業技能的成功是最具可複製性的,希望我的這條路能讓你少走彎路,希望我能幫你抹去知識的蒙塵,希望我能幫你理清知識的脈絡,希望未來技術之巔上有你也有我。

掘金技術徵文第三期:聊聊你的最佳實踐

相關文章