1. 程式人生 > >SpringMVC學習(五)——SpringMVC的引數繫結

SpringMVC學習(五)——SpringMVC的引數繫結

SpringMVC中的引數繫結還是蠻重要的,所以單獨開一篇文章來講解。本文所有案例程式碼的編寫均建立在前文的案例基礎之上,因此希望讀者能仔細閱讀這篇文章。

預設支援的資料型別

現在有這樣一個需求:開啟商品編輯頁面,展示商品資訊。這是我對這個需求的分析:編輯商品資訊,需要根據商品id查詢商品資訊,然後展示到頁面。我這裡假設請求的url為/itemEdit.action,由於我想要根據商品id查詢商品資訊,所以需要傳遞商品id這樣一個引數。最終的一個響應結果就是在商品編輯頁面中展示商品詳細資訊,如下:
這裡寫圖片描述
為了解決這個需求,必然要有一個商品編輯頁面,這裡將如下editItem.jsp複製到工程的/WEB-INF/jsp目錄下。

  • 商品編輯頁面——editItem.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>修改商品資訊</title> </head> <body> <!-- 上傳圖片是需要指定屬性 enctype="multipart/form-data" --> <!-- <form id="itemForm" action="" method="post" enctype="multipart/form-data"> -->
    <form id="itemForm" action="${pageContext.request.contextPath }/updateitem.action" method="post"> <input type="hidden" name="id" value="${item.id }" /> 修改商品資訊: <table width="100%" border=1> <tr> <td>商品名稱</td> <td><input type="text" name="name" value="${item.name }" /></td> </tr> <tr> <td>商品價格</td> <td><input type="text" name="price" value="${item.price }" /></td> </tr> <tr> <td>商品簡介</td> <td><textarea rows="3" cols="30" name="detail">${item.detail }</textarea> </td> </tr> <tr> <td colspan="2" align="center"><input type="submit" value="提交" /> </td> </tr> </table> </form> </body> </html>

當然了,在商品列表展示頁面——itemList.jsp中,我們還須注意編輯修改這個超連結,如下圖所示:
這裡寫圖片描述
所有前端頁面準備好之後,接下來就要編寫後臺業務程式碼了。

編寫Service層程式碼

首先在ItemService介面中新增如下一個方法:

Items getItemById(int id);

如此一來,ItemService介面的程式碼就變為:

public interface ItemService {
    List<Items> getItemList();
    Items getItemById(int id);
}

緊接著在ItemService介面的實現類——ItemServiceImpl.java中實現以上方法,即在ItemServiceImpl實現類中新增如下方法:

@Override
public Items getItemById(int id) {
    // 根據商品id查詢商品資訊
    Items items = itemsMapper.selectByPrimaryKey(id);
    return items;
}

Controller類的引數繫結

要根據商品id查詢商品資料,需要從請求的引數中把請求的id取出來。id應該包含在Request物件中。可以從Request物件中取id。因此我們應在ItemController類中新增如下方法:

public ModelAndView editItem(HttpServletRequest request) {
    // 從request中取出引數
    String strId = request.getParameter("id");
    int id = new Integer(strId);
    // 呼叫服務
    Items items = itemService.getItemById(id);
    // 把結果傳遞給頁面
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("item", items);
    // 設定邏輯檢視
    modelAndView.setViewName("editItem");
    return modelAndView;
}

如果想獲得Request物件只需要在Controller類方法的形參中新增一個引數即可。SpringMVC框架會自動把Request物件傳遞給方法。這就是SpringMVC框架預設支援的引數型別。

SpringMVC框架預設支援的引數型別

