1. 程式人生 > >帶你進SpringMVC接受請求引數、

帶你進SpringMVC接受請求引數、

1. 接收請求引數

1.1. 【不推薦】通過HttpServletRequest

在處理請求的方法中,新增HttpServletRequest物件作為引數,在方法體中,直接呼叫引數物件的getParameter()或類似功能的方法,即可獲取請求引數:

@RequestMapping("handle_reg.do")
public String handleReg(
        HttpServletRequest request) {
    System.out.println("UserController.handleReg()");

    String username
        = request.getParameter("username");
    String password
        = request.getParameter("password");
    Integer age
        = Integer.valueOf(request.getParameter("age"));
    String phone
        = request.getParameter("phone");
    String email
        = request.getParameter("email");

    System.out.println("username=" + username);
    System.out.println("password=" + password);
    System.out.println("age=" + age);
    System.out.println("phone=" + phone);
    System.out.println("email=" + email);

    return null;
}

1.2. 【推薦】在處理請求的方法中宣告同名引數

假設使用者提交的引數是username=root,則引數名是username,當需要獲取這個引數的值時,直接在處理請求的方法中宣告String username即可,框架會把root值直接用於呼叫處理請求的方法,即String username的值就已經是root了:

@RequestMapping("handle_reg.do")
public String handleReg(
        String username, String password, 
        Integer age, String phone, String email) {

    System.out.println("[2] username=" + username);
    System.out.println("[2] password=" + password);
    System.out.println("[2] age=" + (age + 1));
    System.out.println("[2] phone=" + phone);
    System.out.println("[2] email=" + email);   

    return null;
}

使用這種做法時,可以無視資料型別,例如希望ageInteger型別的,則直接宣告為Integer型別即可,無須自行轉換!

使用這種做法時,必須保證提交的請求引數的名稱,與處理請求的方法中的引數名稱是一致的!如果不一致,則處理請求的方法中,對應的引數值會是null值!

如果引數名稱無法統一,後續有解決方案。

這種做法最大的缺陷是:不適用於資料專案太多的表單!否則,會導致處理請求的方法中需要新增大量的引數!

1.3. 【推薦】使用自定義型別獲取多項資料

假設請求引數中包含多項資料,例如:username=admin&password=123456&age=22&phone=13900139001&email=admin%40tedu.cn

,而這些資料都可以封裝在同一個型別中,則直接使用該型別作為處理請求的引數即可:

@RequestMapping("handle_reg.do")
public String handleReg(User user) {

    System.out.println("[3] username=" + user.getUsername());
    System.out.println("[3] password=" + user.getPassword());
    System.out.println("[3] age=" + (1 + user.getAge()));
    System.out.println("[3] phone=" + user.getPhone());
    System.out.println("[3] email=" + user.getEmail()); 

    return null;
}

這種做法,適用於請求引數較多的場合!

注意:如果請求引數的引數名稱,與類中的屬性名稱不一致,則類物件中對應的屬性值為null

注意:這種做法可以與前序介紹的第2種做法組合來使用!

1.4. 小結

關於獲取請求引數,首先,並不推薦使用HttpServletRequest,主要原因是相對比較原始,編碼比較繁瑣!而宣告同名引數,或宣告物件,都是推薦的做法,至於使用哪一種,可以根據引數的數量及資料是否適合被封裝到同一個類中,綜合評定,並且,這2種做法可以組合使用!

2. 控制器的響應

2.1. 常見的響應方式

【轉發】在轉發過程中,客戶端只發出過1次請求!在瀏覽器的位址列中,也只會顯示第1次請求的路徑!轉發是在伺服器內部完成的,可以傳遞資料!

【重定向】當伺服器響應重定向時,客戶端會發出第2次請求!最終,在瀏覽器的位址列中,會顯示第2次請求的路徑!由於是2次不同的請求,基於Http協議是無狀態協議,沒有經過特殊處理(Session/Cookie/資料庫存取……)的資料是無法在2次請求之間傳遞的!

2.2. 常見的響應碼

被伺服器接收到的每個請求,在最終響應時,伺服器端都會給出一個響應碼,例如200404等。通常:

  • 2xx:正確的響應,例如200206等……
  • 3xx:重定向,例如302301等……
  • 4xx:請求錯誤,例如請求的資源不存在,或者請求型別錯誤、或者請求引數錯誤等等,例如400404405406等……
  • 5xx:伺服器內部錯誤,通常可能是出現某種異常,例如500等……

3. 轉發資料

