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

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

一 Spring MVC註解類型

使用基於註解的控制器具備如下優勢:html

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

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

一、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能找到控制器,須要完成兩件事情:web

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

<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/>元素:apache

<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掃描了無關的包。瀏覽器

注意:默認狀況下,<component-scan/>元素查找使用構造型(stereotype)註解所標註的類,包括@Component(組件),@Service(服務),@Controller(控制器),@Repository(數據倉庫)。更多內容能夠參考博客:<context:component-scan>詳解

二、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應用)

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

一、目錄結構

annotated1應用的目錄結構以下:

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

二、配置文件

 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/>元素元素。

三、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()方法的第一個參數是ProductForm類型,這是一個表單類型。Spring MVC會建立一個ProductForm實例,用來接收表單傳遞過來的參數,這樣咱們就能夠直接獲取到表單的數據,而後直接賦值給領域對象product便可。

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

        model.addAttribute("product", product);

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

其中ProductForm類以下:

package form;

public class ProductForm {
    private String name;
    private String description;
    private String price;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getPrice() {
        return price;
    }
    public void setPrice(String price) {
        this.price = price;
    }
}
View Code

Product類以下:

package domain;
import java.io.Serializable;
import java.math.BigDecimal;

public class Product implements Serializable {
    private static final long serialVersionUID = 5784L;
    private long id;
    private String name;
    private String description;
    private BigDecimal price;

    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}
View Code

四、視圖

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

五、測試應用

將項目部署到tomcat服務器,而後啓動服務器,假設示例應用運行在本機的8000端口上,則能夠經過以下URL訪問應用:

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

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

 

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

四 應用@Autowired和@Service進行依賴注入(annotated2應用)

使用Spring 框架的一個好處是容易進行依賴注入。畢竟,Spring框架一開始就是一個依賴注入容器。將依賴注入到Spring MVC控制器的最簡單方法是,經過註解@Autowired到字段或方法。Autowired註解類型屬於org.springframework.beans.factory.annotation包。

此外,爲了能做爲依賴注入,類必需要註明爲@Service。該類型是org.springframework.stereotype包的成員。Service註解類型指示類是一個服務,此外,在配置文件中,還須要增長一個<component-scan/>元素來掃描依賴基本包。

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

下面是以annotated2應用進一步說明Spring MVC如何應用依賴注入。應用的目錄結構以下:

 

注意:這裏引入了taglibs-standard-impl-1.2.5.jar包,在後面會介紹到。下載地址:http://tomcat.apache.org/download-taglibs.cgi

一、視圖

annotated2包含三個jsp頁面,ProductForm.jsp、ProductDetails.jsp、ProductView.jsp:

