Spring mvc框架入門
Spring mvc在Spring特征里面處于滿核心的地位,在官網上的對Spring 特征(FEATURES)羅列中,對應這是“MODERN WEB”(現代web),也就是Spring特征的第一項。也反映了其重要作用,另一方面也是因為mvc在Spring項目中誕生比較早。
只要是同網絡應用相關的,無論是有同用戶互動的(帶UI的)或者沒有互動的情況,spring mvc都是種成熟、功能齊全的架構。閱讀下面的內容需要對java、spring(依賴注入dependency injection)、servlet需要有些基本的了解。
Spring的MVC框架如下圖所示:
在Spring MVC中前端的控制器就是DispatcherServlet
這個Servlet來掌管著用戶的請求及最后的系統回應。這個DispatcherServlet
同具體的業務邏輯一點都不著邊,而是把所有的事情委派給控制器去做(Controller),當然DispatcherServlet
是知道該把當前的事情交個那個控制器去做,這個后面會講;然后當控制器把事情都做完了后,這個時候輪到視圖(View)上場了,簡單的理解好比我們做PPT,那么這里的視圖好比PPT里面的模板,它可以把數據以不同的展現形式交給客戶,可以是jsp、xml、json等等。下面來看看如何具體實現的:
1、Servlet部分,上面說到前端控制器是DispatcherServlet
這個Servlet,那很顯然需要在web.xml中加入一個servlet,然后把用戶的請求都讓DispatcherServlet去處理,下面是web.xml文件:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee ;<!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
</web-app></pre>這里有個地方就是contextConfigLocation,這個是個初始化參數(init-param),在servlet進行初始化的時候可以設置它的值,而這個值定義了spring應用上下文(context—上下文,指的是一種環境,主要是各種bean,可以理解為各種component)的配置文件(XML格式)的位置,這個上下文會被DispatcherServlet加載進來,這樣Dispatcher工作起來時會依照這個上下文的內容進行分配任務。具體原理可以參考下面這個圖:
關于上下文的配置,這里使用的是xml的配置方式,上面代碼指定了配置文件是/WEB-INF/spring/appServlet/servlet-context.xml,這個也可以使用java的配置方式,這里需要在DispatcherServlet的初始參數中加入AnnotationConfigWebApplicationContext。另外在下面的例子中我們會看到組件即可以在XML中配置但也可以使用java代碼來做。接下來看看第一個controller
2、Controller類,下面是一個最mini的控制類了:
package xyz.sample.baremvc;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;
/**
Handles requests for the application home page. */ @Controller public class HomeController {
@RequestMapping(value = "/") public String home() {
System.out.println("HomeController: Passing through..."); return "WEB-INF/views/home.jsp";
} }</pre>上面這個類中有幾個注意的地方:
1、使用了@Controller這個annotation(這個可以翻譯成“注釋”,表示下面的類的作用),來表示HomeController這個類是作為Spring MVC中的Controller(控制器),根據上面的那個圖也就是表示這個類具有處理用戶請求的能力。另外@Controller是@Component這個annotation中的特定annotation(有點類似@Component是@Controller的父類一樣,@Component相當于抽象程度比較高,@Controller是屬于一種特定的@Component),所以這里HomeController也是作為一個組件(Component),在Spring初始化掃描的時候它會被自動檢測到并且加入到Spring container中(Spring容器或者叫Spring上下文,都是類似的概念),然后生成對應的類實例,最后像其他任何Spring組件一樣允許注入到系統中。
2、home這個方法使用了@RequestMapping這個注釋,表示home這個方法可以用來處理對應于路徑“/”(根路徑)的用戶請求
3、這里home的處理就是在log中打印一個字符串,然后返回WEB-INF/views/home.jsp,這個是交給View去處理的,這里就是個jsp文件,默認情況下,如果我們沒有設置特定的View的話,Spring會使用默認的View來處理WEB-INF/views/home.jsp這個Response(回應);對應View在后面將詳細講,這里只要知道View會把系統的home.jsp這個文件呈現給客戶就好
下面是對應的home.jsp文件,文件很簡單,只是個Hello:
<%@ taglib uri="<title>Home</title>
</head> <body>
<h1>Hello world!</h1>
</body> </html></pre>3、配置Spring上下文,上面在Servlet中提到的Spring上下文還沒有創建(/WEB-INF/spring/appServlet/servlet-context.xml),看看下面的代碼:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Scans within the base package of the application for @Components to configure as beans --> <!-- @Controller, @Service, @Configuration, etc. --> <context:component-scan base-package="xyz.sample.baremvc" />
<!-- Enables the Spring MVC @Controller programming model --> <mvc:annotation-driven />
</beans></pre> 1、看到這里xml的根元素是<beans>,所以這里主要涉及創建各種bean,這里的bean其實就是component了(組件)。
2、這里一共有三個名字空間,第一個是默認的beans、然后是mvc和context。
3、<context:component-scan/>表示要求Spring 容器(Spring container)對類進行掃描、檢查,這樣所有代碼中包含有屬于@Component子類型的注釋(例如上面的@Controller就是屬于@Component的子類型)都會被檢查到,然后為了提高效率,這里自動檢查的范圍只在xyz..sample.baremvc這個包下面進行。
4、<mvc:annotation-driven />,表示可以使用annotation來進行工作,也就是支持Spring MVC將請求轉給@Controller去處理;當然還可以涉及到如轉換、格式化、有效性
通過上面的幾個步驟后,啟動服務器,在地址欄輸入
http://localhost:8080/baremvc
,可以看到下面的效果:
上面的這個例子十分簡單吧,但是卻涉及到了Spring MVC應用的關鍵部分,來看看這個應用的調用流程:
A、當Tomcat啟動,我們的Spring MVC應用被啟動起來,DispatcherServlet被加載進來,這是因為我們在Web.xml中定了這個Servlet。
B、DispatcherServlet去加載基于注釋(annotation)的Spring應用上下文,這里是通過對指定的包(指定的包可以使用正則表達式)進行scan的方式來獲得帶注釋的組件。
C、這些帶注釋的組件被Spring 的 container檢測到。
D、
http://localhost:8080/baremvc
的HTTP請求匹配了我們DispatcherServlet中的servlet-mapping,所以DispatcherServlet將去處理這個HTTP請求E、在上面的HTTP請求中暗示著用戶的請求路徑為“/”,然后DispatcherServlet發現他里面有相應該路徑請求的映射(這個其實是我們第二張圖里面的那個Handler Mapping)
F、Dispatcher進行決定要如何處理請求,它使用到的策略叫做
HandlerAdapter
,這個可以這么理解,對于request(請求)來說必須要有對應的handler(處理辦法)去處理,而這些handler可能根據當前所處的不同情況,而使用不同類別的handler,所以這里還需要一個HandlerAdapter來作為不同情況下的來轉換不同類別的handler去處理request。這個HandlerAdapter(或者多個,可以將它們鏈接起來)我們可以去指定,但是沒有指定的情況下將使用系統默認的HandlerAdapter,這里就是使用注釋驅動(annotation-based)的策略了。這里就是把請求轉給@Controller,然后找到匹配對應路徑的@RequestMapping方法,將輸入的參數給該方法,并讓該方法處理請求。使用其他HandlerAdapter的例子還沒做,但是暫時我們不去關心這些,只要有個了解就ok了G、然后home這個方法做它的事情,打印一個log,然后返回一個字符串,這個字符串暗示了要呈現給用戶的東西,也幫助View選擇合適呈現方式。
H、然后從第一個圖中我們知道,Controller做完事情后,任務又交到DispatcherServlet手上了,對于如何選擇合適的視圖,同樣DispatcherServlet需要策略,這次的策略叫做ViewResolver,通過這個ViewResolver(視圖仲裁員)去仲裁那個視圖(View)將回應呈現給用戶,這個ViewResolver也是可以自己定義的。如果默認沒有配置的情況下系統將使用
InternalResourceViewResolver
,這個ViewResolver將創建一個JSTLView,而這個View只是簡單的把任務委托給Servlet內部的RequestDispatcher
去呈現資源,所以這種View很適合于使用JSP或者HTML頁面的情況I、最后Servlet將回應通過制定的jsp呈現給用戶
4、對View進行抽象,讓Controller返回一個純粹的邏輯性的名稱。
上面的home函數中我們看到返回的是個純粹的物理地址,為了減少耦合,讓應用有更好的彈性,當然是不能使用這種硬編碼的方式;要讓Controller返回一個邏輯的名稱,而最終的物理路徑由View來決定,這樣讓Controller只關心邏輯上的處理,把展示留給View去做,真正做到C和V分離。看下改造了得Controller:
@Controller public class HomeController {@RequestMapping(value = "/") public String home() { System.out.println("HomeController: Passing through..."); return "home"; }
}</pre>上面home函數里面返回一個邏輯名稱“home”,也就是好像告訴系統需要調用一個叫做“home“的view給用戶,但是具體這個”home“的view具體是什么樣子完全由系統自己去決定,當然我們知道這里是有ViewResolver去決定的。當然我們可以把這些View的名稱統一到一起,上面的這種直接”home“還是有點”生硬“。
接下來我們要做的就是把”home“這個名稱映射到”WEB-INF/views/home.jsp“這個文件上面。上面我們說過默認情況下這種映射是通過
InternalResourceViewResolver
類來處理的。我們可以使用自己的ViewResolver來處理這種邏輯到物理的映射,但是這個例子里面我們只需要對InternalResourceViewResolver
做一點點調整就可以達到我們的目的了。
InternalResourceViewResolver
對Controller返回過來(其實最后是DispatcherServlet發過來的)的名稱,只是在其前后加上可選的前后綴(默認情況為空),然后把這個字符串交給由它自己創建的JSTLView,然后JSTLView再委托Servlet引擎中的RequestDispatcher去干真正的活,呈現視圖模板,這里是jsp做視圖的模板。這個例子里比較簡單,只要對View的名稱返回增加相應的前后綴就可以了,前綴”WEB-INF/views/“,后綴".jsp"。一種簡單的配置ViewResolver的方式是使用以java為基本的容器配置(另外就是以XML為基礎的容器配置/上下文配置),把我們的Resolver定義為一個Bean,下面是對應的代碼:package xyz.sample.baremvc;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration public class AppConfig {
// Resolve logical view names to .jsp resources in the /WEB-INF/views directory @Bean ViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("WEB-INF/views/"); resolver.setSuffix(".jsp"); return resolver; }
}</pre>上面的代碼需要cglib庫的支持,這個可以網上下載也可以到我的資源里去下 cglib。
由于@Configuration也是一個@Component,所以掃描的時候也會被掃描到,然后這個配置組件(Configuration)會被加入到Spring容器中,Spring容器還會掃描所有的Bean并發現ViewResolver(也就是我們的容器里多了個由viewResolver函數里面創建的resolver,作為一個bean),然后使用這個ViewResolver來處理邏輯的View名稱。
上面的使用的是java的方式,有些人喜歡xml的方式,如下面:
<!-- Resolve logical view names to .jsp resources in the /WEB-INF/views directory --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean>這個效果也是一樣的創建一個InternalResourceViewResolver類型的bean,設置其bean的屬性用來處理View名稱。5、處理用戶輸入
我們總是要和用戶互動的,讓我們看看如何處理些簡單的用戶輸入,然后再輸出結果給用戶。在Spring MVC中有多種輸入、輸出的方式,讓我們看看最簡單的一種。這里我們把上面的HomeController中增加一個的處理請求的方法,讓它能獲得兩個用戶輸入的字符,然后進行比較,并且把結果通過jsp輸出給用戶,看看增加的方法:
package xyz.sample.baremvc;import java.util.Comparator;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam;
/**
Handles requests for the application home page. */ @Controller public class HomeController {
@Autowired Comparator<String> comparator;
@RequestMapping(value = "/") public String home() {
System.out.println("HomeController: Passing through..."); return "home";
}
@RequestMapping(value = "/compare", method = RequestMethod.GET) public String compare(@RequestParam("input1") String input1,
@RequestParam("input2") String input2, Model model) { int result = comparator.compare(input1, input2); String inEnglish = (result < 0) ? "less than" : (result > 0 ? "greater than" : "equal to"); String output = "According to our Comparator, '" + input1 + "' is " + inEnglish + "'" + input2 + "'"; model.addAttribute("output", output); return "compareResult";
} }</pre> 1、增加了一個compare函數,當用戶請求”/compare“這個路徑的時候,這個函數會用來處理用戶請求
2、這個函數希望用戶通過GET請求的方式傳入兩個字符作為其參數,如果用戶沒有傳兩個指定名稱(input1和input2)的字符串來,則會提示HTTP 400的錯誤,然后這里compare函數獲得用戶GET請求中的參數是通過@RequestParam這個注釋來實現的。
3、我們使用自己定義的比較器來比較這兩個字符串。這里注意到@Autowired,也就Spring通過在container中尋找類型同comparator相同(Comparator<String>)的組件,然后wired(連接)到comparator上面(可以理解為調用setComparator這樣)
4、最后我們使用了Model來放置比較的結果,這樣View將可以獲得放在Model中的結果,這里只要把Model看做一個HashMap就可以了,名稱->數據的一個對應。放到Model中的數據主要為了View能夠訪問這些有用的數據。上面的例子中只是個字符串,將來呈現到jsp上面。
我們創建一個對應的jsp文件來顯示結果,
WEB-INF/views/compareResult.jsp
:<%@ taglib uri="<title>Result</title>
</head> <body>
<h1><c:out value="${output}"></c:out></h1>
</body> </html></pre>現在我們還需要創建一個給Controller中comparator注入的組件,下面是這個組件的代碼:
package xyz.sample.baremvc;import java.util.Comparator; import org.springframework.stereotype.Component;
@Service public class CaseInsensitiveComparator implements Comparator<String> { public int compare(String s1, String s2) { assert s1 != null && s2 != null; return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); } }</pre>這個比較器是個很簡單的比較器,實現了Comparator<String>這個接口,這樣就可以注入到Controller中comparator去了。因為@Service也是@Component的一種,因此也是可以被自動掃描到的。當然我們也注意到了,有人要問這個可以不可以通過上面的@Configuration中的@Bean定義來做,當然答案是肯定得,同樣也是可以在XML中進行定義的。不同的定義方式讓這些類提供的控制層次會有高低不同之分。這個應該參考對應的annotation。
現在我們可以輸入一個地址看看
http://localhost:8080/baremvc/compare?input1=Donkey&input2=dog
:
最后想了解更多Spring MVC的特性的話,可以看看這個 show case
轉自:http://blog.csdn.net/rdarda/article/details/7922154