前言
上篇文章介紹了maven WEB 項目的搭建,基本的配置文件也都貼出來了,今天就來介紹下SpringMVC的工作原理以及工作中常用的注解。為以后開發(fā)打下堅實的基礎(chǔ)。
正文
SpringMVC框架介紹
SpringMVC就是通過DispatcherServlet將一堆組件串聯(lián)起來的Web框架。
- Spring MVC屬于SpringFrameWork的后續(xù)產(chǎn)品,已經(jīng)融合在Spring Web Flow里面。
Spring 框架提供了構(gòu)建 Web 應(yīng)用程序的全功能 MVC 模塊。使用 Spring 可插入的 MVC 架構(gòu),可以選擇是使用內(nèi)置的 Spring Web 框架還是 Struts 這樣的 Web 框架。通過策略接口,Spring 框架是高度可配置的,而且包含多種視圖技術(shù),例如 JavaServer Pages(JSP)技術(shù)、Velocity、Tiles、iText 和 POI。Spring MVC 框架并不知道使用的視圖,所以不會強迫您只使用 JSP 技術(shù)。
Spring MVC 分離了控制器、模型對象、分派器以及處理程序?qū)ο蟮慕巧?,這種分離讓它們更容易進(jìn)行定制
- Spring的MVC框架主要由DispatcherServlet、處理器映射、處理器(控制器)、視圖解析器、視圖組成。
SpringMVC原理圖