ProductForm.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url("<c:url value="/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>
View Code

ProductDetails.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Save Product</title>
<style type="text/css">@import url("<c:url value="/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>
View Code

ProductView.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>View Product</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>
<div id="global">
    <h4>${message}</h4>
    <p>
        <h5>Details:</h5>
        Product Name: ${product.name}<br/>
        Description: ${product.description}<br/>
        Price: $${product.price}
    </p>
</div>
</body>
</html>
View Code

二、ProductController類

在annotated2應用程序中,ProductController類已經不一樣於annotated1中的類:

package controller;

import java.math.BigDecimal;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import domain.Product;
import service.ProductService;
import form.ProductForm;

@Controller
public class ProductController {

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

    //爲ProductService字段添加@Autowired註解會使ProductService的一個實例被注入到ProductController實例中
    @Autowired
    private ProductService productService;

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

    @RequestMapping(value = "/save-product", method = RequestMethod.POST)
    public String saveProduct(ProductForm productForm, RedirectAttributes redirectAttributes) {
        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
        Product savedProduct = productService.add(product);
        
        redirectAttributes.addFlashAttribute("message", "The product was successfully added.");

        return "redirect:/view-product/" + savedProduct.getId();
    }

    @RequestMapping(value = "/view-product/{id}")
    public String viewProduct(@PathVariable Long id, Model model) {
        Product product = productService.get(id);
        model.addAttribute("product", product);
        return "ProductView";
    }
}

與annotated1相比,annotated2中的ProductController類作了一系列的調整。首先是在以下的私有字段上增長了@Autowired註解。

    @Autowired
    private ProductService productService;

ProductService 是一個提供各類處理產品的方法的接口。爲ProductService字段添加@Autowired註解會使ProductService 的一個實例被注入到ProductController實例中

此外請求處理方法saveProduct()中使用了重定向和Flash屬性,這在後面將會單獨介紹。

ProductController還提供了請求處理方法viewProduct(),請求處理方法中使用到了路徑變量@PathVariable,這在後面也將會單獨介紹。

三、ProductService接口及實現類ProductServiceImpl1

下面是ProductService接口及實現類ProductServiceImpl1的源代碼。注意:爲了使類能被Spring掃描到,必須爲其標註@Service,並須要在配置文件中使用<component-scan/>元素指定服務類的基本包

ProductService接口:

package service;

import domain.Product;

public interface ProductService {
    Product add(Product product);
    Product get(long id);
}

能夠看到ProductService須要實現兩個方法,一個用於新增產品信息,另外一個是根據id號獲取產品信息。

ProductServiceImpl1類:

package service;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import org.springframework.stereotype.Service;

import domain.Product;

@Service
public class ProductServiceImpl implements ProductService {

    private Map<Long, Product> products = new HashMap<Long, Product>();
    private AtomicLong generator = new AtomicLong();

    public ProductServiceImpl() {
        Product product = new Product();
        product.setName("JX1 Power Drill");
        product.setDescription("Powerful hand drill, made to perfection");
        product.setPrice(new BigDecimal(129.99));
        add(product);
    }

    @Override
    public Product add(Product product) {
        long newId = generator.incrementAndGet();
        product.setId(newId);
        products.put(newId, product);
        return product;
    }

    @Override
    public Product get(long id) {
        return products.get(id);
    }
}

四、配置文件

 annotated2應用由兩個配置文件:

  • 第一個爲部署描述符(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>

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"/>
    <context:component-scan base-package="service"/>    
    <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>

annotated2的Spring MVC配置文件中有兩個<component-scan/>元素,一個用於掃描控制器類@Controller,另外一個用於掃描服務類@Service

五、重定向和Flash屬性

做爲一名經驗豐富的servlet/JSP程序員,必須知道轉發和重定向的區別。轉發比重定向快,由於重定向通過客戶端,而轉發沒有。可是,有時採用重定向更好,若須要重定向到一個外部網站,則沒法使用轉發。

使用重定向的另外一個場景是避免用戶從新加載頁面時再次調用一樣的動做。例如,在annotated1中,當提交產品表單時,saveProduct()方法將被調用,並執行相應的動做。在一個真實的應用程序中,這可能將所述產品加入到數據庫中,可是若是在提交表單後從新加載頁面,saveProduct()就會再次被調用,一樣的產品將可能被再次添加。爲了不這種狀況,提交表單後,你可能更願意將用戶重定位到一個不一樣的頁面。這個網頁任意從新加載都沒有反作用。例如,在annotated1中,能夠在提交表單後,將用戶從新定義到一個新的ProductView頁面。

在annotated2中,ProductController類中的請求處理方法saveProduct()以以下所示行結束:

        return "redirect:/view-product/" + savedProduct.getId();

這裏,使用重定向而不是轉發來防止當用戶從新加載頁面時,saveProduct()方法被二次調用。

使用重定向的一個不便之處是:沒法輕鬆地傳值給目標頁面。若採用轉發,則能夠簡單地將屬性添加到Model,使用目標視圖能夠輕鬆方法。因爲重定向通過客戶端,因此Model中的一切都在重定向時失去。好在,Spring 3.1版本以及更高版本經過Flash屬性提供了一種供重定向傳值的方法

要使用Flash屬性,必須在Spring MVC配置文件中有一個 <annotation-driven/>元素,而後,還必須在方法上添加一個新的參數類型org.springframework.web.servlet.mvc.support.RedirectAttributes。具體以下:

 @RequestMapping(value = "/save-product", method = RequestMethod.POST)
    public String saveProduct(ProductForm productForm, RedirectAttributes redirectAttributes) {
        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
        Product savedProduct = productService.add(product);
        
        redirectAttributes.addFlashAttribute("message", "The product was successfully added."); return "redirect:/view-product/" + savedProduct.getId();
    }

六、請求參數和路徑變量

請求參數和路徑變量均可以用於發送值給服務器。兩者都是URL的一部分。

1)請求參數

請求參數採用key=value形式,並用"&"分隔。例如,以下URL帶有一個名爲productId的請求參數,其值爲3:

http://localhost:8008/annotated2/view-product?productId=3

在傳統的servlet編程中,可使用HttpServletRequest的getParamter()方法來獲取一個請求參數值:

    String productId = request.getParameter("productId");

Spring MVC提供了一個更簡單的方法來獲取請求參數值:使用org.springframework.web.bind.annotation.RequestParam註解類型來註解方法參數。例如:下面的方法包含了一個獲取請求參數productId值的參數:

public void sendProduct(@RequestParam int productId)

正如你所看到的,@RequestParam註解的參數類型不必定是字符串。

咱們能夠在ProductController類中追加以下代碼:

    //請求參數    請求URL:/request-param?id=5
    @RequestMapping(value="/request-param")
   public String requestParamTest(@RequestParam Long id,Model model) {
        model.addAttribute("id", id);
        return "RequestParam";
   }

而後在jsp目錄下建立文件RequestParam.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <h1>請求傳入的參數id:${id}</h1>
</body>
</html>

部署項目,並在瀏覽器輸入:

http://localhost:8008/annotated2/request-param?id=12223

輸出以下:

2)路徑變量

路徑變量相似請求參數,可是沒有key部分,只有一個值。例如:在annotated2中,view-product動做映射到以下URL:

/view-product/productId

其中的productId是表示產品標識符的整數。在Spring MVC中,productId稱爲路徑變量,用來發送一個值到服務器。

ProductController類中的viewProduct()方法演示了一個路徑變量的使用:

    @RequestMapping(value = "/view-product/{id}") public String viewProduct(@PathVariable Long id, Model model) {
        Product product = productService.get(id);
        model.addAttribute("product", product);
        return "ProductView";
    }

爲了使用路徑變量,首先須要在RequestMapping註解的值屬性中添加一個變量,該變量必須放在花括號{}之間。例如,下面的RequestMapping註解定義了一個名爲id的路徑變量:

 @RequestMapping(value = "/view-product/{id}")

而後,在方法簽名中添加一個同名變量,並加上@PathVariable 註解。當該方法被調用時,請求URL的id值將被複制到路徑變量中,並能夠在方法中使用,路徑變量的類型能夠不是字符串。Spring MVC將盡力轉換成一個非字符串類型。這個Spring MVC的強大功能將會在後面的博客中詳細介紹。

能夠在請求映射中使用多個路徑變量,例如,下面定義userId和orderId兩個路徑變量。

 @RequestMapping(value = "/view-product/{userId}/{orderId}")

在瀏覽器輸入以下URL,來測試viewProduct()方法的路徑變量:

http://localhost:8008/annotated2/view-product/1

注意:咱們每次點擊/input-product頁面AddProduct都會將產品信息添加到ProductController類的productService實例中(注意這個實例是經過@Autowired和@Service進行依賴注入的,默認是單例的,也就是說Spring容器中只存在一個ProductService類型的實例),這個實例保存着全部產品的標識符以及產品信息。所以能夠能夠經過更改productId的值取出對應的產品信息。

有時,使用路徑變量會遇到一個小問題:在某些狀況下,瀏覽器可能會誤解路徑變量。考慮以下URL:

http://example.com/context/abc

瀏覽器會(正確)認爲abc是一個動做。任何靜態文件路徑的解析,如css文件,將使用http://example.com/context做爲基本路徑。這就是說,若服務器發送的網頁包含以下img元素:

<img src="logo.png"/>

該瀏覽器將試圖經過http://example.com/context/logo.png來加載logo.png。

然而,若一個應用程序被部署爲默認上下文(默認上下文路徑就是一個空字符串),則對於同一個目標的URL,會是這樣的:

http://example.com/abc

下面是帶有路徑變量的URL:

http://example.com/abc/a

在這種狀況下,瀏覽器會認爲abc是上下文,沒有動做。若是在頁面中使用<img src="logo.png"/>,瀏覽器將試圖經過http://example.com/abc/logo.png來加載圖像,而且它將找不到該圖像。

好在,咱們有一個簡單的解決方案,即經過使用JSTL標記的URL(後面博客會詳細介紹JSTL)。要使用JSTL標記,咱們必須在JSP開頭處聲明這個taglib指令:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

注意:這裏須要引入taglibs-standard-impl-1.2.5.jar包。下載地址:http://tomcat.apache.org/download-taglibs.cgi

標籤會經過正確解析URL來修復該問題。例如annotated2中全部的JSP頁面導入的全部CSS,從:

<style type="text/css">@import url(css/main.css);</style>

修改成:

<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>

若程序部署不在默認上下文,連接標籤會將URL轉換成以下所示形式:

<style type="text/css">@import url("/css/main.css");</style>

若程序部署爲默認上下文,則會被轉換成以下所示形式:

<style type="text/css">@import url("/annotated2/css/main.css");</style>

五 @ModelAttribute

前面談到的Spring MVC在每次調用請求處理方法時,都會建立Model類型的一個實例。若打算使用該實例,則能夠在方法中添加一個Model類型的參數。

    //路徑變量
    @RequestMapping(value = "/view-product/{id}")
    public String viewProduct(@PathVariable Long id, Model model) {
        Product product = productService.get(id);
        model.addAttribute("product", product);
        return "ProductView";
    }

事實上,還可使用在方法中添加ModelAttribute註解類型來訪問Model實例,該註解類型也是org.springframework.web.bind.annotation包的成員。

1)能夠用@ModelAttribute來註解方法參數。帶@ModelAttribute註解的方法參數會將該參數對象添加到Model對象(若方法中沒有顯示添加)。例如,Spring MVC將在每次調用submitOrder()方法時建立一個Order實例。

    @RequestMapping(method=RequestMethod.POST)
    public String submitOrder(@ModelAttribute("@newOrder") Order order,Model model) {
                ...
        return ...;
    }

