1. 程式人生 > >Spring MVC -- 基於註解的控制器

Spring MVC -- 基於註解的控制器

產品 click 視圖 form hid result lock security ext

在Spring MVC -- Spring MVC入門中,我們創建了兩個采用傳統風格控制器的Spring MVC應用程序,其控制器是實現了Controller接口。Spring 2.5版本引入了一個新途徑:使用控制器註釋類型。本篇博客將介紹基於註解的控制器,以及各種對應用程序有用的註解類型。

一 Spring MVC註解類型

使用基於註解的控制器具有以下優點:

  • 一個控制器可以處理多個動作(而實現了Controller接口的一個控制器只能處理一個動作)。這就允許將相關的操作寫在同一個控制器類中,從而減少應用程序中類的數量;
  • 基於註解的控制器的請求映射不需要存儲在配置文件中。使用RequestMapping註釋類型,可以對同一個方法進行請求處理;

Controller和RequestMapping註解類型是Spring MVC API最重要的兩個註解類型,本節將會重點介紹著兩個,並簡要介紹一些其它不太流行的註解類型。

1、Controller註解類型

org.springframework.stereotype.Controller註解類型位於spring-context-x.x.x.RELEASE.jar包下,用於指示Spring類的實例是一個控制器。下面是一個帶註解@Controller的例子:

package com.example.controller;

import org.springframework.stereotype.Controller;
...

@Controller
public class CustomerController { //request-handing methods here }

Spring使用掃描機制來找到應用程序中所有基於註解的控制器類。為了保證Spring能找到控制器,需要完成兩件事情:

1)需要在Spring MVC的配置文件中聲明,spring-context,如下所示:

<beans 
   ...
  xmlns:context="http://www.springframework.org/schema/context"
   ...
>
... </beans
>

2)需要應用<component-scan/>元素,如下所示:

<context:component-scan base-package="basePackage"/>

請在<component-scan/>元素中指定控制器類的基本包。例如,若所有的控制器類都在com.example.controller及其子包下,則需要編寫一個如下所示的<component-scan/>元素:

<context:component-scan base-package="com.example.controller"/>

現在整個在配置文件看上去如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.example.controller"/>    
</beans>

清確保所有控制器類都在基本包下,並且不要指定一個太廣泛的基本包,因為這樣會使得Spring MVC掃描了無關的包。

2、RequestMapping註解類型

現在,我們需要在控制器器類的內部為每一個動作開發相應的處理函數。要讓Spring知道用哪一種方法來處理它的動作,需要使用org.springframework.web.bind.annotation.RequestMapping註解類型來將動作映射到相應的處理函數。

RequestMapping註解類型的作用同其名字所暗示的:將一個請求映射到一個方法。使用@RequestMapping註解一種方法(或者類)。

一個采用@RequestMapping註解的方法將成為一個請求處理方法,並由調度程序在接收到對應URL請求時調用。

下面是一個控制器類,使用@RequestMapping註解了inputCustomer()方法:

package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;


@Controller
public class CustomerController {

    @RequestMapping(value="/input-customer")
    public String inputCustomer() {
        //do something   here
        return "CustomerForm";
    }
}

使用RequestMapping註解的value屬性將URL映射到方法。在上面的例子中,我們將input-customer映射到inputCustomer()方法。這樣可以使用如下URL訪問inputCustomer()方法:

http://domain/context/input-customer

由於value屬性是RequestMapping註解的默認屬性,因此,若只有唯一的屬性,則可以省略屬性名稱。話句話說,如下兩個標註含義相同:

@RequestMapping(value="input-customer")
@RequestMapping("input-customer")

但是有多個屬性時,就必須寫入value屬性名稱。

請求映射的值value可以是一個空字符串,此時該方法被映射到以下網址:

http://domain/context

RequestMapping除了具有value屬性外,還有其它屬性。例如,method屬性用來指示該方法僅處理哪些HTTP方法。

例如,僅當在HTTP POST或PUT方法時,才訪問到下面的processOrder()方法:

...
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
...
    @RequestMapping(value="/process-order",
               method={RequestMethod.POST,RequestMethod.PUT})
    public String processOrder() {
        //do something   here
        return "OrderForm";
    }
...

若method屬性只有一個HTTP方法值,則無需花括號,例如:

@RequestMapping(value="/process-order",method=RequestMethod.POST)

如果沒有指定method屬性值,則請求處理方法可以處理任意HTTP方法。

此外,RequestMapping註解類型也可以用來註解一個控制器類,如下所示:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
... 
@Controller 
@RequestMapping(value="/customer") 
public class CustomerController {
   ... 
}

