本來地址:springMVC乾貨系列:從零搭建springMVC+mybatis(二):springMVC原理解析及經常使用註解
博客地址:tengj.top/php
上篇文章介紹了maven WEB 項目的搭建,基本的配置文件也都貼出來了,今天就來介紹下SpringMVC的工做原理以及工做中經常使用的註解。爲之後開發打下堅實的基礎。html
SpringMVC就是經過DispatcherServlet將一堆組件串聯起來的Web框架。前端
Spring 框架提供了構建 Web 應用程序的全功能 MVC 模塊。使用 Spring 可插入的 MVC 架構,能夠選擇是使用內置的 Spring Web 框架仍是 Struts 這樣的 Web 框架。經過策略接口,Spring 框架是高度可配置的,並且包含多種視圖技術,例如 JavaServer Pages(JSP)技術、Velocity、Tiles、iText 和 POI。Spring MVC 框架並不知道使用的視圖,因此不會強迫您只使用 JSP 技術。
Spring MVC 分離了控制器、模型對象、分派器以及處理程序對象的角色,這種分離讓它們更容易進行定製java
DispatcherServlet接口:
Spring提供的前端控制器,全部的請求都有通過它來統一分發。在DispatcherServlet將請求分發給Spring Controller以前,須要藉助於Spring提供的HandlerMapping定位到具體的Controller。web
HandlerMapping接口:
可以完成客戶請求到Controller映射。spring
Controller接口:
須要爲併發用戶處理上述請求,所以實現Controller接口時,必須保證線程安全而且可重用。安全
Controller將處理用戶請求,這和Struts Action扮演的角色是一致的。一旦Controller處理完用戶請求,則返回ModelAndView對象給DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和視圖(View)。cookie
從宏觀角度考慮,DispatcherServlet是整個Web應用的控制器;從微觀考慮,Controller是單個Http請求處理過程當中的控制器,而ModelAndView是Http請求過程當中返回的模型(Model)和視圖(View)。session
ViewResolver接口:
Spring提供的視圖解析器(ViewResolver)在Web應用中查找View對象,從而將相應結果渲染給客戶。mybatis
客戶端請求提交到DispatcherServlet
由DispatcherServlet控制器查詢一個或多個HandlerMapping,找處處理請求的Controller
DispatcherServlet將請求提交到Controller
Controller調用業務邏輯處理後,返回ModelAndView
DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖
視圖負責將結果顯示到客戶端
DispatcherServlet是整個Spring MVC的核心。它負責接收HTTP請求組織協調Spring MVC的各個組成部分。其主要工做有如下三項:
結合項目理解:
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
是全部受Spring 管理組件的通用形式,@Component註解能夠放在類的頭上,@Component不推薦使用。
@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("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(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;複製代碼
上面介紹中Controller中注入userService或者 Service層裏面注入dao都是用@Resource標籤,其實也可使用@Autowired來替代,可是建議使用@Resource。下面說說這2者的區別:
@Autowired
@Qualifier("baseDao")
private BaseDao baseDao;複製代碼
@Resource(name="baseDao")
private BaseDao baseDao;複製代碼
5.之因此推薦使用@Resource,由於這個註解是屬於J2EE的,減小了與spring的耦合。這樣代碼看起就比較優雅。SpringMVC使用@RequestMapping註解爲控制器制定能夠處理哪些URL請求
在控制器的類定義及方法定義處均可以標註
@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。 帶佔位符的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";
}複製代碼
@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 值。
@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 中除了指定請求路徑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¶m2=value2 的時候可以正確訪問到該testParams 方法,當請求/testParams.do?param1=value1¶m2=value2¶m3=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 方法。
HttpServlet 對象,主要包括HttpServletRequest 、HttpServletResponse 和HttpSession 對象。 這些參數Spring 在調用處理器方法的時候會自動給它們賦值,因此當在處理器方法中須要使用到這些對象的時候,能夠直接在方法上給定一個方法參數的申明,而後在方法體裏面直接用就能夠了。可是有一點須要注意的是在使用HttpSession 對象的時候,若是此時HttpSession 對象尚未創建起來的話就會有問題。
Spring 本身的WebRequest 對象。 使用該對象能夠訪問到存放在HttpServletRequest 和HttpSession 中的屬性值。
InputStream 、OutputStream 、Reader 和Writer 。 InputStream 和Reader 是針對HttpServletRequest 而言的,能夠從裏面取數據;OutputStream 和Writer 是針對HttpServletResponse 而言的,能夠往裏面寫數據。
使用@PathVariable 、@RequestParam 、@CookieValue 和@RequestHeader 標記的參數。
使用@ModelAttribute 標記的參數。
java.util.Map 、Spring 封裝的Model 和ModelMap 。 這些均可以用來封裝模型數據,用來給視圖作展現。
實體類。 能夠用來接收上傳的參數。
Spring 封裝的MultipartFile 。 用來接收上傳文件的。
Spring 封裝的Errors 和BindingResult 對象。 這兩個對象參數必須緊接在須要驗證的實體對象參數以後,它裏面包含了實體對象的驗證結果。
一個包含模型和視圖的ModelAndView 對象。
一個模型對象,這主要包括Spring 封裝好的Model 和ModelMap ,以及java.util.Map ,當沒有視圖返回的時候視圖名稱將由RequestToViewNameTranslator 來決定。
一個View 對象。這個時候若是在渲染視圖的過程當中模型的話就能夠給處理器方法定義一個模型參數,而後在方法體裏面往模型中添加值。
一個String 字符串。這每每表明的是一個視圖名稱。這個時候若是須要在渲染視圖的過程當中須要模型的話就能夠給處理器方法一個模型參數,而後在方法體裏面往模型中添加值就能夠了。
返回值是void 。這種狀況通常是咱們直接把返回結果寫到HttpServletResponse 中了,若是沒有寫的話,那麼Spring 將會利用RequestToViewNameTranslator 來返回一個對應的視圖名稱。若是視圖中須要模型的話,處理方法與返回字符串的狀況相同。
若是處理器方法被註解@ResponseBody 標記的話,那麼處理器方法的任何返回類型都會經過HttpMessageConverters 轉換以後寫到HttpServletResponse 中,而不會像上面的那些狀況同樣當作視圖或者模型來處理。
除以上幾種狀況以外的其餘任何返回類型都會被當作模型中的一個屬性來處理,而返回的視圖仍是由RequestToViewNameTranslator 來決定,添加到模型中的屬性名稱能夠在該方法上用@ModelAttribute(「attributeName」) 來定義,不然將使用返回類型的類名稱的首字母小寫形式來表示。使用@ModelAttribute 標記的方法會在@RequestMapping 標記的方法執行以前執行。
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介紹及經常使用註解
一直以爲本身寫的不是技術,而是情懷,一篇篇文章是本身這一路走來的痕跡。靠專業技能的成功是最具可複製性的,但願個人這條路能讓你少走彎路,但願我能幫你抹去知識的蒙塵,但願我能幫你理清知識的脈絡,但願將來技術之巔上有你也有我。