輸入或建立的Order實例將用」newOrder「鍵值添加到Model對象中。若是未定義鍵值名,則將使用該對象類型的名稱。例如,每次調用以下方法,會使用鍵值」Order「將Order實例添加到Model對象中。

    public String submitOrder(@ModelAttribute Order order,Model model)

2)@ModelAttribute的第二個用途是標註一個非請求的處理方法。被@ModelAttribute註解的方法會在每次調用該控制器類的請求處理方法時被調用。這意味着,若是一個控制器有兩個請求處理方法,以及一個@ModelAttribute註解的方法,該方法的調用次數就會比每一個請求處理方法更頻繁。

Spring MVC會在調用請求處理方法以前調用帶@ModelAttribute註解的方法,@ModelAttribute註解的方法能夠返回一個對象或一個void類型。若是返回一個對象,則返回對象會自動的添加到Model中。

    @ModelAttribute
    public Product addProduct(@RequestParam String productId) {
        return productService.get(productId);
    }

若方法返回void,則還必須添加一個Model類型的參數,並自動將實例添加到Model中,以下面的例子所示:

    @ModelAttribute
    public void populateModel(@RequestParam String id,Model model) {
        model.addAttribute(new Account(id));
    }

參考文章

[1]Spring MVC學習指南

相關文章
相關標籤/搜索