在這種情況下,所有的方法都將映射為相對於類級別的請求。例如,下面的deleteCustomer()方法:

...
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
...
@Controller
@RequestMapping(value="/customer")
public class CustomerController {

    @RequestMapping(value="/delete",
             method={RequestMethod.POST,RequestMethod.PUT})
    public String deleteCustomer() {
        //do something here
        return ...;
    }
}    

由於控制器類的映射使用"/customer",而deleteCustomer()方法映射為"/delete",則如下URL方法會映射到deleteCustomer()方法上:

http://domain/context/customer/delete

二 編寫請求處理方法

每個請求處理方法可以有多個不同類型的參數,以及一個多種類型的返回結果。例如,如果在請求處理方法中需要訪問HttpSession對象,則可以添加HttpSession作為參數,Spring會將對象正確的傳遞給方法:

   @RequestMapping(value="/uri")
    public String myMethod(HttpSession session) {
        ...
        session.addAttribute(key,value);
        ...
    }

或者,如果需要訪問客戶端語言環境和HttpServletRequest對象,則可以在方法簽名上包括這樣的參數:

   @RequestMapping(value="/uri")
    public String myOtherMethod(HttpServletRequest request,Locale locale) {
        ...
        //access Locale and HttpServletRequest here
        ...
    }

下面是可以在請求處理方法中出現的參數類型:

  • javax.servlet.ServletRequest或javax.servlet.http.HttpServletRequest;
  • javax.servlet.ServletResponse或javax.servlet.http.HttpServletResponse;
  • javax.servlet.http.HttpSession;
  • org.springframework.web.context.request.WebRequest或org.springframework.web.context.request.NativeWebRequest;
  • java.util.Locale;
  • java.io.InputStream或java.io.Reader;
  • java.io.OutputStream或java.io.Writer;
  • java.security.Principal;
  • HttpEntity<?>paramters;
  • org.springframework.ui.Model;
  • org.springframework.ui.ModelMap;
  • org.springframework.web.servlet.mvc.support.RedirectAttributes.;
  • org.springframework.validation.Errors;
  • org.springframework.validation.BindingResult;
  • 命令或表單對象;
  • org.springframework.web.bind.support.SessionStatus;
  • org.springframework.web.util.UriComponentsBuilder;
  • 帶@PathVariable、@MatrixVariable、@RequestParam、@RequestHeader、@RequestBody或@RequestPart註解的對象;

特別重要的是org.springframework.ui.Model類型。這不是一個Servlet API類型,而是一個包含java.util.Map字段的Spring MVC類型。每次調用請求處理方法時,Spring MVC都創建Model對象,用於增加需要顯示在視圖中的屬性,這些屬性就可以像被添加到HttpServletRequest那樣訪問了。。

請求處理方法可以返回如下類型的對象:

  • ModelAndView;
  • Model;
  • 包含模型的屬性的Map;
  • View;
  • 代表邏輯視圖名的String;
  • void;
  • 提供對Servlet的訪問,以響應HTTP頭部和內容HttpEntity或ReponseEntity對象;
  • Callable;
  • 其它任意類型,Spring將其視作輸出給View的對象模型;

三 應用基於註解的控制器

本節將創建一個名為annotated1的應用,展示了包含有兩個請求處理方法的一個控制器類。該應用和Spring MVC -- Spring MVC入門中應用的主要區別是:annotated1的控制器類使用了註解@Controller,而不是實現Controller接口。此外,Spring配置文件也增加了一些元素。

1、目錄結構

annotated1應用的目錄結構如下:

技術分享圖片

註意:annotated1應用只有一個控制器類,而不是兩個。

2、配置文件

annotated1應用由兩個配置文件:

  • 第一個為部署描述符(web.xml文件)中註冊Spring MVC的DispatcherServlet;
  • 第二個為springmvc-config.xml,即Spring MVC的配置文件;

部署描述符(web.xml文件):

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" 
        xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
            http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
            
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/config/springmvc-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>    
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

在部署描述符中的<servlert-mapping/>元素,Spring MVC的DispatcherServlet的URL模式設置為"/",因為這所有的請求(包括那些用於靜態資源)都被映射到DispatcherServlet。為了正確處理靜態資源需要在Spring MVC配置文件中添加一些<resources/>元素

