Spring(4)——Spring MVC

Spring MVC請(qǐng)求流程

1、Spring MVC請(qǐng)求流程

  • (1)初始化:(對(duì)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請(qǐng)求
  • (3)請(qǐng)求交由前端控制器DispatcherServlet處理,DispatcherServlet根據(jù)Request的url在HandlerMapping中找到對(duì)應(yīng)的HandlerExecutionChain
  • (4)在執(zhí)行鏈HandlerExecutionChain中找到HandlerAdapter;這里為什么不是 DispatcherServlet 直接把請(qǐng)求傳遞給Handler(即某某Controller),而是給HandlerAdapter代理呢?因?yàn)閏ontroller有多種實(shí)現(xiàn)方式,HandlerAdapter從中間代理可以簡(jiǎn)化很多操作,比如數(shù)據(jù)綁定、數(shù)據(jù)校驗(yàn)等;
  • (5)HandlerAdapter進(jìn)行數(shù)據(jù)適配,將請(qǐng)求傳遞給具體的Controller;
  • (6)由我們寫(xiě)的Controller進(jìn)行數(shù)據(jù)操作,得到model后進(jìn)行返回;這里還要經(jīng)過(guò)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)請(qǐng)求

2、配置文件結(jié)構(gòu)(建議)

3、筆記

1
2
3

4、注解

參考: Spring 注解總結(jié)

為什么會(huì)有注解?

注解可以簡(jiǎn)化借助xml對(duì)bean的配置工作:通過(guò)在類、方法上加上注解和相應(yīng)的注解屬性,再配置spring要掃描的包路徑,spring將會(huì)把合適的java類全部注冊(cè)成spring Bean。

注解實(shí)現(xiàn)Bean配置主要用來(lái)進(jìn)行如依賴注入、生命周期回調(diào)方法定義等,不能消除XML文件中的Bean元數(shù)據(jù)定義,且基于XML配置中的依賴注入的數(shù)據(jù)將覆蓋基于注解配置中的依賴注入的數(shù)據(jù)。

Spring3支持的注解類型

Spring3的基于注解實(shí)現(xiàn)Bean依賴注入支持如下4種注解:

  • Spring自帶依賴注入注解: Spring自帶的一套依賴注入注解;
    @Required:依賴檢查;
    @Autowired:自動(dòng)裝配,用于替代基于XML配置的自動(dòng)裝配;基于@Autowired的自動(dòng)裝配,默認(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)識(shí)符") 
    
  • JSR-250注解:Java平臺(tái)的公共注解,是Java EE 5規(guī)范之一,在JDK6中默認(rèn)包含這些注解,從Spring2.5開(kāi)始支持;
    @Resource:自動(dòng)裝配,默認(rèn)根據(jù)類型裝配,如果指定name屬性將根據(jù)名字裝配,可以使用如下方式來(lái)指定字段或setter方法:.

    @Resource(name = "標(biāo)識(shí)符")  
    

    @PostConstruct和PreDestroy:通過(guò)注解指定初始化和銷毀方法定義

  • JSR-330注解:Java 依賴注入標(biāo)準(zhǔn),Java EE 6規(guī)范之一,可能在加入到未來(lái)JDK版本,從Spring3開(kāi)始支持;
    @Inject:等價(jià)于默認(rèn)的@Autowired,只是沒(méi)有required屬性
    @Named:指定Bean名字,對(duì)應(yīng)于Spring自帶@Qualifier中的缺省的根據(jù)Bean名字注入情況
    @Qualifier:只對(duì)應(yīng)于Spring自帶@Qualifier中的擴(kuò)展@Qualifier限定描述符注解,即只能擴(kuò)展使用,沒(méi)有value屬性

  • JPA注解: JPA全稱Java Persistence API,JPA通過(guò)JDK 5.0注解或XML描述對(duì)象-關(guān)系表的映射關(guān)系,并將運(yùn)行期的實(shí)體對(duì)象持久化到數(shù)據(jù)庫(kù)中。用于注入持久化上下文和實(shí)體管理器。參考:JPA常用注解
    @Entity 標(biāo)識(shí)這個(gè)pojo是一個(gè)jpa實(shí)體
    @Entity @Table(name = "users") 指定表名為users
    @Id 注解在屬性上,設(shè)為主鍵
    @Column 設(shè)置字段類型
    @OrderBy 字段排序
    @GeneratedValue 主鍵生成策略
    @PersistenceContext 用于注入EntityManagerFactory和EntityManager
    @PersistenceUnit
    ... ...