3.1. 【不推薦】將轉發的資料封裝在HttpServletRequest物件中

可以為處理請求的方法新增HttpServletRequest request引數,當需要轉發資料時,將資料封裝在request中即可,後續也不需要顯式的執行轉發,在SpringMVC的控制器中,預設的響應方式就是轉發。

@RequestMapping("handle_reg.do")
public String handleReg(User user,
        HttpServletRequest request) {
    // 假定輸入的使用者名稱已經被佔用
    // 提示:您輸入的使用者名稱XXX已經被佔用
    request.setAttribute("msg", 
        "您輸入的使用者名稱" + user.getUsername() + "已經被佔用!");

    // 返回檢視名,也可以理解為檔案的檔名
    return "error"; // 頁面:/WEB-INF/error.jsp
} 

3.2. 【不推薦】使用ModelAndView

可以將處理請求的方法的返回值設定為ModelAndView型別,該型別的常用構造方法有:

ModelAndView()
ModelAndView(String viewName)
ModelAndView(String viewName, Map<String, ?> model)

當需要轉發資料時,可以使用以上3種中的最後一種:

@RequestMapping("handle_reg.do")
public ModelAndView handleReg(String username) {
    String viewName = "error";
    Map<String, Object> model
        = new HashMap<String, Object>();
    model.put("msg", 
            "[2] 您輸入的使用者名稱" + username + "已經被佔用!");
    ModelAndView mav
        = new ModelAndView(viewName, model);
    return mav;
}

由於這種方式使用相對比較複雜,所以,一般不推薦使用這種做法!

3.3. 【推薦】使用ModelMap封裝需要轉發的資料

使用ModelMap的流程與使用HttpServletRequest完全相同,即:方法的返回值依然使用String型別,在方法中宣告該引數,然後在方法體中直接封裝資料,最後,返回檢視名:

@RequestMapping("handle_reg.do")
public String handleReg(String username,
        ModelMap modelMap) {
    modelMap.addAttribute("msg", 
        "[3] 您輸入的使用者名稱" + username + "已經被佔用!");
    return "error";
}

3.4. 小結

在SpringMVC中,轉發資料共有3種做法,第1種使用HttpServletRequest的做法簡單直接,但是,並不推薦這樣處理,主要是因為框架已經幫我們處理了request需要執行的任務,而我們在編寫程式碼時應該儘量不干預框架的處理過程,第2種使用ModelAndView,是框架的核心處理方式,但是,因為使用方式過於麻煩,所以,也不推薦這樣使用,第3種使用ModelMap,使用簡潔,推薦使用。

3.5. 附:重定向

在SpringMVC中,當需要重定向時,首先,應該保證處理請求的方法的返回值是String型別(與轉發一樣),然後,返回值使用redirect:作為字首即可,例如:

@RequestMapping("handle_reg.do")
public String handleReg() {
    // 假設註冊成功,需要登入
    return "redirect:login.do";
}

需要注意的是:在redirect:右側的不是檢視名,而是重定向的目標的路徑,可以是絕對路徑,也可以是相對路徑。

當處理的請求的返回值型別是String時,如果返回值使用redirect:作為字首,是重定向,否則,是轉發!

4. 關於@RequestMapping註解

通過配置@RequestMapping,可以繫結請求路徑與處理請求的方法,例如:

@RequestMapping("login.do")
public String showLogin() { ...

即:通過以上配置,當接收到login.do請求時,SpringMVC會自動呼叫showLogin()方法。

除了在方法之前新增該註解以外,該註解還可以新增在控制器類的宣告之前,例如:

@RequestMapping("user")
@Controller
public class UserControler { ...

當方法之前添加了該註解之後,方法內配置的所有請求路徑,在最終訪問時都必須新增user路徑,例如:http://localhost:8080/SPRINGMVC-02-USER/user/reg.do

通常,推薦在類之前也新增該註解,方便管理路徑,例如在某個新聞管理的應用中,可能存在news_list.donews_info.do的請求,而在這個應用中,也會有使用者資料,就存在user_list.douser_info.do,可以發現,為了保證請求路徑是唯一的,都需要在路徑之前新增xxx_作為字首,這樣的管理方式是非常不方便的,在類之前新增@RequestMapping註解就可以很好的解決這個問題,每個路徑之前根本就不需要配置字首字元,也不會發生衝突!

@RequestMapping的使用過程中,路徑可以使用/作為第1個字元,也可以不需要這個字元,例如:

/user       /login.do
user        login.do
/user       login.do
user        /login.do

以上4種配置都是正確的!通常,推薦使用/作為第1個字元,即以上第1種方式!

除了配置請求路徑以外,使用@RequestMapping還可以限制請求方式,即某個路徑可以設定為只允許POST請求,而不接收GET請求!

【GET】會將請求的引數與值體現在URL中;請求的引數與值會受到URL長度限制,不適用於傳遞大量的資料;

【POST】請求的引數與值不會體現在URL中;可以傳遞大量的資料;

【選取】請求的引數與值涉及隱私(例如密碼)則必須使用POST;資料量可能較大時必須使用POST;需要共享URL且其中包含引數時必須使用GET;支援頁面重新整理必須使用GET。

【複雜度】如果要發出POST請求,只能通過<form>中的<input type="submit" /><button />,或者通過JS技術,否則,在Web領域無法發出POST請求,而這2種方式也都可以用於發出GET請求,除此以外,直接在瀏覽器中輸入某個URL發出的也是GET請求,總的來說,發GET請求要簡單得多。

【小結】參考以上“選取”原則,選擇請求方式,如果兩者均可,則使用GET即可。

@RequestMapping中配置method屬性可以限制請求型別:

@RequestMapping(value="handle_reg.do",
        method=RequestMethod.POST)
public String handleReg() {

例如以上程式碼限制了handle_reg.do必須通過POST方式來請求,如果使用GET方式,則會返回405錯誤!

只有需要限定請求方式時,才需要顯式的配置value="handlereg.do",否則,直接將"handlereg.do"配置在註解中即可!

小結:關於@RequestMapping註解,主要作用是配置請求路徑,推薦在控制器類和處理請求的方法之前都新增該註解,類之前的註解是用於配置請求路徑中的層次,方法之前的註解是用於配置請求的資源,關於路徑的配置是該屬性的value屬性,如果只配置請求路徑,可以不用顯式的宣告這是配置value屬性,而是直接把值寫出來即可,例如不需要寫成@RequestMapping(values="login.do"),而可以直接寫成@RequestMapping("login.do"),在配置路徑時,推薦使用/作為第1個字元,例如@RequestMapping("/login.do"),如果還需要限制請求方式,則必須顯式的宣告路徑為value屬性的值,並且新增配置method屬性,例如:@RequestMapping(value="handle_reg.do", method=RequestMethod.POST)

5. 關於@RequestParam註解

使用@RequestParam註解,可以解決請求引數名稱與處理請求的方法的引數名稱不一致的問題,例如:

public String handleLogin(
    @RequestParam("name") String username, 
        String password) { ...

則請求引數的名稱是name,而處理請求的方法中的引數名稱卻是username,這是可以正常執行的!

一旦使用了@RequestParam註解,預設情況下,引數就是必須的!例如配置了@RequestParam("passwd") String password後,如果請求中並不存在名為passwd的引數,則會出現400錯誤:

HTTP Status 400 - Required String parameter 'passwd' is not present

沒有提交名為passwd的引數,與提交了空值,是兩碼事!即:如果提交了passwd引數卻沒有值(例如輸入框中沒有輸入值),在伺服器將得到空字串(""),程式並不會出現錯誤!如果根本就沒有提交名為passwd的引數,則會導致400錯誤!

如果使用了@RequestParam註解,卻又不想設定為必須提交該引數,可以:

@RequestParam(value="name", required=false)

則將根據name去接收引數,如果有值,會正確接收,如果沒有(沒有提交該名稱的引數),則會是null值!

required=false時,意味著可以不必提交該引數,還可以多配置一項defaultValue屬性(The default value to use as a fallback when the request parameter value is not provided or empty. Supplying a default value implicitly sets required() to false.),表示如果請求中沒有提交該引數,則預設值是多少!例如:

@RequestParam(value="passwd", required=false, defaultValue="888999") String password

以上程式碼表示:希望請求中包含名為passwd的引數,如果有,則值用於方法的String password的引數,如果沒有,也不是必須要提供(required=false),並且使用"888999"作為預設值(defaultValue="888999"),即:在這種情況下,String password的值是"888999"

小結:@RequestParam註解是用於處理請求的方法中的引數之前,可以配置3項屬性,分別是value表示請求引數名稱,required表示請求中是否必須包含該引數,defaultValue表示引數的預設值,當有以上任何一種需求時,都需要配置該註解,即:請求引數名稱與處理請求的方法的引數名稱不一致;強制必須提交某個引數;為某個引數配置預設值。