處理器形參中新增如下型別的引數,處理介面卡會預設識別並進行賦值。

  • HttpServletRequest:通過request物件獲取請求資訊。
  • HttpServletResponse:通過response處理響應資訊。
  • HttpSession:通過session物件得到session中存放的物件。
  • Model/ModelMap:ModelMap是Model介面的實現類,我們可通過Model或ModelMap向頁面傳遞資料,如下:

    Items items = itemService.getItemById(id);
    model.addAttribute("item", items);

    頁面中通過${item.XXXX}獲取item物件的屬性值。
    使用Model和ModelMap的效果是一樣的,如果直接使用Model介面,SpringMVC會例項化ModelMap。如果使用Model介面,那麼editItem方法可以改造成:

    @RequestMapping("/itemEdit")
    public String editItem(HttpServletRequest request, 
            HttpServletResponse response, HttpSession session, Model model) {
        // 從request中取出引數
        String strId = request.getParameter("id");
        int id = new Integer(strId);
        // 呼叫服務
        Items items = itemService.getItemById(id);
    
        // 使用模型設定返回結果,model是框架給我們傳遞過來的物件,所以這個物件也不需要我們返回
        model.addAttribute("item", items); // 類似於:modelAndView.addObject("item", items);
        // 返回邏輯檢視
        return "editItem";
    }

    如果使用Model介面則可以不使用ModelAndView物件,Model物件可以向頁面傳遞資料(model是框架給我們傳遞過來的物件,所以這個物件不需要我們返回),View物件則可以使用String返回值替代。不管是Model還是ModelAndView,其本質都是使用Request物件向jsp傳遞資料。

簡單資料型別繫結

當請求的引數名稱和處理器形參名稱一致時會將請求引數與形參進行繫結。從Request取引數的方法可以進一步簡化。這樣一來,editItem方法可以改造成:

@RequestMapping("/itemEdit")
public String editItem(Integer id, Model model) { 
    // 呼叫服務
    Items items = itemService.getItemById(id);
    // 把資料傳遞給頁面,需要用到Model介面
    model.addAttribute("item", items); 
    // 返回邏輯檢視
    return "editItem";
}

支援的資料型別

SpringMVC框架支援的資料型別有:

  • 整形:Integer、int
  • 字串:String
  • 單精度:Float、float
  • 雙精度:Double、double
  • 布林型:Boolean、boolean
    說明:對於布林型別的引數,請求的引數值為true或false。處理器方法可是這樣的:

    public String editItem(Model model,Integer id,Boolean status) throws Exception {
        ...
    }

    至於請求的url,可是http://localhost:8080/xxx.action?id=2&status=false

注意:引數型別推薦使用包裝資料型別,因為基礎資料型別不可以為null

@RequestParam

使用@RequestParam註解常用於處理簡單型別的繫結。

  • value:引數名字,即入參的請求引數名字,如value=“item_id”表示請求的引數區中的名字為item_id的引數的值將傳入。
  • required:是否必須,預設是true,表示請求中一定要有相應的引數,否則將報如下錯誤:
    這裡寫圖片描述
  • defaultValue:預設值,表示如果請求中沒有同名引數時的預設值。

使用@RequestParam註解,editItem方法可以改造成:

@RequestMapping("/itemEdit")
public String editItem(@RequestParam(value="id",defaultValue="1",required=true) Integer ids, Model model) { 
    // 呼叫服務
    Items items = itemService.getItemById(ids);
    // 把資料傳遞給頁面,需要用到Model介面
    model.addAttribute("item", items); 
    // 返回邏輯檢視
    return "editItem";
}

這裡寫圖片描述
形參名稱為ids,但是這裡使用value=”id”限定請求的引數名為id,所以頁面傳遞引數的名稱必須為id。注意:如果請求引數中沒有id將丟擲異常:
這裡寫圖片描述
這裡通過required=true限定id引數為必須傳遞,如果不傳遞則報400錯誤,可以使用defaultvalue設定預設值,即使required=true也可以不傳id引數值。

繫結pojo型別

現有這樣一個需求:將頁面修改後的商品資訊儲存到資料庫表中。這是我對這個需求的分析:我這裡假設請求的url為/updateitem.action,由於我想要將頁面修改後的商品資訊儲存到資料庫表中,所以需要傳遞的引數是表單中的資料。最終的一個響應結果就是跳轉到更新成功頁面。

使用pojo接收表單資料

如果提交的引數很多,或者提交的表單中的內容很多的時候可以使用pojo接收資料。要求pojo物件中的屬性名和表單中input的name屬性一致。就像下圖所示:
這裡寫圖片描述