這4種類型的注解在Spring3中都支持,類似于注解事務(wù)支持,想要使用這些注解需要在Spring容器中**開(kāi)啟注解驅(qū)動(dòng),使用<context:annotation-config />簡(jiǎn)化配置 **:

Spring2.1添加了一個(gè)新的context的Schema命名空間,該命名空間對(duì)注釋驅(qū)動(dòng)、屬性文件引入、加載期織入等功能提供了便捷的配置。我們知道**注釋本身是不會(huì)做任何事情的,它僅提供元數(shù)據(jù)信息。要使元數(shù)據(jù)信息真正起作用,必須讓負(fù)責(zé)處理這些元數(shù)據(jù)的處理器工作起來(lái)。 **
AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor就是處理這些注釋元數(shù)據(jù)的處理器。但是直接在Spring配置文件中定義這些Bean顯得比較笨拙。Spring為我們提供了一種方便的注冊(cè)這些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

上面三個(gè)注解的作用都是對(duì)bean進(jìn)行自動(dòng)裝配;所謂自動(dòng)裝配,就是容器自動(dòng)配置bean,而不需手動(dòng)顯示配置。
自動(dòng)裝配有4種類型(除了no即不自動(dòng)裝配):

  • 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:

  • 可以對(duì)成員變量、方法和構(gòu)造函數(shù)進(jìn)行標(biāo)注,來(lái)完成自動(dòng)裝配的工作
  • byType進(jìn)行自動(dòng)裝配,如果出現(xiàn)多個(gè)相同的類型,就會(huì)拋出BeanCreationException異常,我們可以使用@Qualifier配合@Autowired來(lái)解決這些問(wèn)題。

@Qualifier:進(jìn)行更細(xì)粒度的“候選bean”控制

// 可能存在多個(gè)UserDao實(shí)例 :這樣,Spring會(huì)找到id為userDao的bean進(jìn)行裝配。 
@Autowired    
public void setUserDao(@Qualifier("userDao") UserDao userDao) {    
    this.userDao = userDao;    
} 

// 可能不存在UserDao實(shí)例 
@Autowired(required = false)    
public void setUserDao(UserDao userDao) {    
      this.userDao = userDao;    
}    

@Resource:推薦使用它來(lái)代替Spring專有的@Autowired注解,因?yàn)椋?/p>

  • 它默認(rèn)按byName自動(dòng)注入;
  • @Resource有兩個(gè)屬性是比較重要的,分別是name和type,Spring將@Resource注解的name屬性解析為bean的名字,而type屬性則解析為bean的類型。所以如果使用name屬性,則使用byName的自動(dòng)注入策略,而使用type屬性時(shí)則使用byType自動(dòng)注入策略。如果既不指定name也不指定type屬性,這時(shí)將通過(guò)反射機(jī)制使用byName自動(dòng)注入策略。
  • 裝配順序
    如果同時(shí)指定了name和type,則從Spring上下文中找到唯一匹配的bean進(jìn)行裝配,找不到則拋出異常;
    如果指定了name,則從上下文中查找名稱(id)匹配的bean進(jìn)行裝配,找不到則拋出異常;
    如果指定了type,則從上下文中找到類型匹配的唯一bean進(jìn)行裝配,找不到或者找到多個(gè),都會(huì)拋出異常;
    如果既沒(méi)有指定name,又沒(méi)有指定type,則自動(dòng)按照byName方式進(jìn)行裝配;如果沒(méi)有匹配,則回退為一個(gè)原始類型(UserDao)進(jìn)行匹配,如果匹配則自動(dòng)裝配
@PostConstruct

在方法上加上注解@PostConstruct,這個(gè)方法就會(huì)在Bean初始化之后被Spring容器執(zhí)行(注:Bean初始化包括,實(shí)例化Bean,并裝配Bean的屬性(依賴注入))。

它的一個(gè)典型的應(yīng)用場(chǎng)景是,當(dāng)你需要往Bean里注入一個(gè)其父類中定義的屬性,而你又無(wú)法復(fù)寫(xiě)父類的屬性或?qū)傩缘膕etter方法時(shí),如:

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);    
    }    
    ...    
}   
這里通過(guò)@PostConstruct,為UserDaoImpl的父類里定義的一個(gè)sessionFactory私有屬性,
注入了我們自己定義的sessionFactory(父類的setSessionFactory方法為final,不可復(fù)寫(xiě)),
之后我們就可以通過(guò)調(diào)用super.getSessionFactory()來(lái)訪問(wèn)該屬性了。
@PreDestroy