Spring MVC配置文件(springmvc-servlet.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd     
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="controller"/>
    <mvc:annotation-driven/>
    <mvc:resources mapping="/css/*" location="/css/"/>
    <mvc:resources mapping="/*.html" location="/"/>
    
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

Spring MVC的配置文件中最主要的是<component-scan/>元素,這是要指示Spring MVC掃描目標包中的控制器類,本例是controller包。

接下來是<annotation-driven/>元素,該元素做了很多事情,其中包括註冊用於控制器註解的bean對象;

最後是<resources/>元素,用於指示Spring MVC哪些靜態資源需要單獨處理(不通過DispatcherServlet)。

在上面的配置中,有兩個<resources/>元素。第一個確保在/css目錄下的所有文件可見,第二個允許顯示所有的/.html文件。

註意:如果沒有<annotation-driven/>元素,<resources/>元素會阻止任意控制器被調用。若不需要使用<resources/>元素,則不需要<annotation-driven/>元素元素。

3、Controller類

如前所述,使用Controller註解類型的一個優點在於:一個控制器類可以包含多個請求處理方法。ProductController類中有inputProduct()和saveProduct()兩個請求處理方法:

package controller;

import java.math.BigDecimal;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import domain.Product;
import form.ProductForm;

@Controller
public class ProductController {

    private static final Log logger = LogFactory.getLog(ProductController.class);

    @RequestMapping(value="/input-product")
    public String inputProduct() {
        logger.info("inputProduct called");
        return "ProductForm";
    }

    @RequestMapping(value="/save-product")
    public String saveProduct(ProductForm productForm, Model model) {
        logger.info("saveProduct called");
        // no need to create and instantiate a ProductForm
        // create Product
        Product product = new Product();
        product.setName(productForm.getName());
        product.setDescription(productForm.getDescription());
        try {
            product.setPrice(new BigDecimal(productForm.getPrice()));
        } catch (NumberFormatException e) {
        }

        // add product

        model.addAttribute("product", product);
        return "ProductDetails";
    }
}

其中saveProduct()方法的第二個參數是org.springframework.ui.Model類型。無論是否會使用,Spring MVC都會在每一個請求處理方法被調用時創建一個Model實例,用於增加需要顯示在視圖中的屬性。例如,通過調用model.addAttribute()來增加Product對象:

        model.addAttribute("product", product);

Product對象就可以像被添加到HttpServletRequest那樣訪問了。

4、視圖

annotated1應用程序包含兩個jsp頁面:ProductForm.jsp頁面和ProductDetails頁面:

ProductForm.jsp:

<!DOCTYPE HTML>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>

<div id="global">
<form action="save-product" method="post">
    <fieldset>
        <legend>Add a product</legend>
        <p>
            <label for="name">Product Name: </label>
            <input type="text" id="name" name="name" 
                tabindex="1">
        </p>
        <p>
            <label for="description">Description: </label>
            <input type="text" id="description" 
                name="description" tabindex="2">
        </p>
        <p>
            <label for="price">Price: </label>
            <input type="text" id="price" name="price" 
                tabindex="3">
        </p>
        <p id="buttons">
            <input id="reset" type="reset" tabindex="4">
            <input id="submit" type="submit" tabindex="5" 
                value="Add Product">
        </p>
    </fieldset>
</form>
</div>
</body>
</html>

ProductDetails.jsp:

<!DOCTYPE HTML>
<html>
<head>
<title>Save Product</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<div id="global">
    <h4>The product has been saved.</h4>
    <p>
        <h5>Details:</h5>
        Product Name: ${product.name}<br/>
        Description: ${product.description}<br/>
        Price: $${product.price}
    </p>
</div>
</body>
</html>

ProductDetails頁面通過EL表達式語言訪問product對象的各種屬性。

main.css:

技術分享圖片
#global {
    text-align: left;
    border: 1px solid #dedede;
    background: #efefef;
    width: 560px;
    padding: 20px;
    margin: 100px auto;
}

form {
  font:100% verdana;
  min-width: 500px;
  max-width: 600px;
  width: 560px;
}

form fieldset {
  border-color: #bdbebf;
  border-width: 3px;
  margin: 0;
}

legend {
    font-size: 1.3em;
}

form label { 
    width: 250px;
    display: block;
    float: left;
    text-align: right;
    padding: 2px;
}

#buttons {
    text-align: right;
}
#errors, li {
    color: red;
}
View Code

5、測試應用

將項目部署到tomcat服務器,然後啟動服務器,假設示例應用運行在本機的8000端口上,則可以通過如下URL訪問應用:

http://localhost:8008/annotated1/input-product

會看到如下產品表單頁面:

技術分享圖片

在表單中輸入相應的值後單擊Add Product按鈕,會調用saveProduct()方法,在下一頁中看到產品屬性:

技術分享圖片

參考文章

[1]Spring MVC學習指南

Spring MVC -- 基於註解的控制器