在 Spring MVC 中控制器的主要作用就是綁定請求參數
、處理業(yè)務邏輯
逞度、返回模型數據和視圖
悍手,要定義一個控制器也很簡單,使用@Controller
注解標注一個類即可悠轩,但這還不夠间狂,還需要結合@RequestMapping
注解,它可以在類火架、方法上使用鉴象,用來指定請求 URL 可以由控制類中的那個方法來處理,這里我們主要學習以下兩方面的內容:
- 綁定請求參數
- 模型數據和視圖
- 重定向和轉發(fā)
一何鸡、綁定請求參數
1纺弊、普通請求參數
在 Spring MVC 中,如果網絡請求時傳遞的參數名稱和控制器方法的形參名稱一致骡男,則對應的參數值會自動綁定到控制器方法的形參上淆游。
先準備一個表單 RoleForm.jsp 來提交參數:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>RoleForm</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/role/commonParams" id="form" method="post">
<table>
<tr>
<td>名稱</td>
<td><input id="roleName" name="roleName" value=""/></td>
</tr>
<tr>
<td>備注</td>
<td><input id="note" name="note" value=""/></td>
</tr>
<tr>
<td></td>
<td align="right"><input id="commit" type="submit" value="提交"/></td>
</tr>
</table>
</form>
</body>
</html>
要提交的兩個參數名稱分別為roleName
、name
隔盛,地址是http://localhost:8080/role/commonParams
犹菱,再看對應的控制器類:
@Controller
@RequestMapping("/role")
public class RoleController {
@RequestMapping("/roleForm")
public String roleForm() {
return "roleForm";
}
@RequestMapping("/commonParams")
public ModelAndView commonParams(String roleName, String note) {
ModelAndView mv = new ModelAndView();
Role role = new Role();
role.setRoleName(roleName);
role.setNote(note);
mv.addObject("role", role);
mv.setViewName("result");
return mv;
}
}
形參名和表單提交的參數名一致,這樣當提交表單后吮炕,就可以直接通過形參拿到參數值腊脱,同時將請求轉發(fā)到result.jsp
來展示提交的數據,結果如下:
如果提交的參數名稱和控制器方法的形參不一致龙亲,則是無法將參數值綁定到形參上的陕凹,同時又無法修改提交的參數名稱悍抑。例如將 RoleForm.jsp 的name="roleName"
改為name="role_name"
,上邊的控制器方法自然是無法正常獲取參數值的捆姜,要解決這個問題可以使用@RequestParam
注解传趾,它的作用是將請求參數的值賦給形參:修改后的控制器方法如下:
@RequestMapping("/commonParams2")
public ModelAndView commonParams2(@RequestParam("role_name") String roleName, String note) {
return commonParams(roleName, note);
}
這樣問題就解決了,要注意的是使用了@RequestParam("role_name")
則role_name
參數的值默認不能為空泥技,否則會有異常浆兰,如果不能保證role_name
有值,可以配置required
屬性:
@RequestMapping("/commonParams2")
public ModelAndView commonParams2(@RequestParam(value = "role_name", required = false) String roleName, String note) {
return commonParams(roleName, note);
}
如果表單提交的參數過多珊豹,那么控制器方法的形參也會很多簸呈,這樣大大降到底了代碼的可讀性、增加了后期的維護成本店茶,當然 Spring MVC 早已想到了這一點蜕便,我們可以將控制器的方法參數聲明為一個對象,只要其包含的屬性名稱和請求參數的名稱一致即可贩幻,就會得到一個屬性值為提交參數的對像轿腺。
修改 RoleForm.jsp 的 action
屬性為${pageContext.request.contextPath}/role/commonParams3
,對應的控制方法如下:
@RequestMapping("/commonParams3")
public ModelAndView commonParams3(Role role) {
ModelAndView mv = new ModelAndView();
mv.addObject("role", role);
mv.setViewName("result");
return mv;
}
2丛楚、URL 模板參數
在 RESTful API 的設計規(guī)范中族壳,可以通過 URL 來傳遞參數,例如要通過 id 獲得某個角色的信息趣些,并用 JSON 格式返回仿荆,我們希望 URL 是這樣的:http://localhost:8080/role/getRole/1
,其中 1 是角色 id坏平,動態(tài)變化的拢操。當然 Spring MVC 也支持這樣的情況,通過@PathVariable
注解可以方便的獲取請求 URL 的動態(tài)參數舶替,對應的控制器方法如下:
@RequestMapping("/getRole/{id}")
@ResponseBody
public Role getRole(@PathVariable("id") long id) {
Role role = roleService.getRole(id);
return role;
}
@ResponseBody
注解可以將返回的對象轉換成 JSON 格式令境,在瀏覽器測試效果如下:
3、JSON 格式參數
如果參數結構比較復雜坎穿,為了方便而使用 JSON 格式傳遞數據也是比較常見的展父,接下來實現 JSON 數據的提交和解析。首先準備 RoleForm2.jsp玲昧,主要的作用是將表單數據已 JSON 格式提交:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>RoleForm2</title>
<script src="${pageContext.request.contextPath}/js/jquery-3.4.1.min.js"></script>
<script>
$(function () {
$("#commit").click(function () {
var data = {
id: 1,
roleName: $("#roleName").val(),
note: $("#note").val()
}
$.post({
url: "./jsonParams",
contentType: "application/json",
data: JSON.stringify(data),
success: function (result) {
alert("名稱:" + result.roleName + "\n" + "備注:" + result.note)
}
});
});
});
</script>
</head>
<body>
<form id="form">
<table>
<tr>
<td>名稱</td>
<td><input id="roleName" name="roleName" value=""/></td>
</tr>
<tr>
<td>備注</td>
<td><input id="note" name="note" value=""/></td>
</tr>
<tr>
<td></td>
<td align="right"><input id="commit" type="button" value="提交"/></td>
</tr>
</table>
</form>
</body>
</html>
這里通過 jQuery 以 JSON 格式來提交數據到http://localhost:8080/role/jsonParams
令漂,并在 success
回調中用一個彈窗回顯提交的數據乱灵,控制器方法如下:
@Controller
@RequestMapping("/role")
public class RoleController {
@RequestMapping("/roleForm2")
public String roleForm2() {
return "roleForm2";
}
@RequestMapping("/jsonParams")
@ResponseBody
public Role jsonParams(@RequestBody Role role) {
return role;
}
}
這里的重點是jsonParams
方法中的@RequestBody
注解孝常,它會將 JSON 串自動映射為對象的状共,當然 JSON 串中的屬性名要和對象中屬性名保持一致。同時jsonParams
方法將提交的數據以 JSON 格式返回尘应,以便在彈窗中獲取其屬性值惶凝。測試效果如下:
同樣的原理吼虎,我們可以將列表數據以 JSON 的格式提交,具體代碼可參考 demo苍鲜。
4思灰、序列化參數
如果表單數據比較復雜,我們自己收集數據的話會比較麻煩混滔,一種簡便的方式就是將表單序列化洒疚。我們修改 RoleForm2.jsp 提交按鈕的事件:
$("#commit").click(function () {
//提交表單
var data = $("form").serialize();
$.post({
url: "./serializeParams",
data: data,
success: function (result) {
var s = $.parseJSON(result);
alert("名稱:" + s.roleName + "\n" + "備注:" + s.note)
}
});
});
其中$("form").serialize()
就是將表單的數據序列化坯屿,返回一個字符串油湖,得到的參數如下:roleName=管理員¬e=666
。
對應的控制器方法為:
@RequestMapping("/serializeParams")
@ResponseBody
public Role serializeParams(Role role) {
return role;
}
執(zhí)行效果和圖4類似领跛。
同樣可以將表單數據序列化成 JSON 數組格式乏德,使用如下 jQuery 方法即可:$("form").serializeArray()
,得到的 JSON 數據格式參數如下:
[{
"name": "roleName",
"value": "管理員"
}, {
"name": "note",
"value": "666"
}]
二吠昭、模型數據和視圖
其實我們在綁定請求參數部分內容中喊括,已經接觸到了模型數據和視圖,在 Spring MVC 中兩者一般是搭配使用的矢棚,模型數據一般保存控制器方法中執(zhí)行業(yè)務后需要展示或返回的數據瘾晃,然后將模型數據渲染到視圖中。
1幻妓、模型數據
常用的模型數據有Model
、ModelMap
劫拢、ModelAndView
肉津。Spring MVC 在控制器方法被調用前會創(chuàng)建一個隱含的BindingAwareModelMap
類型的模型數據,這樣如果在控制器方法中聲明了Model
舱沧、ModelMap
妹沙、ModelAndView
其中某個類型的形參,則 Spring MVC 會將隱含的模型數據轉換為聲明的模型數據類型熟吏,即自動創(chuàng)建了形參的實例距糖,當然你也可以選擇手動創(chuàng)建。有了模型數據的實例牵寺,接下來就可以添加數據了悍引,之后可以在視圖頁面得到添加的數據。模型數據以鍵值對的形式來添加數據帽氓。
2趣斤、視圖
視圖分為邏輯視圖
和非邏輯視圖
,怎么理解呢黎休?
- 邏輯視圖浓领,需要通過視圖解析器解析邏輯視圖名玉凯,得到真實的視圖,然后渲染模型數據联贩,例如
InternalResourceView
漫仆,視圖解析器以及邏輯視圖的文件目錄在dispatcher_servlet.xml
中已經配置過了。 - 非邏輯視圖泪幌,無需視圖解析器盲厌,直接渲染模型數據,例如
MappingJackson2JsonView
2.1
使用邏輯視圖時座菠,控制器方法的返回值可以是String
類型狸眼,即邏輯視圖名,這樣 Spring MVC 會根據邏輯視圖名以及配置的視圖解析器的視圖文件目錄和文件后綴名找到對應的視圖文件浴滴,得到真實的視圖拓萌,如果有模型數據則渲染數據,將最終的視圖呈現給用戶升略∥⑼酰看如下的例子:
@Controller
@RequestMapping("/role")
public class RoleController {
@Autowired
private RoleService roleService;
@RequestMapping("/getRoles2")
public String getRoles2(Model model) {
List<Role> roles = roleService.getAllRole();
model.addAttribute("roles", roles);
return "role";
}
}
控制器方法getRoles2
返回邏輯視圖名role
,所以需要提前在/WEB-INF/JSP/
目錄下定義role.jsp
文件品嚣。模型數據model
中保存了角色的集合數據炕倘。所以在jsp文件中根據角色數據渲染一個表格:
<c:forEach items="${roles}" var="role">
<tr>
<td><c:out value="${role.id}"/></td>
<td><c:out value="${role.roleName}"/></td>
<td><c:out value="${role.note}"/></td>
</tr>
</c:forEach>
運行后可以看到如下效果:
2.2
控制器方法除了返回String
,還可以返回ModelAndView
翰撑,ModelAndView
既可以添加數據也可以設置邏輯視圖名罩旋,對應的控制器方法如下:
@RequestMapping("/getRoles")
public ModelAndView getRoles(ModelAndView mv) {
List<Role> roles = roleService.getAllRole();
mv.setViewName("role");
mv.addObject("roles", roles);
return mv;
}
運行效果和圖5一致。
2.3
這里補充一點眶诈,如果在控制器方法中聲明了Model
或者ModelMap
參數涨醋,同時聲明了一個類的對象參數,例如Role
來接收請求參數逝撬,那么Model
或者ModelMap
參數會自動添加聲明的Role
對象浴骂,添加數據時使用的默認 key 就是類首字母小寫的字符串,即role
宪潮,是由框架自動推斷出的溯警。
2.4
使用非邏輯視圖時,例如返回 JSON 格式的數據狡相,由于 Spring MVC 默認使用 Jackson 處理 JSON 數據梯轻,所以可以先用MappingJackson2JsonView
視圖來完成數據格式的轉換:
@RequestMapping("/getRoles3")
public ModelAndView getRoles3() {
ModelAndView mv = new ModelAndView();
List<Role> roles = roleService.getAllRole();
mv.addObject("roles", roles);
mv.setView(new MappingJackson2JsonView());
return mv;
}
當然,要返回 JSON 格式數據尽棕,更簡單的方式是使用@ResponseBody
檩淋,但本質上還是采用 Jackson 處理 JSON 數據,當然也可以整合其它的 JSON 處理框架,例如Fastjson蟀悦,這樣當使用@ResponseBody
媚朦、@RequestBody
注解時會使用 Fastjson 來處理 JSON 數據。整合的方式也很簡單日戈,使用 Fastjson 提供的FastJsonHttpMessageConverter
來替換 Spring MVC 默認的 HttpMessageConverter
即可询张,在dispatcher_servlet.xml
添加如下配置
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
使用@ResponseBody
返回 JSON 的控制器如下:
@RequestMapping("/getRoles4")
@ResponseBody
public List<Role> getRoles4() {
List<Role> roles = roleService.getAllRole();
return roles;
}
瀏覽器輸入http://localhost:8080/role/getRoles4
,效果如下:
三浙炼、重定向和轉發(fā)
首先通過一個表格來了解下重定向和轉發(fā)之間的差別:
重定向(redirect) | 轉發(fā)(forward) |
---|---|
執(zhí)行重定向會重新發(fā)起新請求 | 執(zhí)行轉發(fā)依然是上次的請求 |
地址欄的請求URL會變?yōu)樾抡埱蟮腢RL | 地址欄的請求URL不會改變 |
由于重新發(fā)起請求份氧,原請求的請求參數、request范圍內的屬性全部丟失 | 原請求的請求參數弯屈、request范圍內的屬性依然存在 |
1蜗帜、重定向(redirect)
在 Spring MVC 中,當控制器方法返回的字符串帶有redirect:
前綴時资厉,那么該字符串不是用來查找視圖的邏輯視圖名厅缺,而是要執(zhí)行重定向,我們這里所說的重定向僅代表重定向到控制器的請求處理方法宴偿。例如"redirect:./result"
湘捎,代表重定向到./result
路徑對應的控制器方法。其實要實現重定向很簡單窄刘,關鍵是如何傳遞參數窥妇。
1.1
如果要傳遞參數到目標控制器方法,可以采用model.addAttribute
方式添加參數:
@RequestMapping("/addRole")
public String addRole(Model model, Role role) {
model.addAttribute("roleName", role.getRoleName());
model.addAttribute("note", role.getNote());
return "redirect:./result";
}
目標控制器方法很簡單娩践,僅僅是展示傳遞的參數:
@RequestMapping("/result")
public String result(Model model, Role role) {
return "result";
}
大致的流程是活翩,通過表單將參數提交到addRole
控制器方法,然后傳遞參數重定向到result
控制器方法來展示提交的參數翻伺。在瀏覽器執(zhí)行后發(fā)現纱新,如果按照上述方式添加重定向參數,則在重定向時參數會以查詢參數的形式拼接到 URL 上穆趴,例如:http://localhost:8080/role/result?roleName=%E7%AE%A1%E7%90%86%E5%91%98¬e=666
1.2
由于通過model.addAttribute
添加重定向參數時,參數會拼接在 URL 上遇汞,這樣存在安全性問題未妹。我們可以考慮另外一種方式,就是我們之前學習的通過 URL 模板傳遞參數空入,代碼如下:
@RequestMapping("/addRole2")
public String addRole2(Model model, Role role) {
model.addAttribute("roleName", role.getRoleName());
model.addAttribute("note", role.getNote());
return "redirect:./result2/{roleName}";
}
model 中的roleName
會作為參數填充到 URL 中络它,note
由于和 URL 模板參數不匹配則會以查詢參數的形式拼接到 URL 上。
要重定向到的控制器方法如下歪赢,用來接收 URL 模板參數和查詢參數:
@RequestMapping("/result2/{roleName}")
public String result2(Model model, @PathVariable("roleName") String roleName, String note) {
model.addAttribute("roleName", roleName);
model.addAttribute("note", note);
return "result2";
}
重定向時的 URL 如下:http://localhost:8080/role/result2/%E7%AE%A1%E7%90%86%E5%91%98?note=666化戳,可以和第一種方式對比下。
1.3
上邊兩種方式只能添加簡單的基本類型參數,例如字符串点楼、數字等扫尖,如要傳遞對象就無能為力了,當然 Spring MVC 自然有解決方案掠廓,我們可以采用另外一個模型數據RedirectAttributes
來添加 flash 類型的參數换怖,在執(zhí)行重定向前,會將參數復制到會話(Session)中蟀瞧,當重定向后沉颂,從會話中取出參數。
添加參數的方式如下:
@RequestMapping("/addRole3")
public String addRole3(RedirectAttributes ra, Role role) {
ra.addFlashAttribute("role", role);
return "redirect:./result3";
}
重定向后悦污,參數值會自動定轉存到 model 中铸屉,當然也可以定義 Role 對象來接收參數值。
@RequestMapping("/result3")
public String result3(Model model) {
return "result";
}
重定向的 URL 如下:http://localhost:8080/role/result3切端,由于是通過會話保存參數的彻坛,并不會將參數值暴露,所以也更加的安全帆赢。
1.4
上邊的例子中小压,我們的重定向是在同一個控制器中進行的,更多的時候可能需要重定向到另一個控制器的請求處理方法椰于,我們來修改1.3的例子怠益,要重定向到的控制器類的方法如下:
@Controller
@RequestMapping("/result")
public class ResultController {
@RequestMapping("/success")
public String result(Role role) {
return "result";
}
}
編寫addRole4
方法來重定向到上邊的控制器方法:
@RequestMapping("/addRole4")
public String addRole4(RedirectAttributes ra, Role role) {
ra.addFlashAttribute("role", role);
return "redirect:/result/success";
}
注意返回值,即多了一個控制器類的根路徑/result
瘾婿,少了.
符號蜻牢。
1.5
上邊的例子重定向時都是返回以redirect:
開頭的字符串,當然也可以使用ModelAndView
偏陪,例如:mv.setViewName("redirect:/result/success")
2抢呆、轉發(fā)(forward)
2.1
和重定向類似, 在 Spring MVC 中笛谦,當控制器方法返回的字符串帶有forward:
前綴時抱虐,那么就是要執(zhí)行請求轉發(fā)操作,我們之前接觸到的返回一個邏輯視圖名饥脑,來展示相應的jsp頁面恳邀,就是一種轉發(fā),只是默認省略了forward:
灶轰。這里我們要討論的控制器方法之間的轉發(fā)谣沸。先看個例子:
@RequestMapping("/addRole5")
public String addRole5(Model model, Role role) {
model.addAttribute("roleName", role.getRoleName());
return "forward:./fail";
}
addRole5
控制器方法會直接將請求轉發(fā)到當前控制下可以相應fail
請求的控制器方法,提示當前添加的角色已存在:
@RequestMapping("/fail")
public String fail() {
return "fail";
}
2.2
如果控制器方法返回ModelAndView
笋颤,也是可以進行請求轉發(fā)的:
@RequestMapping("/addRole6")
public ModelAndView addRole6(ModelAndView mv, Role role) {
mv.addObject("roleName", role.getRoleName());
mv.setViewName("forward:./fail");
return mv;
}
2.3
當然也可以轉發(fā)到另一個控制器方法:
@RequestMapping("/addRole7")
public ModelAndView addRole7(ModelAndView mv, Role role) {
mv.addObject("roleName", role.getRoleName());
mv.setViewName("forward:/result/fail");
return mv;
}
關鍵的代碼就是mv.setViewName("forward:/result/fail")
乳附,要轉發(fā)的目標控制器方法如下:
@Controller
@RequestMapping("/result")
public class ResultController {
@RequestMapping("/fail")
public String fail() {
return "fail";
}
}
文中demo地址:https://github.com/SheHuan/MyJavaEE