編寫Service層程式碼

首先在ItemService介面中新增如下一個方法:

void updateItem(Items items);

如此一來,ItemService介面的程式碼就變為:

public interface ItemService {
    List<Items> getItemList();
    Items getItemById(int id);
    void updateItem(Items items);
}

緊接著在ItemService介面的實現類——ItemServiceImpl.java中實現以上方法,即在ItemServiceImpl實現類中新增如下方法:

@Override
public void updateItem(Items items) {
    itemsMapper.updateByPrimaryKeySelective(items);
}

Controller類的引數繫結

在Controller類中使用pojo資料型別進行引數繫結,即應在ItemController類中新增如下方法:

@RequestMapping("/updateitem")
public String updateItems(Items items) {
    itemService.updateItem(items);
    // 返回成功頁面
    return "success";
}

請求的引數名稱和pojo的屬性名稱一致,會自動將請求引數賦值給pojo的屬性。注意:提交的表單中不要有日期型別的資料,否則會報400錯誤。如果想提交日期型別的資料需要用到後面的自定義引數繫結的內容
最後的響應結果就是跳轉到更新成功頁面。所以還須在/WEB-INF/jsp目錄下編寫一個jsp頁面——success.jsp,如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <h1>商品更新成功!!!</h1>
</body>
</html>

這樣,當我們修改完商品資訊之後,去資料庫表中瞧一瞧,就會看到中文亂碼情況了,如下:
這裡寫圖片描述
我們知道表單提交的方式是post,那麼如何來解決post提交的中文亂碼問題呢?我們可在web.xml檔案中加入一個過濾器,如下:

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

注意:以上只可以解決post請求亂碼問題。
對於get請求方式中文引數出現亂碼解決方法有兩個:

  1. 修改tomcat配置檔案新增編碼與工程編碼一致,如下:

    <Connector URIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
  2. 另外一種方法對引數進行重新編碼:

    String username = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8");

    ISO8859-1是tomcat預設編碼,需要將tomcat編碼後的內容按utf-8編碼。

解決完post提交的中文亂碼問題之後,再次修改商品資訊,去資料庫表中瞧一瞧,將發現一切正常。

繫結包裝pojo

現有這樣一個需求:使用包裝的pojo接收商品資訊的查詢條件。下面是我的需求分析:
首先,在com.itheima.springmvc.pojo包下編寫一個包裝類,定義如下:

public class QueryVo {
    private Items items;

    public Items getItems() {
        return items;
    }

    public void setItems(Items items) {
        this.items = items;
    }
}

然後在itemList.jsp頁面中新增如下input輸入項:

<input type="text" name="items.id" />
<input type="text" name="items.name" />

新增的位置我已在下圖框出:
這裡寫圖片描述
接著在ItemController類中新增如下方法:

@RequestMapping("/queryitem")
public String queryItem(QueryVo queryVo) {
    // 列印繫結結果
    System.out.println(queryVo.getItems().getId());
    System.out.println(queryVo.getItems().getName());
    return "success";
}

最後的一個訪問流程大致就是這樣:
這裡寫圖片描述

自定義引數繫結

有這樣一個需求:在商品修改頁面可以修改商品的生產日期,並且根據業務需求自定義日期格式。
要解決這個需求,首先要在editItem.jsp頁面中新增商品生產日期的輸入項,因此該頁面應改造為:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>修改商品資訊</title>

</head>
<body> 
    <!-- 上傳圖片是需要指定屬性 enctype="multipart/form-data" -->
    <!-- <form id="itemForm" action="" method="post" enctype="multipart/form-data"> -->
    <form id="itemForm" action="${pageContext.request.contextPath }/updateitem.action" method="post">
        <input type="hidden" name="id" value="${item.id }" /> 修改商品資訊:
        <table width="100%" border=1>
            <tr>
                <td>商品名稱</td>
                <td><input type="text" name="name" value="${item.name }" /></td>
            </tr>
            <tr>
                <td>商品價格</td>
                <td><input type="text" name="price" value="${item.price }" /></td>
            </tr>
            <tr>
                <td>商品生產日期</td>
                <td><input type="text" name="createtime"
                    value="<fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/>" /></td>
            </tr>
            <tr>
                <td>商品簡介</td>
                <td><textarea rows="3" cols="30" name="detail">${item.detail }</textarea>
                </td>
            </tr>
            <tr>
                <td colspan="2" align="center"><input type="submit" value="提交" />
                </td>
            </tr>
        </table>

    </form>