在方法上加上注解@PreDestroy,這個(gè)方法就會(huì)在Bean初始化之后被Spring容器執(zhí)行。由于我們當(dāng)前還沒(méi)有需要用到它的場(chǎng)景,這里不不去演示。其用法同@PostConstruct。


以上我們介紹了通過(guò)@Autowired或@Resource來(lái)實(shí)現(xiàn)在Bean中自動(dòng)注入的功能,下面我們將介紹如何注解Bean,從而從XML配置文件中完全移除Bean定義的配置。

@Component(不推薦使用)、@Repository、@Service、@Controller

只需要在對(duì)應(yīng)的類上加上一個(gè)@Component注解,就將該類定義為一個(gè)Bean了:

@Component    
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {    
      ...    
}   

使用@Component注解定義的Bean,默認(rèn)的名稱(id)是小寫(xiě)開(kāi)頭的非限定類名。如這里定義的Bean名稱就是userDaoImpl。你也可以指定Bean的名稱:

  • @Component("userDao")

@Component是所有受Spring管理組件的通用形式

Spring還提供了更加細(xì)化的注解形式:

  • @Repository 對(duì)應(yīng)存儲(chǔ)層Bean
  • @Service 對(duì)應(yīng)業(yè)務(wù)層Bean
  • @Controller 對(duì)應(yīng)展示層Bean(頁(yè)面控制器)

這些注解與@Component的語(yǔ)義是一樣的,完全通用,在Spring以后的版本中可能會(huì)給它們追加更多的語(yǔ)義。所以,我們推薦使用@Repository、@Service、@Controller來(lái)替代@Component。

使用<context:component-scan />讓Bean定義注解工作起來(lái)
<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>   

從上面的幾個(gè)注解可以看到:原先需要在xml配置文件中定義的bean,現(xiàn)在不需要定義了,而是直接寫(xiě)在了注解里;所有通過(guò)<bean>元素定義Bean的配置內(nèi)容已經(jīng)被移除,如果使其生效,僅需要添加一行<context:component-scan />配置就解決所有問(wèn)題了——Spring XML配置文件得到了極致的簡(jiǎn)化(當(dāng)然配置元數(shù)據(jù)還是需要的,只不過(guò)以注釋形式存在罷了)。<context:component-scan />的base-package屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會(huì)被處理。

<context:component-scan />還允許定義過(guò)濾器將基包下的某些類納入或排除。Spring支持以下4種類型的過(guò)濾方式:
(過(guò)濾器類型)(表達(dá)式范例)(說(shuō)明)
注解---org.example.SomeAnnotation---將所有使用SomeAnnotation注解的類過(guò)濾出來(lái)
類名指定---org.example.SomeClass---過(guò)濾指定的類
正則表達(dá)式---com.kedacom.spring.annotation.web..---通過(guò)正則表達(dá)式過(guò)濾一些類
AspectJ表達(dá)式---org.example..
Service+---通過(guò)AspectJ表達(dá)式過(guò)濾一些類

值得注意的是<context:component-scan />配置項(xiàng)不但啟用了對(duì)類包進(jìn)行掃描以實(shí)施注釋驅(qū)動(dòng)Bean定義的功能,同時(shí)還啟用了注釋驅(qū)動(dòng)自動(dòng)注入的功能(即還隱式地在內(nèi)部注冊(cè)了AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor),因此當(dāng)使用<context:component-scan />后,就可以將<context:annotation-config />移除了。

@ModelAttribute

