
1、Spring MVC請求流程
- (1)初始化:(對DispatcherServlet和ContextLoderListener進(jìn)行初始化),讀取web.xml,根據(jù)web.xml中指定的xml配置文件(spring/mvc.xml | spring/app.xml),引入MVC相關(guān)配置(路由映射規(guī)則、視圖模板配置等);引入應(yīng)用上下文Context相關(guān)配置(數(shù)據(jù)源、DAO、Service等非web層組件)
- (2)客戶端http請求
- (3)請求交由前端控制器DispatcherServlet處理,DispatcherServlet根據(jù)Request的url在HandlerMapping中找到對應(yīng)的HandlerExecutionChain
- (4)在執(zhí)行鏈HandlerExecutionChain中找到HandlerAdapter;這里為什么不是 DispatcherServlet 直接把請求傳遞給Handler(即某某Controller),而是給HandlerAdapter代理呢?因為controller有多種實現(xiàn)方式,HandlerAdapter從中間代理可以簡化很多操作,比如數(shù)據(jù)綁定、數(shù)據(jù)校驗等;
- (5)HandlerAdapter進(jìn)行數(shù)據(jù)適配,將請求傳遞給具體的Controller;
- (6)由我們寫的Controller進(jìn)行數(shù)據(jù)操作,得到model后進(jìn)行返回;這里還要經(jīng)過ReturnValueHandler和HttpMessageConvert處理,如果controller返回的是json數(shù)據(jù)而不需要進(jìn)行視圖渲染,則在ReturnValueHandler中進(jìn)行設(shè)置
- (7)根據(jù)視圖模型名稱傳遞給DispatcherServlet
- (8)將ModelAndView傳給ViewResolver進(jìn)行視圖解析
- (9)視圖解析器返回解析好的視圖模板文件
- (10)將視圖模板文件傳遞給View進(jìn)行處理
- (11)視圖渲染
- (12)響應(yīng)請求
2、配置文件結(jié)構(gòu)(建議)

3、筆記