</body>

</html>

我們還是一如往前地修改商品的資訊(當然包含修改商品的生產日期),當我們點選提交按鈕時,發現報400錯誤,如下:
這裡寫圖片描述
我之前就已講過:提交的表單中不要有日期型別的資料,否則會報400錯誤。如果想提交日期型別的資料需要用到後面的自定義引數繫結的內容。所以要真正解決這個需求,就必然要用到自定義引數繫結的內容。下面是我對這個需求的分析:由於日期資料有很多種格式,所以SpringMVC沒辦法把字串轉換成日期型別。所以需要自定義引數繫結。前端控制器接收到請求後,找到註解形式的處理器介面卡,對RequestMapping標記的方法進行適配,並對方法中的形參進行引數繫結。在SpringMVC中可以在處理器介面卡上自定義Converter進行引數繫結。如果使用<mvc:annotation-driven/>,可以在此標籤上進行擴充套件。

自定義Converter

在com.itheima.springmvc.converter下編寫一個自定義Converter——DateConverter.java,如下:

/**
 * SpringMVC轉換器
 * Converter<S, T>  S:source源資料型別,T:target目標資料型別
 * @author 李阿昀
 *
 */
public class DateConverter implements Converter<String, Date> {

    @Override
    public Date convert(String source) {
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = simpleDateFormat.parse(source);
            return date;
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

}

配置Converter

如果使用<mvc:annotation-driven/>,可以在此標籤上進行擴充套件。在springmvc.xml配置檔案中新增如下配置:

<!-- 配置一個註解驅動,如果配置此標籤,那麼就可以不用配置處理器對映器和處理器介面卡 -->
<mvc:annotation-driven conversion-service="conversionService" />
<!-- 轉換器的配置 -->
<bean id="conversionService"
    class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.itheima.springmvc.converter.DateConverter"/>
        </set>
    </property>
</bean>

其實配置Converter還有另一種方式,不過實際開發中用到的很少,這裡還是講一下,按照這種方式配置完Converter之後,springmvc.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:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 配置一個掃描包,指定Controller的包,它會掃描這個包下所有帶@Controller註解的類,並建立物件放到springmvc容器中 -->
    <context:component-scan base-package="com.itheima.springmvc.controller"/>

    <!-- 轉換器配置 -->
    <bean id="conversionService"
        class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.itheima.springmvc.converter.DateConverter"/>
            </set>
        </property>
    </bean>
    <!-- 自定義webBinder -->
    <bean id="customBinder" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
        <property name="conversionService" ref="conversionService" />
    </bean>
    <!-- 註解介面卡 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
         <property name="webBindingInitializer" ref="customBinder"></property> 
    </bean>
    <!-- 註解處理器對映器 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

    <!-- 配置檢視解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>

注意:此方法需要獨立配置處理器對映器、介面卡,不再使用<mvc:annotation-driven/>
這樣當我們修改商品的資訊(當然包含修改商品的生產日期)時,就能修改成功了,並不會報400的錯誤。

SpringMVC與Struts2的不同

  1. SpringMVC的入口是一個servlet即前端控制器,而Struts2入口是一個filter過慮器。
  2. SpringMVC是基於方法開發(一個url對應一個方法),請求引數傳遞到方法的形參,可以設計為單例或多例(建議單例),Struts2是基於類開發,傳遞引數是通過類的屬性,只能設計為多例
  3. Struts2採用值棧儲存請求和響應的資料,通過OGNL存取資料,SpringMVC通過引數解析器將request請求內容解析,並給方法形參賦值,將資料和檢視封裝成ModelAndView物件,最後又將ModelAndView中的模型資料通過request域傳輸到頁面。jsp檢視解析器預設使用jstl。