SpringMVC接口解釋
DispatcherServlet接口:
Spring提供的前端控制器,所有的請求都有經(jīng)過它來統(tǒng)一分發(fā)。在DispatcherServlet將請求分發(fā)給Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具體的Controller。
HandlerMapping接口:
能夠完成客戶請求到Controller映射。
Controller接口:
需要為并發(fā)用戶處理上述請求,因此實現(xiàn)Controller接口時,必須保證線程安全并且可重用。
Controller將處理用戶請求,這和Struts Action扮演的角色是一致的。一旦Controller處理完用戶請求,則返回ModelAndView對象給DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和視圖(View)。
從宏觀角度考慮,DispatcherServlet是整個Web應(yīng)用的控制器;從微觀考慮,Controller是單個Http請求處理過程中的控制器,而ModelAndView是Http請求過程中返回的模型(Model)和視圖(View)。
ViewResolver接口:
Spring提供的視圖解析器(ViewResolver)在Web應(yīng)用中查找View對象,從而將相應(yīng)結(jié)果渲染給客戶。
SpringMVC運行原理
客戶端請求提交到DispatcherServlet
由DispatcherServlet控制器查詢一個或多個HandlerMapping,找到處理請求的Controller
DispatcherServlet將請求提交到Controller
Controller調(diào)用業(yè)務(wù)邏輯處理后,返回ModelAndView
DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖
視圖負(fù)責(zé)將結(jié)果顯示到客戶端
DispatcherServlet是整個Spring MVC的核心。它負(fù)責(zé)接收HTTP請求組織協(xié)調(diào)Spring MVC的各個組成部分。其主要工作有以下三項:
- 截獲符合特定格式的URL請求。
- 初始化DispatcherServlet上下文對應(yīng)的WebApplicationContext,并將其與業(yè)務(wù)層、持久化層的WebApplicationContext建立關(guān)聯(lián)。
- 初始化Spring MVC的各個組成組件,并裝配到DispatcherServlet中。
結(jié)合項目理解:
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配置了掃描路徑以及開啟了注解驅(qū)動來實現(xiàn)的。
applicationContext-mvc.xml中的配置:
<context:component-scan base-package="com.tengj.demo"/>
context:component-scan說明了要掃描com.tengj.demo這個包下所有的類。這里要注意一下,大家以后開發(fā)中有用到注解的類一定都要在這個demo包下,不然就會拋異常的。
加載了掃描路徑后,還要開啟注解驅(qū)動,這樣才能認(rèn)到代碼中使用到的注解,比如@Controller這個注解。
<mvc:annotation-driven />
3.ViewResoler視圖解析器對應(yīng)配置里面的
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
這樣,當(dāng)controller中方法返回的是
return "index";
的時候,實際是指向了/WEB-INF/pages/index.jsp這個頁面。
常用到的注解
當(dāng)我們使用了自動掃描+注解的方式后,就不需要在applicationContext-mvc.xml里面配置類的bean了,要引用類直接在成員變量上面加行注解,set/get方法也省了。超級方便,下面就列出常規(guī)開發(fā)中常用的注解。
@Component
@Component
是所有受Spring 管理組件的通用形式,@Component注解可以放在類的頭上,@Component不推薦使用。
使用@Controller定義一個Controller控制器
@Controller對應(yīng)表現(xiàn)層的Bean,也就是Action,例如:
@Controller
public class UserController {
……
}
使用@Controller注解標(biāo)識UserController之后,就表示要把UserController交給Spring容器管理,在Spring容器中會存在一個名字為"userController"的action,這個名字是根據(jù)UserController類名來取的。注意:如果@Controller不指定其value【@Controller】,則默認(rèn)的bean名字為這個類的類名首字母小寫,如果指定value【@Controller(value="UserController")】或者【@Controller("UserController")】,則使用value作為bean的名字。
使用@Service定義一個業(yè)務(wù)層Bean
@Service對應(yīng)的是業(yè)務(wù)層Bean,例如:
@Service("userService")
public class UserServiceImpl implements UserService{
………
}
@Service("userService")注解是告訴Spring,當(dāng)Spring要創(chuàng)建UserServiceImpl的的實例時,bean的名字必須叫做"userService",這樣當(dāng)Action需要使用UserServiceImpl的的實例時,就可以由Spring創(chuàng)建好的"userService",然后注入給Action:在Action只需要聲明一個名字叫“userService”的變量來接收由Spring注入的"userService"即可,具體代碼如下:
//注入userService
@Resource(name="userService")
UserService userService;
注意:在UserController聲明的“userService”變量的類型必須是“UserServiceImpl”或者是其父類“UserService”,否則由于類型不一致而無法注入,由于UserController中的聲明的“userService”變量使用了@Resource注解去標(biāo)注,并且指明了其name = "userService",這就等于告訴Spring,說我UserController要實例化一個“userService”,你Spring快點幫我實例化好,然后給我,當(dāng)Spring看到userService變量上的@Resource的注解時,根據(jù)其指明的name屬性可以知道,UserController中需要用到一個UserServiceImpl的實例,此時Spring就會把自己創(chuàng)建好的名字叫做"userService"的UserServiceImpl的實例注入給UserController中的“userService”變量,幫助UserController完成userService的實例化,這樣在UserController中就不用通過“UserService userService = new UserServiceImpl();”這種最原始的方式去實例化userService了。
如果沒有Spring,那么當(dāng)UserController需要使用UserServiceImpl時,必須通過“UserService userService = new UserServiceImpl();”主動去創(chuàng)建實例對象,但使用了Spring之后,UserController要使用UserServiceImpl時,就不用主動去創(chuàng)建UserServiceImpl的實例了,創(chuàng)建UserServiceImpl實例已經(jīng)交給Spring來做了,Spring把創(chuàng)建好的UserServiceImpl實例給UserController,UserController拿到就可以直接用了。
UserController由原來的主動創(chuàng)建UserServiceImpl實例后就可以馬上使用,變成了被動等待由Spring創(chuàng)建好UserServiceImpl實例之后再注入給UserController,UserController才能夠使用。這說明UserController對“UserServiceImpl”類的“控制權(quán)”已經(jīng)被“反轉(zhuǎn)”了,原來主動權(quán)在自己手上,自己要使用“UserServiceImpl”類的實例,自己主動去new一個出來馬上就可以使用了,但現(xiàn)在自己不能主動去new“UserServiceImpl”類的實例,new“UserServiceImpl”類的實例的權(quán)力已經(jīng)被Spring拿走了,只有Spring才能夠new“UserServiceImpl”類的實例,而UserController只能等Spring創(chuàng)建好“UserServiceImpl”類的實例后,再“懇求”Spring把創(chuàng)建好的“UserServiceImpl”類的實例給他,這樣他才能夠使用“UserServiceImpl”,這就是Spring核心思想“控制反轉(zhuǎn)”,也叫“依賴注入”。
“依賴注入”也很好理解,UserController需要使用UserServiceImpl干活,那么就是對UserServiceImpl產(chǎn)生了依賴,Spring把Acion需要依賴的UserServiceImpl注入(也就是“給”)給UserController,這就是所謂的“依賴注入”。對UserController而言,UserController依賴什么東西,就請求Spring注入給他,對Spring而言,UserController需要什么,Spring就主動注入給他。
使用@Repository定義一個數(shù)據(jù)訪問層Bean
@Repository對應(yīng)數(shù)據(jù)訪問層Bean ,例如:
@Repository(value="userDao")
public class UserDao {
………
}
@Repository(value="userDao")注解是告訴Spring,讓Spring創(chuàng)建一個名字叫“userDao”的UserDao實例。
當(dāng)Service需要使用Spring創(chuàng)建的名字叫“userDao”的UserDao實例時,就可以使用@Resource(name = "userDao")注解告訴Spring,Spring把創(chuàng)建好的userDao注入給Service即可。
// 注入userDao
@Resource(name = "userDao")
private UserDao userDao;
@Resource跟@Autowired比較
上面介紹中Controller中注入userService或者 Service層里面注入dao都是用@Resource標(biāo)簽,其實也可以使用@Autowired來替代,但是建議使用@Resource。下面說說這2者的區(qū)別:
- @Autowired和@Resource都可以用來裝配bean,都可以寫在字段上,或者方法上。
- @Autowired屬于Spring的;@Resource為JSR-250標(biāo)準(zhǔn)的注釋,屬于J2EE的。
- @Autowired默認(rèn)按類型裝配,默認(rèn)情況下必須要求依賴對象必須存在,如果要允許null值,可以設(shè)置它的required屬性為false,例如:@Autowired(required=false) ,如果我們想使用名稱裝配可以結(jié)合@Qualifier注解進(jìn)行使用
例如:
@Autowired
@Qualifier("baseDao")
private BaseDao baseDao;
- @Resource,默認(rèn)按照名稱進(jìn)行裝配,名稱可以通過name屬性進(jìn)行指定,如果沒有指定name屬性,當(dāng)注解寫在字段上時,默認(rèn)取字段名進(jìn)行安裝名稱查找,如果注解寫在setter方法上默認(rèn)取屬性名進(jìn)行裝配。當(dāng)找不到與名稱匹配的bean時才按照類型進(jìn)行裝配。但是需要注意的是,如果name屬性一旦指定,就只會按照名稱進(jìn)行裝配。
例如:
@Resource(name="baseDao")
private BaseDao baseDao;
5.之所以推薦使用@Resource,因為這個注解是屬于J2EE的,減少了與spring的耦合。這樣代碼看起就比較優(yōu)雅。
使用@RequestMapping來映射Request請求與處理器
SpringMVC使用@RequestMapping注解為控制器制定可以處理哪些URL請求
在控制器的類定義及方法定義處都可以標(biāo)注
- 類定義處:提供初步的請求映射信息。相對于WEB應(yīng)用的根目錄
- 方法處:提供進(jìn)一步的細(xì)分映射信息。相對于類定義處的URL,若類定義處未標(biāo)注@RequestMapping,則方法處標(biāo)記的URL相對于WEB應(yīng)用的根目錄。
舉個列子:
@Controller
@RequestMapping(value="/test")
public class UserController{
@RequestMapping(value="/view",method = RequestMethod.GET)
public String index(){
System.out.println("進(jìn)來了");
return "index";
}
}
上面這樣,只要地址訪問http://localhost:8080/SpringMVCMybatis/test/view 就能進(jìn)入這個index方法了,其中使用method屬性來指定請求是get還是post。
(一)使用帶占位符URI@PathVariable
帶占位符的URL是Spring3.0新增的功能,該功能在SpringMVC向REST目標(biāo)挺進(jìn)發(fā)展過程中具有里程碑的意義
通過@PathVariable可以將URL中占位符參數(shù)綁定到控制器處理方法的入?yún)⒅校篣RL中的{xxx}占位符可以通過@PathVariable("xxx")綁定到操作方法入?yún)⒅小?br> 例子:
/**
* @RequestMapping 可以來映射URL中的占位符到目標(biāo)方法的參數(shù)中
* @param id
* @return
*/
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") String id){
System.out.println("testPathVariable id="+id);
return "index";
}
(二)使用@RequestParam綁定HttpServletRequest請求參數(shù)到控制器方法參數(shù)
@RequestMapping ( "requestParam" )
public String testRequestParam( @RequestParam(required=false) String name, @RequestParam ( "age" ) int age) {
return "requestParam" ;
}
在上面代碼中利用@RequestParam 從HttpServletRequest 中綁定了參數(shù)name 到控制器方法參數(shù)name ,綁定了參數(shù)age 到控制器方法參數(shù)age 。值得注意的是和@PathVariable 一樣,當(dāng)你沒有明確指定從request 中取哪個參數(shù)時,Spring 在代碼是debug 編譯的情況下會默認(rèn)取更方法參數(shù)同名的參數(shù),如果不是debug 編譯的就會報錯。此外,當(dāng)需要從request 中綁定的參數(shù)和方法的參數(shù)名不相同的時候,也需要在@RequestParam 中明確指出是要綁定哪個參數(shù)。在上面的代碼中如果我訪問/requestParam.do?name=hello&age=1 則Spring 將會把request請求參數(shù)name 的值hello 賦給對應(yīng)的處理方法參數(shù)name ,把參數(shù)age 的值1 賦給對應(yīng)的處理方法參數(shù)age 。
在@RequestParam 中除了指定綁定哪個參數(shù)的屬性value 之外,還有一個屬性required ,它表示所指定的參數(shù)是否必須在request 屬性中存在,默認(rèn)是true ,表示必須存在,當(dāng)不存在時就會報錯。在上面代碼中我們指定了參數(shù)name 的required 的屬性為false ,而沒有指定age 的required 屬性,這時候如果我們訪問/requestParam.do而沒有傳遞參數(shù)的時候,系統(tǒng)就會拋出異常,因為age 參數(shù)是必須存在的,而我們沒有指定。而如果我們訪問/requestParam.do?age=1 的時候就可以正常訪問,因為我們傳遞了必須的參數(shù)age ,而參數(shù)name 是非必須的,不傳遞也可以。
(三)使用@CookieValue綁定cookie的值到Controller方法參數(shù)
@RequestMapping ( "cookieValue" )
public String testCookieValue( @CookieValue ( "hello" ) String cookieValue, @CookieValue String hello) {
System. out .println(cookieValue + "-----------" + hello);
return "cookieValue" ;
}
在上面的代碼中我們使用@CookieValue 綁定了cookie 的值到方法參數(shù)上。上面一共綁定了兩個參數(shù),一個是明確指定要綁定的是名稱為hello 的cookie 的值,一個是沒有指定。使用沒有指定的形式的規(guī)則和@PathVariable、@RequestParam 的規(guī)則是一樣的,即在debug 編譯模式下將自動獲取跟方法參數(shù)名同名的cookie 值。
(四)使用@RequestHeader注解綁定 HttpServletRequest頭信息到Controller方法參數(shù)
@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 的方法參數(shù)。上面方法的三個參數(shù)都將會賦予同一個值,由此我們可以知道在綁定請求頭參數(shù)到方法參數(shù)的時候規(guī)則和 @PathVariable 、 @RequestParam 以及 @CookieValue 是一樣的,即沒有指定綁定哪個參數(shù)到方法參數(shù)的時候,在 debug 編譯模式下將使用方法參數(shù)名作為需要綁定的參數(shù)。但是有一點 @RequestHeader 跟另外三種綁定方式是不一樣的,那就是在使用 @RequestHeader 的時候是大小寫不敏感的,即 @RequestHeader(“Host”) 和 @RequestHeader(“host”) 綁定的都是 Host 頭信息。記住在 @PathVariable 、 @RequestParam 和 @CookieValue 中都是大小寫敏感的。
(五)@RequestMapping的一些高級應(yīng)用
在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 屬性指定了三個參數(shù),這些參數(shù)都是針對請求參數(shù)而言的,它們分別表示參數(shù)param1 的值必須等于value1 ,參數(shù)param2 必須存在,值無所謂,參數(shù)param3 必須不存在,只有當(dāng)請求/testParams.do 并且滿足指定的三個參數(shù)條件的時候才能訪問到該方法。所以當(dāng)請求/testParams.do?param1=value1¶m2=value2 的時候能夠正確訪問到該testParams 方法,當(dāng)請求/testParams.do?param1=value1¶m2=value2¶m3=value3 的時候就不能夠正常的訪問到該方法,因為在@RequestMapping 的params 參數(shù)里面指定了參數(shù)param3 是不能存在的。
2.method屬性
@RequestMapping (value= "testMethod" , method={RequestMethod. GET , RequestMethod. DELETE })
public String testMethod() {
return "method" ;
}
在上面的代碼中就使用method 參數(shù)限制了以GET 或DELETE 方法請求/testMethod.do 的時候才能訪問到該Controller 的testMethod 方法。
3.headers屬性
@RequestMapping (value= "testHeaders" , headers={ "host=localhost" , "Accept" })
public String testHeaders() {
return "headers" ;
}
headers 屬性的用法和功能與params 屬性相似。在上面的代碼中當(dāng)請求/testHeaders.do 的時候只有當(dāng)請求頭包含Accept 信息,且請求的host 為localhost 的時候才能正確的訪問到testHeaders 方法。
(六)以@RequestMapping標(biāo)記的處理器方法支持的方法參數(shù)和返回類型
1. 支持的方法參數(shù)類型
HttpServlet 對象,主要包括HttpServletRequest 、HttpServletResponse 和HttpSession 對象。 這些參數(shù)Spring 在調(diào)用處理器方法的時候會自動給它們賦值,所以當(dāng)在處理器方法中需要使用到這些對象的時候,可以直接在方法上給定一個方法參數(shù)的申明,然后在方法體里面直接用就可以了。但是有一點需要注意的是在使用HttpSession 對象的時候,如果此時HttpSession 對象還沒有建立起來的話就會有問題。
Spring 自己的WebRequest 對象。 使用該對象可以訪問到存放在HttpServletRequest 和HttpSession 中的屬性值。
InputStream 、OutputStream 、Reader 和Writer 。 InputStream 和Reader 是針對HttpServletRequest 而言的,可以從里面取數(shù)據(jù);OutputStream 和Writer 是針對HttpServletResponse 而言的,可以往里面寫數(shù)據(jù)。
使用@PathVariable 、@RequestParam 、@CookieValue 和@RequestHeader 標(biāo)記的參數(shù)。
使用@ModelAttribute 標(biāo)記的參數(shù)。
java.util.Map 、Spring 封裝的Model 和ModelMap 。 這些都可以用來封裝模型數(shù)據(jù),用來給視圖做展示。
實體類。 可以用來接收上傳的參數(shù)。
Spring 封裝的MultipartFile 。 用來接收上傳文件的。
Spring 封裝的Errors 和BindingResult 對象。 這兩個對象參數(shù)必須緊接在需要驗證的實體對象參數(shù)之后,它里面包含了實體對象的驗證結(jié)果。
2. 支持的返回類型
一個包含模型和視圖的ModelAndView 對象。
一個模型對象,這主要包括Spring 封裝好的Model 和ModelMap ,以及java.util.Map ,當(dāng)沒有視圖返回的時候視圖名稱將由RequestToViewNameTranslator 來決定。
一個View 對象。這個時候如果在渲染視圖的過程中模型的話就可以給處理器方法定義一個模型參數(shù),然后在方法體里面往模型中添加值。
一個String 字符串。這往往代表的是一個視圖名稱。這個時候如果需要在渲染視圖的過程中需要模型的話就可以給處理器方法一個模型參數(shù),然后在方法體里面往模型中添加值就可以了。
返回值是void 。這種情況一般是我們直接把返回結(jié)果寫到HttpServletResponse 中了,如果沒有寫的話,那么Spring 將會利用RequestToViewNameTranslator 來返回一個對應(yīng)的視圖名稱。如果視圖中需要模型的話,處理方法與返回字符串的情況相同。
如果處理器方法被注解@ResponseBody 標(biāo)記的話,那么處理器方法的任何返回類型都會通過HttpMessageConverters 轉(zhuǎn)換之后寫到HttpServletResponse 中,而不會像上面的那些情況一樣當(dāng)做視圖或者模型來處理。
除以上幾種情況之外的其他任何返回類型都會被當(dāng)做模型中的一個屬性來處理,而返回的視圖還是由RequestToViewNameTranslator 來決定,添加到模型中的屬性名稱可以在該方法上用@ModelAttribute(“attributeName”) 來定義,否則將使用返回類型的類名稱的首字母小寫形式來表示。使用@ModelAttribute 標(biāo)記的方法會在@RequestMapping 標(biāo)記的方法執(zhí)行之前執(zhí)行。
(七)使用 @ModelAttribute 和 @SessionAttributes 傳遞和保存數(shù)據(jù)
SpringMVC 支持使用 @ModelAttribute 和 @SessionAttributes 在不同的模型和控制器之間共享數(shù)據(jù)。 **@ModelAttribute **主要有兩種使用方式,一種是標(biāo)注在方法上,一種是標(biāo)注在 Controller 方法參數(shù)上。
當(dāng) @ModelAttribute 標(biāo)記在方法上的時候,該方法將在處理器方法執(zhí)行之前執(zhí)行,然后把返回的對象存放在 session 或模型屬性中,屬性名稱可以使用 @ModelAttribute(“attributeName”) 在標(biāo)記方法的時候指定,若未指定,則使用返回類型的類名稱(首字母小寫)作為屬性名稱。關(guān)于 @ModelAttribute 標(biāo)記在方法上時對應(yīng)的屬性是存放在 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" );
}
}
當(dāng)我們請求 /myTest/sayHello.do 的時候使用 @ModelAttribute 標(biāo)記的方法會先執(zhí)行,然后把它們返回的對象存放到模型中。最終訪問到 sayHello 方法的時候,使用 @ModelAttribute 標(biāo)記的方法參數(shù)都能被正確的注入值。執(zhí)行結(jié)果如下所示:
Hello world,Hello user210
由執(zhí)行結(jié)果我們可以看出來,此時 session 中沒有包含任何屬性,也就是說上面的那些對象都是存放在模型屬性中,而不是存放在 session 屬性中。那要如何才能存放在 session 屬性中呢?這個時候我們先引入一個新的概念 @SessionAttributes ,它的用法會在講完 @ModelAttribute 之后介紹,這里我們就先拿來用一下。我們在 MyController 類上加上 @SessionAttributes 屬性標(biāo)記哪些是需要存放到 session 中的??聪旅娴拇a:
@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中,利用上面的代碼當(dāng)我們訪問 /myTest/sayHello.do 的時候,結(jié)果如下:
Hello world,Hello user210
仍然沒有打印出任何 session 屬性,這是怎么回事呢?怎么定義了把模型中屬性名為 intValue 的對象和類型為 User 的對象存到 session 中,而實際上沒有加進(jìn)去呢?難道我們錯啦?我們當(dāng)然沒有錯,只是在第一次訪問 /myTest/sayHello.do 的時候 @SessionAttributes 定義了需要存放到 session 中的屬性,而且這個模型中也有對應(yīng)的屬性,但是這個時候還沒有加到 session 中,所以 session 中不會有任何屬性,等處理器方法執(zhí)行完成后 Spring 才會把模型中對應(yīng)的屬性添加到 session 中。所以當(dāng)請求第二次的時候就會出現(xiàn)如下結(jié)果:
Hello world,Hello user210
user2
intValue
stringValue
當(dāng) @ModelAttribute 標(biāo)記在處理器方法參數(shù)上的時候,表示該參數(shù)的值將從模型或者 Session 中取對應(yīng)名稱的屬性值,該名稱可以通過 @ModelAttribute(“attributeName”) 來指定,若未指定,則使用參數(shù)類型的類名稱(首字母小寫)作為屬性名稱。
總結(jié)
到此,SpringMVC的原理以及常用注解就介紹的差不多了,平時開發(fā)這些就夠用了,如果你還想深入學(xué)習(xí)SpringMVC知識點,可以關(guān)注我個人公眾號,里面資源貼有全套的視頻教程。
參考
Spring常用注解
@AUTOWIRED與@RESOURCE的區(qū)別
SpringMVC Controller介紹及常用注解
一直覺得自己寫的不是技術(shù),而是情懷,一篇篇文章是自己這一路走來的痕跡??繉I(yè)技能的成功是最具可復(fù)制性的,希望我的這條路能讓你少走彎路,希望我能幫你抹去知識的蒙塵,希望我能幫你理清知識的脈絡(luò),希望未來技術(shù)之巔上有你也有我。