4、注解
參考: Spring 注解總結(jié)
為什么會有注解?
注解可以簡化借助xml對bean的配置工作:通過在類、方法上加上注解和相應(yīng)的注解屬性,再配置spring要掃描的包路徑,spring將會把合適的java類全部注冊成spring Bean。
注解實現(xiàn)Bean配置主要用來進(jìn)行如依賴注入、生命周期回調(diào)方法定義等,不能消除XML文件中的Bean元數(shù)據(jù)定義,且基于XML配置中的依賴注入的數(shù)據(jù)將覆蓋基于注解配置中的依賴注入的數(shù)據(jù)。
Spring3支持的注解類型
Spring3的基于注解實現(xiàn)Bean依賴注入支持如下4種注解:
-
Spring自帶依賴注入注解: Spring自帶的一套依賴注入注解;
@Required:依賴檢查;
@Autowired:自動裝配,用于替代基于XML配置的自動裝配;基于@Autowired的自動裝配,默認(rèn)是根據(jù)類型注入,可以用于構(gòu)造器、字段、方法注入
@Value:注入SpEL表達(dá)式;用于注入SpEL表達(dá)式,可以放置在字段方法或參數(shù)上@Value(value = "SpEL表達(dá)式") @Value(value = "#{message}")@Qualifier限定描述符除了能根據(jù)名字進(jìn)行注入,還能進(jìn)行更細(xì)粒度的控制如何選擇候選者
@Qualifier(value = "限定標(biāo)識符") -
JSR-250注解:Java平臺的公共注解,是Java EE 5規(guī)范之一,在JDK6中默認(rèn)包含這些注解,從Spring2.5開始支持;
@Resource:自動裝配,默認(rèn)根據(jù)類型裝配,如果指定name屬性將根據(jù)名字裝配,可以使用如下方式來指定字段或setter方法:.@Resource(name = "標(biāo)識符")@PostConstruct和PreDestroy:通過注解指定初始化和銷毀方法定義
JSR-330注解:Java 依賴注入標(biāo)準(zhǔn),Java EE 6規(guī)范之一,可能在加入到未來JDK版本,從Spring3開始支持;
@Inject:等價于默認(rèn)的@Autowired,只是沒有required屬性
@Named:指定Bean名字,對應(yīng)于Spring自帶@Qualifier中的缺省的根據(jù)Bean名字注入情況
@Qualifier:只對應(yīng)于Spring自帶@Qualifier中的擴(kuò)展@Qualifier限定描述符注解,即只能擴(kuò)展使用,沒有value屬性JPA注解: JPA全稱Java Persistence API,JPA通過JDK 5.0注解或XML描述對象-關(guān)系表的映射關(guān)系,并將運(yùn)行期的實體對象持久化到數(shù)據(jù)庫中。用于注入持久化上下文和實體管理器。參考:JPA常用注解
@Entity 標(biāo)識這個pojo是一個jpa實體
@Entity @Table(name = "users") 指定表名為users
@Id 注解在屬性上,設(shè)為主鍵
@Column 設(shè)置字段類型
@OrderBy 字段排序
@GeneratedValue 主鍵生成策略
@PersistenceContext 用于注入EntityManagerFactory和EntityManager
@PersistenceUnit
... ...
這4種類型的注解在Spring3中都支持,類似于注解事務(wù)支持,想要使用這些注解需要在Spring容器中**開啟注解驅(qū)動,使用<context:annotation-config />簡化配置 **:
Spring2.1添加了一個新的context的Schema命名空間,該命名空間對注釋驅(qū)動、屬性文件引入、加載期織入等功能提供了便捷的配置。我們知道**注釋本身是不會做任何事情的,它僅提供元數(shù)據(jù)信息。要使元數(shù)據(jù)信息真正起作用,必須讓負(fù)責(zé)處理這些元數(shù)據(jù)的處理器工作起來。 **
AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor就是處理這些注釋元數(shù)據(jù)的處理器。但是直接在Spring配置文件中定義這些Bean顯得比較笨拙。Spring為我們提供了一種方便的注冊這些BeanPostProcessor的方式,這就是<context:annotation-config />:
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config />
</beans>
@Autowired,@Qualifier,@Resource
上面三個注解的作用都是對bean進(jìn)行自動裝配;所謂自動裝配,就是容器自動配置bean,而不需手動顯示配置。
自動裝配有4種類型(除了no即不自動裝配):
- byName:根據(jù)與bean的屬性具有相同name(或者id)的其他bean進(jìn)行注入
- byType: 根據(jù)與bean的屬性具有相同類型的其他bean進(jìn)行注入
- constructor:根據(jù)與bean的構(gòu)造函數(shù)參數(shù)有相同類型的bean進(jìn)行注入
- autodetect : 首先嘗試使用constructor進(jìn)行注入,失敗則嘗試使用byType。
@Autowired:
- 可以對成員變量、方法和構(gòu)造函數(shù)進(jìn)行標(biāo)注,來完成自動裝配的工作
- byType進(jìn)行自動裝配,如果出現(xiàn)多個相同的類型,就會拋出BeanCreationException異常,我們可以使用@Qualifier配合@Autowired來解決這些問題。
@Qualifier:進(jìn)行更細(xì)粒度的“候選bean”控制
// 可能存在多個UserDao實例 :這樣,Spring會找到id為userDao的bean進(jìn)行裝配。
@Autowired
public void setUserDao(@Qualifier("userDao") UserDao userDao) {
this.userDao = userDao;
}
// 可能不存在UserDao實例
@Autowired(required = false)
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Resource:推薦使用它來代替Spring專有的@Autowired注解,因為:
- 它默認(rèn)按byName自動注入;
- @Resource有兩個屬性是比較重要的,分別是name和type,Spring將@Resource注解的name屬性解析為bean的名字,而type屬性則解析為bean的類型。所以如果使用name屬性,則使用byName的自動注入策略,而使用type屬性時則使用byType自動注入策略。如果既不指定name也不指定type屬性,這時將通過反射機(jī)制使用byName自動注入策略。
- 裝配順序
如果同時指定了name和type,則從Spring上下文中找到唯一匹配的bean進(jìn)行裝配,找不到則拋出異常;
如果指定了name,則從上下文中查找名稱(id)匹配的bean進(jìn)行裝配,找不到則拋出異常;
如果指定了type,則從上下文中找到類型匹配的唯一bean進(jìn)行裝配,找不到或者找到多個,都會拋出異常;
如果既沒有指定name,又沒有指定type,則自動按照byName方式進(jìn)行裝配;如果沒有匹配,則回退為一個原始類型(UserDao)進(jìn)行匹配,如果匹配則自動裝配
@PostConstruct
在方法上加上注解@PostConstruct,這個方法就會在Bean初始化之后被Spring容器執(zhí)行(注:Bean初始化包括,實例化Bean,并裝配Bean的屬性(依賴注入))。
它的一個典型的應(yīng)用場景是,當(dāng)你需要往Bean里注入一個其父類中定義的屬性,而你又無法復(fù)寫父類的屬性或?qū)傩缘膕etter方法時,如:
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
private SessionFactory mySessionFacotry;
@Resource
public void setMySessionFacotry(SessionFactory sessionFacotry) {
this.mySessionFacotry = sessionFacotry;
}
@PostConstruct
public void injectSessionFactory() {
super.setSessionFactory(mySessionFacotry);
}
...
}
這里通過@PostConstruct,為UserDaoImpl的父類里定義的一個sessionFactory私有屬性,
注入了我們自己定義的sessionFactory(父類的setSessionFactory方法為final,不可復(fù)寫),
之后我們就可以通過調(diào)用super.getSessionFactory()來訪問該屬性了。
@PreDestroy
在方法上加上注解@PreDestroy,這個方法就會在Bean初始化之后被Spring容器執(zhí)行。由于我們當(dāng)前還沒有需要用到它的場景,這里不不去演示。其用法同@PostConstruct。
以上我們介紹了通過@Autowired或@Resource來實現(xiàn)在Bean中自動注入的功能,下面我們將介紹如何注解Bean,從而從XML配置文件中完全移除Bean定義的配置。
@Component(不推薦使用)、@Repository、@Service、@Controller
只需要在對應(yīng)的類上加上一個@Component注解,就將該類定義為一個Bean了:
@Component
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
...
}
使用@Component注解定義的Bean,默認(rèn)的名稱(id)是小寫開頭的非限定類名。如這里定義的Bean名稱就是userDaoImpl。你也可以指定Bean的名稱:
- @Component("userDao")
@Component是所有受Spring管理組件的通用形式
Spring還提供了更加細(xì)化的注解形式:
- @Repository 對應(yīng)存儲層Bean
- @Service 對應(yīng)業(yè)務(wù)層Bean
- @Controller 對應(yīng)展示層Bean(頁面控制器)
這些注解與@Component的語義是一樣的,完全通用,在Spring以后的版本中可能會給它們追加更多的語義。所以,我們推薦使用@Repository、@Service、@Controller來替代@Component。
使用<context:component-scan />讓Bean定義注解工作起來
<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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="com.kedacom.ksoa" />
</beans>
從上面的幾個注解可以看到:原先需要在xml配置文件中定義的bean,現(xiàn)在不需要定義了,而是直接寫在了注解里;所有通過<bean>元素定義Bean的配置內(nèi)容已經(jīng)被移除,如果使其生效,僅需要添加一行<context:component-scan />配置就解決所有問題了——Spring XML配置文件得到了極致的簡化(當(dāng)然配置元數(shù)據(jù)還是需要的,只不過以注釋形式存在罷了)。<context:component-scan />的base-package屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會被處理。
<context:component-scan />還允許定義過濾器將基包下的某些類納入或排除。Spring支持以下4種類型的過濾方式:
(過濾器類型)(表達(dá)式范例)(說明)
注解---org.example.SomeAnnotation---將所有使用SomeAnnotation注解的類過濾出來
類名指定---org.example.SomeClass---過濾指定的類
正則表達(dá)式---com.kedacom.spring.annotation.web..---通過正則表達(dá)式過濾一些類
AspectJ表達(dá)式---org.example..Service+---通過AspectJ表達(dá)式過濾一些類
值得注意的是<context:component-scan />配置項不但啟用了對類包進(jìn)行掃描以實施注釋驅(qū)動Bean定義的功能,同時還啟用了注釋驅(qū)動自動注入的功能(即還隱式地在內(nèi)部注冊了AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor),因此當(dāng)使用<context:component-scan />后,就可以將<context:annotation-config />移除了。
@ModelAttribute
@ModelAttribute一個具有如下三個作用:
① 綁定請求參數(shù)到命令對象:放在功能處理方法的入?yún)⑸蠒r,用于將多個請求參數(shù)綁定到一個命令對象,從而簡化綁定流程,而且自動暴露為模型數(shù)據(jù)用于視圖頁面展示時使用;
public String test1(@ModelAttribute("user") UserModel user)
只是此處多了一個注解@ModelAttribute("user"),
它的作用是將該綁定的命令對象以“user”為名稱添加到模型對象中供視圖頁面展示使用。
我們此時可以在視圖頁面使用${user.username}來獲取綁定的命令對象的屬性。-
②暴露表單引用對象為模型數(shù)據(jù):放在處理器的一般方法(非功能處理方法)上時,是為表單準(zhǔn)備要展示的表單引用對象,如注冊時需要選擇的所在城市等,而且在執(zhí)行功能處理方法(@RequestMapping 注解的方法)之前,自動添加到模型對象中,用于視圖頁面展示時使用;
/**
* 設(shè)置這個注解之后可以直接在前端頁面使用hb這個對象(List)集合
* @return
*/
@ModelAttribute("hb")
public List<String> hobbiesList(){
List<String> hobbise = new LinkedList<String>();
hobbise.add("basketball");
hobbise.add("football");
hobbise.add("tennis");
return hobbise;
}JSP頁面展示出來: <br> 初始化的數(shù)據(jù) : ${hb } <br> <c:forEach items="${hb}" var="hobby" varStatus="vs"> <c:choose> <c:when test="${hobby == 'basketball'}"> 籃球<input type="checkbox" name="hobbies" value="basketball"> </c:when> <c:when test="${hobby == 'football'}"> 足球<input type="checkbox" name="hobbies" value="football"> </c:when> <c:when test="${hobby == 'tennis'}"> 網(wǎng)球<input type="checkbox" name="hobbies" value="tennis"> </c:when> </c:choose> </c:forEach> ③暴露@RequestMapping 方法返回值為模型數(shù)據(jù):放在功能處理方法的返回值上時,是暴露功能處理方法的返回值為模型數(shù)據(jù),用于視圖頁面展示時使用。
public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)
5、Controller的請求映射與RESTful模式
RESTful
REST(英文:Representational State Transfer,簡稱REST)描述了一個架構(gòu)樣式的網(wǎng)絡(luò)系統(tǒng),比如 web 應(yīng)用程序。它首次出現(xiàn)在 2000 年 Roy Fielding 的博士論文中,他是 HTTP 規(guī)范的主要編寫者之一。
原則條件:
REST 指的是一組架構(gòu)約束條件和原則。滿足這些約束條件和原則的應(yīng)用程序或設(shè)計就是 RESTful。
Web 應(yīng)用程序最重要的 REST 原則是,客戶端和服務(wù)器之間的交互在請求之間是無狀態(tài)的。從客戶端到服務(wù)器的每個請求都必須包含理解請求所必需的信息。如果服務(wù)器在請求之間的任何時間點重啟,客戶端不會得到通知。此外,無狀態(tài)請求可以由任何可用服務(wù)器回答,這十分適合云計算之類的環(huán)境。客戶端可以緩存數(shù)據(jù)以改進(jìn)性能。
在服務(wù)器端,應(yīng)用程序狀態(tài)和功能可以分為各種資源。資源是一個有趣的概念實體,它向客戶端公開。資源的例子有:應(yīng)用程序?qū)ο?、?shù)據(jù)庫記錄、算法等等。每個資源都使用 URI (Universal Resource Identifier) 得到一個唯一的地址。所有資源都共享統(tǒng)一的接口,以便在客戶端和服務(wù)器之間傳輸狀態(tài)。使用的是標(biāo)準(zhǔn)的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。Hypermedia 是應(yīng)用程序狀態(tài)的引擎,資源表示通過超鏈接互聯(lián)。
分層系統(tǒng):
另一個重要的 REST 原則是分層系統(tǒng),這表示組件無法了解它與之交互的中間層以外的組件。通過將系統(tǒng)知識限制在單個層,可以限制整個系統(tǒng)的復(fù)雜性,促進(jìn)了底層的獨(dú)立性。
當(dāng) REST 架構(gòu)的約束條件作為一個整體應(yīng)用時,將生成一個可以擴(kuò)展到大量客戶端的應(yīng)用程序。它還降低了客戶端和服務(wù)器之間的交互延遲。統(tǒng)一界面簡化了整個系統(tǒng)架構(gòu),改進(jìn)了子系統(tǒng)之間交互的可見性。REST 簡化了客戶端和服務(wù)器的實現(xiàn)。
我的理解:既然HTTP請求無狀態(tài) -> 那么客戶端請求如何映射到服務(wù)器資源? -> 服務(wù)端通過路徑+請求方法+參數(shù)+提交的內(nèi)容類型+返回的內(nèi)容類型+Header+...來進(jìn)行地址映射,確定一個唯一的響應(yīng)接口。
在Controller中使用@RequestMapping進(jìn)行地址映射
參考:
@RequestMapping 用法詳解之地址映射
@RequestParam @RequestBody @PathVariable 等參數(shù)綁定注解詳解
@RequestMapping是一個用來處理請求地址映射的注解,可用于類(Controller Bean)或該類的方法上。用于類上,表示類中的所有響應(yīng)請求的方法都是以該地址作為父路徑;用于方法上,表示方法響應(yīng)的子路徑以及要響應(yīng)的請求需要具備哪些條件(-->RESTful)。
在具體的方法參數(shù)里,可以使用@RequestParam、 @RequestBody、 @RequestHeader 、 @PathVariable進(jìn)行參數(shù)綁定,將請求參數(shù)作為方法參數(shù)使用(實際上是對Servlet做了封裝,很酷)
// 例子
@Controller
@RequestMapping("/demo")
public class DemoModelController {
/**
* 列表查詢
*
* @param map the map
* @param rowBounds the row bounds
* @return the list
*/
@RequestMapping(value = "/list",
method = {RequestMethod.GET},
consumes = {MediaType.ALL_VALUE},
produces = {MediaType.TEXT_HTML_VALUE})
@ModelAttribute("list")
public List<DemoModel> listView(@RequestParam Map<String,String> map,
RowBounds rowBounds) {
if(rowBounds!=null){
LOG.info("rowbunds.offset = {}",rowBounds.getOffset());
LOG.info("rowbunds.limit = {}",rowBounds.getLimit());
}
if(map!=null){
LOG.info("map = {}",map);
}
return demoModelService.findAll(rowBounds);
}
@RequestMapping注解有六個屬性
這6個屬性可以用來將請求“過濾”(或者說映射)到具體的方法,下面我們把這6個屬性分成三類進(jìn)行說明:
-
value和method
value: 指定請求的實際地址,指定的地址可以是URI Template 模式:value的uri值為以下三類: A) 可以指定為普通的具體值,如 /demo; B) 可以指定為含有某變量的一類值(URI Template Patterns with Path Variables),如 /{id},其中id為log類型; C) 可以指定為含正則表達(dá)式的一類值( URI Template Patterns with Regular Expressions);
method: 指定請求的method類型, GET、POST、PUT、DELETE等
@RequestMapping(value="/{day}", method = RequestMethod.GET)
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
-
consumes和produces
consumes: 指定處理請求的提交內(nèi)容類型(Content-Type),例如application/json, text/html;
produces: 指定返回的內(nèi)容類型,僅當(dāng)request請求頭中的(Accept)類型中包含該指定類型才返回;// cousumes的樣例:方法僅處理request Content-Type為“application/json”類型的請求。 @Controller @RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json") public void addPet(@RequestBody Pet pet, Model model) { // implementation omitted } produces的樣例:方法僅處理request請求中Accept頭中包含了"application/json"的請求,同時暗示了返回的內(nèi)容類型為application/json @Controller @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json") @ResponseBody public Pet getPet(@PathVariable String petId, Model model) { // implementation omitted } params和headers
params: 指定request中必須包含某些參數(shù)值是,才讓該方法處理。params 為請求參數(shù)的數(shù)組 支持一些簡單的表達(dá)式
params={"name", "!id", "name!=James"} 表示必須攜帶name參數(shù) / 不能帶名稱為id的參數(shù) , 而且name的值不能為James 等等表達(dá)式
params = {"name=kobe", "number=23"}) 否則 404錯誤
headers: 指定request中必須包含某些指定的header值,才能讓該方法處理請求。
// params的樣例:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
// headers的樣例:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(value = "/pets", method = RequestMethod.GET, headers="Referer=http://www.ifeng.com/")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
參數(shù)綁定
下面主要講解request數(shù)據(jù)到處理方法參數(shù)數(shù)據(jù)的綁定所用到的注解和什么情形下使用。
@PathVariable
處理requet uri 部分(這里指uri template中variable,不含queryString部分)
當(dāng)使用@RequestMapping URI template 樣式映射時, 即 someUrl/{paramId}, 這時的paramId可通過 @Pathvariable注解綁定它傳過來的值到方法的參數(shù)上。
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}-
@RequestHeader, @CookieValue
處理request header部分,可以把Request請求header部分的值綁定到方法的參數(shù)上;可以使用Map來接收整個Header、Cookie,也可以綁定具體的值:@RequestMapping(value = "/list") @ModelAttribute("list") public List<DemoModel> listView(@RequestHeader Map<String,String> map) { for(Map.Entry entry : map.entrySet()){ System.out.println(entry.getKey() + " <---> " + entry.getValue()); } } @RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { //... } -
@RequestParam, @RequestBody;
處理request body部分@RequestMapping(method = RequestMethod.GET) public String setupForm(@RequestParam("petId") int petId, ModelMap model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute("pet", pet); return "petForm"; } -
@SessionAttributes, @ModelAttribute
處理attribute類型@RequestMapping("/editPet.do") @SessionAttributes("pet") public class EditPetForm { // ... }
6、Controller如何獲取Request參數(shù)
1、利用原有的Servlet方法,使用HttpServletRequest
2、利用@RequestParam注解
@RequestParam("username")String name
當(dāng)username在request中不存在,會拋出異常,可以使用@RequestParam(value="username" required=false default=" 默認(rèn)值")這樣請求有值就取,沒有值就不取。3、使用實體類封裝
將實體類(需要具有setter,getter方法)作為Controller方法的參數(shù),請求參數(shù)與實體類的屬性保持一致,則會完成自動綁定,將請求參數(shù)自動綁定到這個實體類上,方法直接使用即可。
7、請求攔截
Spring MVC中通過配置<mvc:interceptors>來設(shè)置攔截方式,其子標(biāo)簽<mvc:interceptor>下有3種子標(biāo)簽來配置攔截方式:
<mvc:interceptors>
<!-- 日志攔截器 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/static/**" />
<bean class="HandlerInterceptor攔截器" />
</mvc:interceptor>
</mvc:interceptors>
-
mvc:mapping 攔截器路徑配置
要進(jìn)行攔截的路徑
-
mvc:exclude-mapping 攔截器不需要攔截的路徑
例如資源文件等不需要進(jìn)行攔截的,可以在這里進(jìn)行排除
-
<bean class="HandlerInterceptor攔截器" />
SpringMVC 中的Interceptor 攔截器也是相當(dāng)重要和相當(dāng)有用的,它的主要作用是攔截用戶的請求并進(jìn)行相應(yīng)的處理。比如通過它來進(jìn)行權(quán)限驗證,或者是來判斷用戶是否登陸,或者是像12306 那樣子判斷當(dāng)前時間是否是購票時間。
(1)定義Interceptor實現(xiàn)類
Spring MVC 中的Interceptor 攔截請求是通過HandlerInterceptor 來實現(xiàn)的。在Spring MVC 中定義一個Interceptor 非常簡單,主要有兩種方式,第一種方式是要定義的Interceptor類要實現(xiàn)了Spring 的HandlerInterceptor 接口,或者是這個類繼承實現(xiàn)了HandlerInterceptor 接口的類,比如Spring 已經(jīng)提供的實現(xiàn)了HandlerInterceptor 接口的抽象類HandlerInterceptorAdapter ;第二種方式是實現(xiàn)Spring的WebRequestInterceptor接口,或者是繼承實現(xiàn)了WebRequestInterceptor的類。
(2)實現(xiàn)HandlerInterceptor接口
HandlerInterceptor 接口中定義了三個方法,我們就是通過這三個方法來對用戶的請求進(jìn)行攔截處理的。
(1)preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法
顧名思義,該方法將在請求處理之前進(jìn)行調(diào)用。Spring MVC 中的Interceptor 是鏈?zhǔn)降恼{(diào)用的,在一個應(yīng)用中或者說是在一個請求中可以同時存在多個Interceptor 。每個Interceptor 的調(diào)用會依據(jù)它的聲明順序依次執(zhí)行,而且最先執(zhí)行的都是Interceptor 中的preHandle 方法,所以可以在這個方法中進(jìn)行一些前置初始化操作或者是對當(dāng)前請求的一個預(yù)處理,也可以在這個方法中進(jìn)行一些判斷來決定請求是否要繼續(xù)進(jìn)行下去。該方法的返回值是布爾值Boolean 類型的,當(dāng)它返回為false 時,表示請求結(jié)束,后續(xù)的Interceptor 和Controller 都不會再執(zhí)行;當(dāng)返回值為true 時就會繼續(xù)調(diào)用下一個Interceptor 的preHandle 方法,如果已經(jīng)是最后一個Interceptor 的時候就會是調(diào)用當(dāng)前請求的Controller 方法。
(2)postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法
由preHandle 方法的解釋我們知道這個方法包括后面要說到的afterCompletion 方法都只能是在當(dāng)前所屬的Interceptor 的preHandle 方法的返回值為true 時才能被調(diào)用。postHandle 方法,顧名思義就是在當(dāng)前請求進(jìn)行處理之后,也就是Controller 方法調(diào)用之后執(zhí)行,但是它會在DispatcherServlet 進(jìn)行視圖返回渲染之前被調(diào)用,所以我們可以在這個方法中對Controller 處理之后的ModelAndView 對象進(jìn)行操作。postHandle 方法被調(diào)用的方向跟preHandle 是相反的,也就是說先聲明的Interceptor 的postHandle 方法反而會后執(zhí)行,這和Struts2 里面的Interceptor 的執(zhí)行過程有點類型。Struts2 里面的Interceptor 的執(zhí)行過程也是鏈?zhǔn)降模皇窃赟truts2 里面需要手動調(diào)用ActionInvocation 的invoke 方法來觸發(fā)對下一個Interceptor 或者是Action 的調(diào)用,然后每一個Interceptor 中在invoke 方法調(diào)用之前的內(nèi)容都是按照聲明順序執(zhí)行的,而invoke 方法之后的內(nèi)容就是反向的。
(3)afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle,
Exception ex) 方法
該方法也是需要當(dāng)前對應(yīng)的Interceptor 的preHandle 方法的返回值為true 時才會執(zhí)行。顧名思義,該方法將在整個請求結(jié)束之后,也就是在DispatcherServlet 渲染了對應(yīng)的視圖之后執(zhí)行。這個方法的主要作用是用于進(jìn)行資源清理工作的。
/**
* 例子
* version date author
* ──────────────────────────────────
* 1.0 17-3-17 wanlong.ma
* Description: 攔截器 【攔截所有的請求,并通過logger的方式打印到控制臺上,每次請求的ip地址,格式為request ip:{ip}】
* Others:
* Function List:
* History:
*/
public class RequestIpHandlerInterceptor extends HandlerInterceptorAdapter {
private static Logger logger = LoggerFactory.getLogger(RequestIpHandlerInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String ip = RequestUtils.getRemoteHost(request);
logger.info("request ip:{{}}",ip);
return true;
}
}
-
攔截器不攔截靜態(tài)資源的三種處理方式
增加攔截器之后,如果不進(jìn)行相關(guān)設(shè)置,那么一些不需要攔截的對靜態(tài)資源文件的請求也會被攔截。
方案一、攔截器中增加針對靜態(tài)資源不進(jìn)行過濾(涉及spring-mvc.xml)
<!--靜態(tài)資源文件路徑配置-->
<mvc:resources location="/" mapping="/**/*.js"/>
<mvc:resources location="/" mapping="/**/*.css"/>
<mvc:resources location="/assets/" mapping="/assets/**/*"/>
<mvc:resources location="/images/" mapping="/images/*" cache-period="360000"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**/*"/>
<!--不攔截這些靜態(tài)資源請求-->
<mvc:exclude-mapping path="/**/fonts/*"/>
<mvc:exclude-mapping path="/**/*.css"/>
<mvc:exclude-mapping path="/**/*.js"/>
<mvc:exclude-mapping path="/**/*.png"/>
<mvc:exclude-mapping path="/**/*.gif"/>
<mvc:exclude-mapping path="/**/*.jpg"/>
<mvc:exclude-mapping path="/**/*.jpeg"/>
<mvc:exclude-mapping path="/**/*login*"/>
<mvc:exclude-mapping path="/**/*Login*"/>
<bean class="com.luwei.console.mg.interceptor.VisitInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
8、Controller的幾種類型的返回
Spring MVC 支持如下的返回方式:ModelAndView, Model, ModelMap, Map,View, String, void。
(1)返回ModelAndView
-
對于ModelAndView構(gòu)造函數(shù)可以指定返回頁面的名稱,也可以通過setViewName方法來設(shè)置所需要跳轉(zhuǎn)的頁面:
@RequestMapping(value="/index1",method=RequestMethod.GET)
public ModelAndView index(){
ModelAndView modelAndView = new ModelAndView("/user/index");
modelAndView.addObject("name", "xxx");
return modelAndView;
}// 返回的是一個包含模型和視圖的ModelAndView對象 @RequestMapping(value="/index2",method=RequestMethod.GET) public ModelAndView index2(){ ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("name", "xxx"); modelAndView.setViewName("/user/index"); return modelAndView; }
(2)返回Map
- 主要包含Spring封裝好的Model和ModelMap,以及java.util.Map;當(dāng)沒有視圖返回的時候視圖名稱將由requestToViewNameTranslator決定;響應(yīng)的view應(yīng)該也是該請求的view,等同于void返回:
@RequestMapping(value="/index3",method=RequestMethod.GET)
public Map<String, String> index3(){
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "1");
//map.put相當(dāng)于request.setAttribute方法
return map;
}
在jsp頁面中可直通過${key1}獲得到值, map.put()相當(dāng)于request.setAttribute方法。
(3)返回String
指定返回的視圖頁面名稱,結(jié)合設(shè)置的返回地址路徑加上頁面名稱后綴即可訪問到;例如下面的例子將返回到視圖 hello.vm(.jsp...):
@RequestMapping(value="/showdog")
public String hello1(){
return "hello";
}如果方法聲明了注解@ResponseBody ,則會直接將返回值輸出到頁面:
@RequestMapping(value="/print")
@ResponseBody
public String print(){
String message = "Hello World, Spring MVC!";
return message;
}
(4)返回void
如果返回值為空,則響應(yīng)的視圖頁面對應(yīng)為訪問地址:
@RequestMapping("/index")
public void index() {
return;
}
小結(jié)
- 1、使用 String 作為請求處理方法的返回值類型是比較通用的方法,這樣返回的邏輯視圖名不會和請求 URL 綁定,具有很大的靈活性,而模型數(shù)據(jù)又可以通過 ModelMap 控制。
- 2、使用void,map,Model 時,返回對應(yīng)的邏輯視圖名稱真實url為:prefix前綴+視圖名稱 +suffix后綴組成。
- 3、使用String,ModelAndView返回視圖名稱可以不受請求的url綁定,ModelAndView可以設(shè)置返回的視圖名稱。
9、數(shù)據(jù)格式轉(zhuǎn)換
- 日期格式轉(zhuǎn)換
- 數(shù)字格式轉(zhuǎn)換
- 類型轉(zhuǎn)換
(1)Converter接口
推酷:SpringMVC之類型轉(zhuǎn)換Converter
SpringMVC之類型轉(zhuǎn)換Converter 1
SpringMVC之類型轉(zhuǎn)換Converter 2
在Spring3中引入了一個Converter接口,使用Converter接口可以進(jìn)行自定義的數(shù)據(jù)轉(zhuǎn)換,它支持從一個Object轉(zhuǎn)為另一種類型的Object。除了Converter接口之外,實現(xiàn)ConverterFactory接口和GenericConverter接口也可以實現(xiàn)我們自己的類型轉(zhuǎn)換邏輯。
例子:
// Converter
public class StringToDateConverter implements Converter<String, Date> {
private static Logger logger = LoggerFactory.getLogger(StringToDateConverter.class);
@Override
public Date convert(String source) {
System.out.println("->> i'm here ! ");
if(!canConverte(source)){
logger.warn("參數(shù)有誤,無法解析為Date類型:{}",source);
return new Date(0); // 返回一個
}
DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd");
DateTime dateTime = DateTime.parse(source, dateTimeFormatter);
return dateTime.toDate();
}
/**
* 是否可以轉(zhuǎn)換
* @param source
* @return
*/
private boolean canConverte(String source){
if(Strings.isNullOrEmpty(source))
return false;
List<String> stringList = Splitter.on("-").trimResults().splitToList(source);
if(stringList.size() != 3)
return false;
return true;
}
}
<!--注冊時間格式轉(zhuǎn)換注解驅(qū)動-->
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.quanr.fresh.support.StringToDateConverter"/>
</set>
</property>
</bean>
@Controller
@RequestMapping("/support")
public class SupportController {
@RequestMapping(value = "/dateformat", method = RequestMethod.GET)
@ResponseBody
public ResultModel dateFormat(@RequestParam("date") Date date){
System.out.println("-->>>" + date);
ResultModel resultModel = new ResultModel();
resultModel.setMessage(date.toString());
return resultModel;
}
}
2、@DateTimeFormat
x、單元測試
TODO