還記得以前在配置參數綁定時,視圖渲染一直404- -、,註解的映射器和適配器沒問題,掃描Controller包配置也沒問題,視圖邏輯名,請求路徑都檢查過很多遍,最後發現,竟然是視圖解析器沒配置好!!崩潰!!!排除故障後,正確運行,算是當初踩過的一個大坑。
請求映射
在前面配置註解的處理器映射器和適配器時,我用的是簡寫方式,即使用標籤:
<mvc:annotation-driven></mvc:annotation-driven>
該標籤默認加載的時RequestMappingHandlerMapping和RequestMappingHandlerAdapter。當然你也可以選擇手動配置映射器和適配器的方式:
<!-- 註解形式配置映射器和適配器 -->
<!-- 註解形式的處理器映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" />
<!-- 註解形式的處理器適配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />
由Spring MVC的各個組件來看,Handler處理器和View視圖這兩個模塊是程序員需要編寫的,尤其是進行業務邏輯處理的Handler。在使用了註解的處理器映射器和適配器配置後,我們就可以在自己編寫的Handler處理器模塊,即Controller類上面,使用@Controller註解,以此來標識這個類是一個Handler處理器類,在Handler中,我們就要編寫處理請求的實現方法。
在前面的Spring MVC工作流中我們知道,前端控制器DispatcherServlet在請求完處理器映射器HandlerAdapter後,會帶着一個返回的Handler(或者說執行鏈),去請求處理器適配器HandlerAdapter,處理器適配器通過自己的supports(Object handler)方法,判斷是否支持這個類型的Handler。那麼處理器適配器是根據什麼來判斷支不支持這個Handler的呢?來看看這個判斷的過程:
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
public boolean supports(Object handler) {
return getMethodResolver(handle).hasHandlerMethods();
}
public final boolean hasHandlerMethods() {
return !this.handlerMethods.isEmpty();
}
對於每一個處理器適配器,都要實現HandlerAdapter接口,由上面源碼可以看到,supports()方法會去判斷Handler處理器中是否至少存在一個實現方法,當存在至少一個時,則表明支持。
知道了處理器適配器是根據存在方法判斷是否支持Handler處理器後,最後一個問題,處理器映射器是如何判斷使用哪一個Handler處理器的呢?在註解的處理器映射器中,答案是使用@RequestMapping註解。
@RequestMapping
@RequestMapping註解的作用是將用戶的HTTP請求,映射到可以處理該請求的Handler處理器中,即制定了某一個Handler可以處理那些URL請求。@RequestMapping註解提供的配置參數有很多,例如name屬性指定映射器的名稱,method屬性指定請求方法的類型,params屬性指定請求的參數,value和path屬性指定映射路徑等。這裏我們拿value屬性舉例,因爲它是一個十分常用到的屬性配置。@RequestMapping註解可以放在Handler處理器類上,也可以放在類中的具體實現方法上,或者同時放在類與方法上都可以。
當@RequestMapping註解放到處理器類上時,表示的是一個前置請求路徑,例如配置@RequestMapping(value=“/user”),那麼我們就可以通過請求路徑:(這是我的配置路徑,以“.action”作爲後綴)
http://localhost:8080/SpringMVC__Project/user.action
映射到該Handler進行處理。如果只把註解放在方法上,效果一樣。而如果在Handler類和具體實現方法上都放置@RequestMapping註解,那麼方法上的註解就會變成下一級URL路徑。例如這種情況:
@RequestMapping(“/user”)
Handler類 {
@RequestMapping(“/findUser“)
具體方法
}
那麼映射的請求路徑就變爲
http://localhost:8080/SpringMVC__Project/user/findUser.action
當@RequestMapping註解中只配置一個請求映射屬性時,value字樣可以省略。
前面說到,使用了annotation-driven標籤配置的處理器映射器和適配器,還提供了數據綁定功能,即在HTTP請求路徑中,可以進行數據參數的綁定。
處理器參數綁定
用戶在前端頁面發出一些請求時,通常會攜帶一些參數到後端進行處理,例如用戶登錄,註冊和查詢等操作。在Spring MVC中,數據參數綁定組件由處理器映射器負責,將HTTP請求中攜帶的數據參數綁定到Controller處理器中,接收參數的自然就是處理器類裏具體實現方法的形參。在處理器類實現方法支持的參數綁定有HttpServletRequest、HttpServletResponse、HttpSession還有Model/ModelMap。這裏使用Model/ModelMap舉例,它可以實現視圖和模型的分離,在後臺完成數據的操作後,通過model對象將數據傳達到前端頁面。
基本類型的數據參數綁定
基本類型的數據綁定,即int、long、float、double、char,boolean等。來看一個例子,假如我們要查詢id爲1的用戶信息,在處理器類中應該怎麼寫:
@Controller
@RequestMapping("/user")
public class SimpleParamBind {
private UserService userService = new UserService();
@RequestMapping(value="/queryUserById", method= {RequestMethod.GET})
public String queryUserById(Integer id, Model model) throws Exception {
// 查詢用戶信息
List<User> userList = userService.queryUserById(id);
// 通過形參中的model將數據傳到頁面
model.addAttribute("userList", userList);
return "users/userList"; // 返回視圖
}
@RequestMapping註解中除了配置了URL映射路徑,還配置了請求類型要爲GET類型,也就是說該處理器方法只接受請求GET方法類型的HTTP請求。在進行queryUserById()方法進行用戶查詢時,我們爲其提供一個參數Integer id,標識用戶id,第19行並根據傳入的id查詢用戶信息。第11行,將查詢結果userList,通過model對象的addAttribute()方法傳到前端視圖中,最後返回邏輯視圖名。按照這樣的配置,在URL請求中我們可以傳入id參數:
http://localhost:8080/SpringMVC__Project/user/queryUserById.action?id=1
查詢id爲1的用戶信息:
@RequestParam註解自定義參數別名
在URL請求中,假如我們不給id參數,或者參數名字不爲“id”,都會導致出錯,例如我們在請求路徑中把參數名改爲userId的話:
就會出錯,因爲參數名不一致嘛。不過我們可以使用@RequestParam註解,來自定義基本類型的參數綁定,也就是不需要 URL請求中參數名與Controller控制器中具體方法的參數名一致。@RequestParam註解的具體使用是在Controller方法中:
@Controller
@RequestMapping("/user")
public class SimpleParamBind {
private UserService userService = new UserService();
@RequestMapping(value="/queryUserById", method= {RequestMethod.GET})
public String queryUserById(@RequestParam(value="userId")Integer id, Model model) throws Exception {
// 查詢用戶信息
List<User> userList = userService.queryUserById(id);
// 通過形參中的model將數據傳到頁面
model.addAttribute("userList", userList);
return "users/userList"; // 返回視圖
}
第7行,我們在queryUserById()方法的參數id前加上@RequestParam註解,value=“userId”,這樣就可以在URL請求中可以接收名爲“userId”的參數傳遞:
如果我們在Controller方法中設置了參數傳遞,但在HTTP請求裏又沒有給予參數,顯然這樣會出錯:
如果我們想在參數爲空時,給予參數一個默認值,可以在@RequestParam註解中設置defaultValue屬性,來指定某一個參數的默認值:
@Controller
@RequestMapping("/user")
public class SimpleParamBind {
private UserService userService = new UserService();
@RequestMapping(value="/queryUserById", method= {RequestMethod.GET})
public String queryUserById(@RequestParam(value="userId,
defaultValue="2")Integer id, Model model) throws Exception {
// 查詢用戶信息
List<User> userList = userService.queryUserById(id);
// 通過形參中的model將數據傳到頁面
model.addAttribute("userList", userList);
return "users/userList"; // 返回視圖
}
第8行,我們設置defaultValue=“2”,爲id參數設置一個默認值,當HTTP請求中沒有id參數傳遞時,則默認查詢id爲2的用戶信息:
除了基本類型的數據參數綁定外,還可以進行JavaBean或者包裝類型的數據綁定,例如我們想要根據用戶姓名和性別兩個條件來查詢數據表中這名用戶的信息,怎麼做?
包裝類型的數據參數綁定
來看一個簡單的測試用例,在前端頁面傳遞Java包裝類參數,與Controller處理器綁定參數後,根據參數條件查詢用戶信息。首先簡單地創建一個查詢包裝類:
public class User {
private Integer id;
private String username;
private String password;
private String gender;
private String email;
private String province;
private String city;
private Date birthday;
private Integer age;
// 省略get()和set()方法
}
Service接口與實現類
接下來看具體的數據處理部分方法,在Spring MVC部分的日誌中,我沒有把它和MyBatis整合,打算放到以後做一個簡單項目例子時在整合講解,Spring MVC部分的日誌我們就專心於Spring MVC,所以在數據處理部分,我們模擬數據庫的數據初始化與查詢。首先創建數據處理部分的接口:
package com.mvc.service;
import java.util.List;
import com.mvc.model.User;
public interface UserService {
public List<User> queryUserList(); // 查詢所有顧客信息
public List<User> queryUserById(Integer id); // id查找
public List<User> queryUserByCondition(User user); // 條件查找
}
接口中定義了三個方法,分別查詢所有的顧客用戶信息,按id查找(上面的實現)以及接下來要用到的,根據包裝類信息查找。來看queryUserByCondition()方法,根據Java包裝類查詢的實現:
@Override
public List<User> queryUserByCondition(User user) {
init();
String name = user.getUsername();
String gender = user.getGender();
List<User> queryList = new ArrayList<User>();
User u = null;
for(int i=0; i<userList.size(); i++) {
u = userList.get(i);
if((!name.equals("") && u.getUsername().contains(name)) &&
(!gender.equals("") && u.getGender().contains(gender))) {
queryList.add(u);
}
}
return queryList;
}
init()方法初始化用戶數據,然後根據傳入的參數user對象,首先獲取姓名和性別屬性,接着61到67行逐一判斷,當條件不爲空且在數據表中找到對應的用戶信息,第65行就把該信息保存到查詢結果集合中,最後返回出去。
Controller處理器類
數據處理部分完成,回到Controller處理器類,它負責調用數據處理方法,完成業務邏輯,也就是將前端頁面發來的請求user對象參數,傳遞到數據處理方法中,進行條件查詢:
package com.mvc.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.mvc.model.User;
import com.mvc.service.UserServiceImpl;
@Controller
@RequestMapping("user")
public class PackParamBind {
private UserServiceImpl userService = new UserServiceImpl();
@RequestMapping("findUserByCondition")
public String queryUserByCondition(Model model, User user) {
List<User> userList = null;
if(user == null || (user.getUsername() == null && user.getGender() == null)) {
// 如果查詢框中兩個查詢條件都爲空,則默認查詢所有顧客數據
userList = userService.queryUserList();
} else {
// 否則進行條件查詢
userList = userService.queryUserByCondition(user);
}
// model數據傳到頁面
model.addAttribute("userList", userList);
return "users/queryUser"; // 返回視圖
}
}
可以看到,具體方法中需要User類的對象作爲傳入參數,如果查詢條件爲空,則進行所有數據的查詢,當查詢條件不爲空,調用方法進行條件查詢。第28行調用addAttribute()方法,將查詢結果集合userList傳遞到視圖中進行視圖渲染,最後返回的便是要使用的視圖邏輯名。
前端視圖
最後來看看我們的視圖部分,接收到後端傳來的數據,就要進行數據填充,完成視圖渲染:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用戶查詢列表</title>
</head>
<body>
<form action="findUserByCondition.action" method="post">
用戶名:<input type="text" name="username" />
性別:<input type="text" name="gender" />
<input type="submit" value="查找" />
</form>
<hr/>
<h2>搜索結果</h2>
<table width="300px;" border=1>
<tr>
<td>顧客名</td>
<td>性別</td>
<td>電子郵箱</td>
<td>省會</td>
<td>城市</td>
</tr>
<c:forEach items="${userList }" var="user">
<tr>
<td>${user.username }</td>
<td>${user.gender }</td>
<td>${user.email }</td>
<td>${user.province }</td>
<td>${user.city }</td>
</tr>
</c:forEach>
</table>
</body>
</html>
第10行,form表單中的action制定了該頁面請求地址要爲findUserByCondition.action,即對應了我們Controller處理器類中的具體方法名。第11行和12行用戶名文本框和性別文本框的name屬性分別爲username和gender,對應Java包裝類中的屬性,這樣才能被處理器適配器解析,爲其創建對應的Java實體類,通過set()方法將數據綁定到實體類的對象中進行參數傳遞。最後來看看運行結果:
當搜索框中沒有填入查詢條件時,默認查詢所有用戶的信息。當填入查詢條件後,就調用條件查詢的方法:
完整實現已上傳GitHub:
https://github.com/justinzengtm/SSM-Framework/tree/master/SpringMVC_Project