@ModelAttribute一個(gè)具有如下三個(gè)作用:

  • ① 綁定請(qǐng)求參數(shù)到命令對(duì)象:放在功能處理方法的入?yún)⑸蠒r(shí),用于將多個(gè)請(qǐng)求參數(shù)綁定到一個(gè)命令對(duì)象,從而簡(jiǎn)化綁定流程,而且自動(dòng)暴露為模型數(shù)據(jù)用于視圖頁(yè)面展示時(shí)使用;
    public String test1(@ModelAttribute("user") UserModel user)
    只是此處多了一個(gè)注解@ModelAttribute("user"),
    它的作用是將該綁定的命令對(duì)象以“user”為名稱添加到模型對(duì)象中供視圖頁(yè)面展示使用。
    我們此時(shí)可以在視圖頁(yè)面使用${user.username}來(lái)獲取綁定的命令對(duì)象的屬性。

  • ②暴露表單引用對(duì)象為模型數(shù)據(jù):放在處理器的一般方法(非功能處理方法)上時(shí),是為表單準(zhǔn)備要展示的表單引用對(duì)象,如注冊(cè)時(shí)需要選擇的所在城市等,而且在執(zhí)行功能處理方法(@RequestMapping 注解的方法)之前,自動(dòng)添加到模型對(duì)象中,用于視圖頁(yè)面展示時(shí)使用;
    /**
    * 設(shè)置這個(gè)注解之后可以直接在前端頁(yè)面使用hb這個(gè)對(duì)象(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頁(yè)面展示出來(lái):
    <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í),是暴露功能處理方法的返回值為模型數(shù)據(jù),用于視圖頁(yè)面展示時(shí)使用。
    public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)

5、Controller的請(qǐng)求映射與RESTful模式

RESTful

REST(英文:Representational State Transfer,簡(jiǎn)稱REST)描述了一個(gè)架構(gòu)樣式的網(wǎng)絡(luò)系統(tǒng),比如 web 應(yīng)用程序。它首次出現(xiàn)在 2000 年 Roy Fielding 的博士論文中,他是 HTTP 規(guī)范的主要編寫(xiě)者之一。

原則條件:

REST 指的是一組架構(gòu)約束條件和原則。滿足這些約束條件和原則的應(yīng)用程序或設(shè)計(jì)就是 RESTful。
Web 應(yīng)用程序最重要的 REST 原則是,客戶端和服務(wù)器之間的交互在請(qǐng)求之間是無(wú)狀態(tài)的。從客戶端到服務(wù)器的每個(gè)請(qǐng)求都必須包含理解請(qǐng)求所必需的信息。如果服務(wù)器在請(qǐng)求之間的任何時(shí)間點(diǎn)重啟,客戶端不會(huì)得到通知。此外,無(wú)狀態(tài)請(qǐng)求可以由任何可用服務(wù)器回答,這十分適合云計(jì)算之類的環(huán)境??蛻舳丝梢跃彺鏀?shù)據(jù)以改進(jìn)性能。
在服務(wù)器端,應(yīng)用程序狀態(tài)和功能可以分為各種資源。資源是一個(gè)有趣的概念實(shí)體,它向客戶端公開(kāi)。資源的例子有:應(yīng)用程序?qū)ο?、?shù)據(jù)庫(kù)記錄、算法等等。每個(gè)資源都使用 URI (Universal Resource Identifier) 得到一個(gè)唯一的地址。所有資源都共享統(tǒng)一的接口,以便在客戶端和服務(wù)器之間傳輸狀態(tài)。使用的是標(biāo)準(zhǔn)的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。Hypermedia 是應(yīng)用程序狀態(tài)的引擎,資源表示通過(guò)超鏈接互聯(lián)。

分層系統(tǒng):

另一個(gè)重要的 REST 原則是分層系統(tǒng),這表示組件無(wú)法了解它與之交互的中間層以外的組件。通過(guò)將系統(tǒng)知識(shí)限制在單個(gè)層,可以限制整個(gè)系統(tǒng)的復(fù)雜性,促進(jìn)了底層的獨(dú)立性。
當(dāng) REST 架構(gòu)的約束條件作為一個(gè)整體應(yīng)用時(shí),將生成一個(gè)可以擴(kuò)展到大量客戶端的應(yīng)用程序。它還降低了客戶端和服務(wù)器之間的交互延遲。統(tǒng)一界面簡(jiǎn)化了整個(gè)系統(tǒng)架構(gòu),改進(jìn)了子系統(tǒng)之間交互的可見(jiàn)性。REST 簡(jiǎn)化了客戶端和服務(wù)器的實(shí)現(xiàn)。

我的理解:既然HTTP請(qǐng)求無(wú)狀態(tài) -> 那么客戶端請(qǐng)求如何映射到服務(wù)器資源? -> 服務(wù)端通過(guò)路徑+請(qǐng)求方法+參數(shù)+提交的內(nèi)容類型+返回的內(nèi)容類型+Header+...來(lái)進(jìn)行地址映射,確定一個(gè)唯一的響應(yīng)接口。

在Controller中使用@RequestMapping進(jìn)行地址映射

參考:
@RequestMapping 用法詳解之地址映射
@RequestParam @RequestBody @PathVariable 等參數(shù)綁定注解詳解

@RequestMapping是一個(gè)用來(lái)處理請(qǐng)求地址映射的注解,可用于類(Controller Bean)或該類的方法上。用于類上,表示類中的所有響應(yīng)請(qǐng)求的方法都是以該地址作為父路徑;用于方法上,表示方法響應(yīng)的子路徑以及要響應(yīng)的請(qǐng)求需要具備哪些條件(-->RESTful)。

在具體的方法參數(shù)里,可以使用@RequestParam、 @RequestBody、 @RequestHeader 、 @PathVariable進(jìn)行參數(shù)綁定,將請(qǐng)求參數(shù)作為方法參數(shù)使用(實(shí)際上是對(duì)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注解有六個(gè)屬性

這6個(gè)屬性可以用來(lái)將請(qǐng)求“過(guò)濾”(或者說(shuō)映射)到具體的方法,下面我們把這6個(gè)屬性分成三類進(jìn)行說(shuō)明:

  • value和method
    value: 指定請(qǐng)求的實(shí)際地址,指定的地址可以是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: 指定請(qǐng)求的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: 指定處理請(qǐng)求的提交內(nèi)容類型(Content-Type),例如application/json, text/html;
    produces: 指定返回的內(nèi)容類型,僅當(dāng)request請(qǐng)求頭中的(Accept)類型中包含該指定類型才返回;

      // cousumes的樣例:方法僅處理request Content-Type為“application/json”類型的請(qǐng)求。
      @Controller
      @RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
      public void addPet(@RequestBody Pet pet, Model model) {    
          // implementation omitted
      }
    
      produces的樣例:方法僅處理request請(qǐng)求中Accept頭中包含了"application/json"的請(qǐng)求,同時(shí)暗示了返回的內(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 為請(qǐng)求參數(shù)的數(shù)組 支持一些簡(jiǎn)單的表達(dá)式
    params={"name", "!id", "name!=James"} 表示必須攜帶name參數(shù) / 不能帶名稱為id的參數(shù) , 而且name的值不能為James 等等表達(dá)式
    params = {"name=kobe", "number=23"}) 否則 404錯(cuò)誤

headers: 指定request中必須包含某些指定的header值,才能讓該方法處理請(qǐng)求。

    // 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 樣式映射時(shí), 即 someUrl/{paramId}, 這時(shí)的paramId可通過(guò) @Pathvariable注解綁定它傳過(guò)來(lái)的值到方法的參數(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請(qǐng)求header部分的值綁定到方法的參數(shù)上;可以使用Map來(lái)接收整個(gè)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中不存在,會(huì)拋出異常,可以使用@RequestParam(value="username" required=false default=" 默認(rèn)值")這樣請(qǐng)求有值就取,沒(méi)有值就不取。

  • 3、使用實(shí)體類封裝
    將實(shí)體類(需要具有setter,getter方法)作為Controller方法的參數(shù),請(qǐng)求參數(shù)與實(shí)體類的屬性保持一致,則會(huì)完成自動(dòng)綁定,將請(qǐng)求參數(shù)自動(dòng)綁定到這個(gè)實(shí)體類上,方法直接使用即可。

7、請(qǐng)求攔截

Spring MVC中通過(guò)配置<mvc:interceptors>來(lái)設(shè)置攔截方式,其子標(biāo)簽<mvc:interceptor>下有3種子標(biāo)簽來(lái)配置攔截方式:

<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攔截器

SpringMVC 中的Interceptor 攔截器也是相當(dāng)重要和相當(dāng)有用的,它的主要作用是攔截用戶的請(qǐng)求并進(jìn)行相應(yīng)的處理。比如通過(guò)它來(lái)進(jìn)行權(quán)限驗(yàn)證,或者是來(lái)判斷用戶是否登陸,或者是像12306 那樣子判斷當(dāng)前時(shí)間是否是購(gòu)票時(shí)間。

(1)定義Interceptor實(shí)現(xiàn)類

Spring MVC 中的Interceptor 攔截請(qǐng)求是通過(guò)HandlerInterceptor 來(lái)實(shí)現(xiàn)的。在Spring MVC 中定義一個(gè)Interceptor 非常簡(jiǎn)單,主要有兩種方式,第一種方式是要定義的Interceptor類要實(shí)現(xiàn)了Spring 的HandlerInterceptor 接口,或者是這個(gè)類繼承實(shí)現(xiàn)了HandlerInterceptor 接口的類,比如Spring 已經(jīng)提供的實(shí)現(xiàn)了HandlerInterceptor 接口的抽象類HandlerInterceptorAdapter ;第二種方式是實(shí)現(xiàn)Spring的WebRequestInterceptor接口,或者是繼承實(shí)現(xiàn)了WebRequestInterceptor的類。

(2)實(shí)現(xiàn)HandlerInterceptor接口

HandlerInterceptor 接口中定義了三個(gè)方法,我們就是通過(guò)這三個(gè)方法來(lái)對(duì)用戶的請(qǐng)求進(jìn)行攔截處理的。

(1)preHandle (HttpServletRequest request, HttpServletResponse response, Object handle) 方法

顧名思義,該方法將在請(qǐng)求處理之前進(jìn)行調(diào)用。Spring MVC 中的Interceptor 是鏈?zhǔn)降恼{(diào)用的,在一個(gè)應(yīng)用中或者說(shuō)是在一個(gè)請(qǐng)求中可以同時(shí)存在多個(gè)Interceptor 。每個(gè)Interceptor 的調(diào)用會(huì)依據(jù)它的聲明順序依次執(zhí)行,而且最先執(zhí)行的都是Interceptor 中的preHandle 方法,所以可以在這個(gè)方法中進(jìn)行一些前置初始化操作或者是對(duì)當(dāng)前請(qǐng)求的一個(gè)預(yù)處理,也可以在這個(gè)方法中進(jìn)行一些判斷來(lái)決定請(qǐng)求是否要繼續(xù)進(jìn)行下去。該方法的返回值是布爾值Boolean 類型的,當(dāng)它返回為false 時(shí),表示請(qǐng)求結(jié)束,后續(xù)的Interceptor 和Controller 都不會(huì)再執(zhí)行;當(dāng)返回值為true 時(shí)就會(huì)繼續(xù)調(diào)用下一個(gè)Interceptor 的preHandle 方法,如果已經(jīng)是最后一個(gè)Interceptor 的時(shí)候就會(huì)是調(diào)用當(dāng)前請(qǐng)求的Controller 方法。

(2)postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView) 方法

由preHandle 方法的解釋我們知道這個(gè)方法包括后面要說(shuō)到的afterCompletion 方法都只能是在當(dāng)前所屬的Interceptor 的preHandle 方法的返回值為true 時(shí)才能被調(diào)用。postHandle 方法,顧名思義就是在當(dāng)前請(qǐng)求進(jìn)行處理之后,也就是Controller 方法調(diào)用之后執(zhí)行,但是它會(huì)在DispatcherServlet 進(jìn)行視圖返回渲染之前被調(diào)用,所以我們可以在這個(gè)方法中對(duì)Controller 處理之后的ModelAndView 對(duì)象進(jìn)行操作。postHandle 方法被調(diào)用的方向跟preHandle 是相反的,也就是說(shuō)先聲明的Interceptor 的postHandle 方法反而會(huì)后執(zhí)行,這和Struts2 里面的Interceptor 的執(zhí)行過(guò)程有點(diǎn)類型。Struts2 里面的Interceptor 的執(zhí)行過(guò)程也是鏈?zhǔn)降?,只是在Struts2 里面需要手動(dòng)調(diào)用ActionInvocation 的invoke 方法來(lái)觸發(fā)對(duì)下一個(gè)Interceptor 或者是Action 的調(diào)用,然后每一個(gè)Interceptor 中在invoke 方法調(diào)用之前的內(nèi)容都是按照聲明順序執(zhí)行的,而invoke 方法之后的內(nèi)容就是反向的。

(3)afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle,
Exception ex) 方法

該方法也是需要當(dāng)前對(duì)應(yīng)的Interceptor 的preHandle 方法的返回值為true 時(shí)才會(huì)執(zhí)行。顧名思義,該方法將在整個(gè)請(qǐng)求結(jié)束之后,也就是在DispatcherServlet 渲染了對(duì)應(yīng)的視圖之后執(zhí)行。這個(gè)方法的主要作用是用于進(jìn)行資源清理工作的。

    /**
     * 例子
     * version    date      author
     * ──────────────────────────────────
     * 1.0       17-3-17   wanlong.ma
     * Description: 攔截器 【攔截所有的請(qǐng)求,并通過(guò)logger的方式打印到控制臺(tái)上,每次請(qǐng)求的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)資源的三種處理方式

SpringMVC 攔截器不攔截靜態(tài)資源的三種處理方式

增加攔截器之后,如果不進(jìn)行相關(guān)設(shè)置,那么一些不需要攔截的對(duì)靜態(tài)資源文件的請(qǐng)求也會(huì)被攔截。

方案一、攔截器中增加針對(duì)靜態(tài)資源不進(jìn)行過(guò)濾(涉及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)資源請(qǐng)求-->
        <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
  • 對(duì)于ModelAndView構(gòu)造函數(shù)可以指定返回頁(yè)面的名稱,也可以通過(guò)setViewName方法來(lái)設(shè)置所需要跳轉(zhuǎn)的頁(yè)面:
    @RequestMapping(value="/index1",method=RequestMethod.GET)
    public ModelAndView index(){
    ModelAndView modelAndView = new ModelAndView("/user/index");
    modelAndView.addObject("name", "xxx");
    return modelAndView;
    }

    // 返回的是一個(gè)包含模型和視圖的ModelAndView對(duì)象
    @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)沒(méi)有視圖返回的時(shí)候視圖名稱將由requestToViewNameTranslator決定;響應(yīng)的view應(yīng)該也是該請(qǐ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頁(yè)面中可直通過(guò)${key1}獲得到值, map.put()相當(dāng)于request.setAttribute方法。
(3)返回String
  • 指定返回的視圖頁(yè)面名稱,結(jié)合設(shè)置的返回地址路徑加上頁(yè)面名稱后綴即可訪問(wèn)到;例如下面的例子將返回到視圖 hello.vm(.jsp...):
    @RequestMapping(value="/showdog")
    public String hello1(){
    return "hello";
    }

  • 如果方法聲明了注解@ResponseBody ,則會(huì)直接將返回值輸出到頁(yè)面:
    @RequestMapping(value="/print")
    @ResponseBody
    public String print(){
    String message = "Hello World, Spring MVC!";
    return message;
    }

(4)返回void

如果返回值為空,則響應(yīng)的視圖頁(yè)面對(duì)應(yīng)為訪問(wèn)地址:
@RequestMapping("/index")
public void index() {
return;
}

小結(jié)
  • 1、使用 String 作為請(qǐng)求處理方法的返回值類型是比較通用的方法,這樣返回的邏輯視圖名不會(huì)和請(qǐng)求 URL 綁定,具有很大的靈活性,而模型數(shù)據(jù)又可以通過(guò) ModelMap 控制。
  • 2、使用void,map,Model 時(shí),返回對(duì)應(yīng)的邏輯視圖名稱真實(shí)url為:prefix前綴+視圖名稱 +suffix后綴組成。
  • 3、使用String,ModelAndView返回視圖名稱可以不受請(qǐng)求的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中引入了一個(gè)Converter接口,使用Converter接口可以進(jìn)行自定義的數(shù)據(jù)轉(zhuǎn)換,它支持從一個(gè)Object轉(zhuǎn)為另一種類型的Object。除了Converter接口之外,實(shí)現(xiàn)ConverterFactory接口和GenericConverter接口也可以實(shí)現(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ù)有誤,無(wú)法解析為Date類型:{}",source);
            return new Date(0); // 返回一個(gè)
        }

        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;
    }
}

<!--注冊(cè)時(shí)間格式轉(zhuǎn)換注解驅(qū)動(dòng)-->
<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、單元測(cè)試

TODO

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,253評(píng)論 6 342
  • 什么是Spring Spring是一個(gè)開(kāi)源的Java EE開(kāi)發(fā)框架。Spring框架的核心功能可以應(yīng)用在任何Jav...
    jemmm閱讀 16,764評(píng)論 1 133
  • Spring MVC一、什么是 Spring MVCSpring MVC 屬于 SpringFrameWork 的...
    任任任任師艷閱讀 3,532評(píng)論 0 32
  • 題目1: DOM0 事件和DOM2級(jí)在事件監(jiān)聽(tīng)使用方式上有什么區(qū)別? DOM0級(jí)方法指定的事件處理程序被認(rèn)為是元素...
    cross_王閱讀 334評(píng)論 0 0

友情鏈接更多精彩內(nèi)容