場景:
要講述本章的內容,首先要描述這樣一個場景:
現在有一個註冊用戶的頁面,如下:
填寫完用戶名後,點擊【註冊】,便會調用後臺controller,保存用戶名到數據庫,然後跳轉到hello頁面,如下
這裏注意地址欄,如果我們現在刷新頁面,可怕的事情就會發生,此時便會又調用了一次controller保存一次用戶名。
這裏爲了更直觀一些,我把代碼貼出來:
@Controller
public class FlashMapController {
@RequestMapping(value = "toRegister")
public String toRegister() {
return "register";
}
@RequestMapping(value = "regist")
public ModelAndView doRegist(HttpServletRequest request) {
ModelAndView model = new ModelAndView("hello");
String name = request.getParameter("name");
model.addObject("user", name);
return model;
}
}
register.jsp
<body>
<form action="regist" method="post" >
<input type="text" name="name"/><br/>
<input type="submit" value="註冊" />
</form>
</body>
hello.jsp
<body>
你好<span>${name}</span>
</body>
爲了防止刷新而導致重複提交的問題,相信很多人都知道,就是採用重定向來解決,當點擊註冊,調用後臺的controller保存完數據後,使用重定向來跳轉到hello頁面,同樣爲了更清新,把代碼粘上:
@Controller
public class FlashMapController {
@RequestMapping(value = "toRegister")
public String toRegister() {
return "register";
}
@RequestMapping(value = "regist")
public String doRegist(HttpServletRequest request) {
String name = request.getParameter("name");
request.setAttribute("user", name);
return "redirect:toHello";
}
@RequestMapping(value = "toHello")
public String toHello() {
return "hello";
}
}
效果如下,注意觀察瀏覽器的地址欄
這裏發現,此時地址欄已經不是之前的localhost:8088/regist了,這是因爲使用重定向
現在我們已經可以通過重定向來防止重複提交了,但是此時另一個問題出現了,我們的hello頁面,沒辦法把我註冊的用戶名顯示出來了。因爲重定向的原理是,瀏覽器重新發起了一次請求,新的請求將不再含有原先請求中的內容了。所以註冊時的用戶名無法獲取到了。
圈重點:那麼如果在使用重定向的情況下,還能將我們請求request的填寫的註冊用戶名顯示在hello頁面上呢,有請我們豬腳,FlashMap和RedirectAttributes
1.什麼是FlashMap和RedirectAttributes?
FlashMap和RedirectAttributes兩個都是springmvc爲了解決重定向時無法顯示源請求中內容,無法將內容傳遞給到重定向後的頁面或者controller的問題,如上面所講。
2.FlashMap和RedirectAttributes的使用實例
我們對上面的重定向的例子進行FlashMap和RedirectAttributes的改造,實現重定向後也能顯示出請求中的內容,先看例子,在講原理:
1)例子:
@Controller
public class FlashMapController {
@RequestMapping(value = "toRegister")
public String toRegister() {
return "register";
}
@RequestMapping(value = "regist")
public String doRegist(HttpServletRequest request,RedirectAttributes redirectAttributes) {
String name = request.getParameter("name");
FlashMap flashmap = RequestContextUtils.getOutputFlashMap(request);
flashmap.put("user", name);
String age = request.getParameter("age");
//addAttribute會將內容添加到url後面變爲參數的形式
//redirectAttributes.addAttribute("age", age);
redirectAttributes.addFlashAttribute("age", age);
return "redirect:toHello";
}
@RequestMapping(value = "toHello")
public String toHello() {
return "hello";
}
}
register.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>
<form action="regist" method="post" >
姓名:<input type="text" name="name"/><br/>
年齡:<input type="text" name="age"/><br/>
<input type="submit" value="註冊" />
</form>
</body>
</html>
hello.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=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
你好,<span>${user}</span><br/>
您的年齡爲:<span>${age}</span>
</body>
</html>
效果如下:
點擊註冊->
重定向後,我們發現用戶名和密碼正常顯示出來了。代碼中分別通過RedirectAttributes和FlashMap兩種方式一起使用來證明兩種形式都可以實現重定向時保存住最開始請求的數據,並在重定向後的頁面顯示出數據。至於用那個,二者選一即可。RedirectAttributes最後還是會被放到FlashMap中,下面源碼分析中將說明。
2)二者的原理:
當點擊註冊按鈕的發起/regist請求時,Springmvc會通過DispatcherServlet的doDispatch方法中的:
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
這個方法中主要調用了RequestMappingHandlerAdapter的invokeHandlerMethod()方法,如下:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 調用我們的controller中的處理請求的方法
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// controller中方法執行完成後,便會進入到這個方法中
return getModelAndView(mavContainer, modelFactory, webRequest);
}
通過RequestMappingHandlerAdapter的調用到我們controller中處理請求的方法,方法返回後,會調用RequestMappingHandlerAdapter的getModelAndView生成一個ModelAndView,這個方法會將我們代碼中redirectAttributes.addFlashAttribute("age", age);的值放到FlashMap中,這也就是爲什麼開始的時候說redirectAbbributes最終也會被放到FlashMap的原因了。源碼如下
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
...省略其它代碼
if (model instanceof RedirectAttributes) {
//獲取controller中redirectAttributes.addFlashAttribute("age", age);的值
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
// 將controller中通過FlashMap設置的值和redirectAttributes設置的值,都保存在FlashMap中
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
獲取到ModelAndView後,DispatcherServlet的doDispatch方法會繼續執行下面的
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
這裏面都做了什麼,通過下面的源碼的調用來看一下
DispatcherServlet.java
/**
*第一步
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response){
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
/**
*第二步
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
// 生成redirectView,並執行重定向請求
render(mv, request, response);
}
/**
*第三步
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) {
// 因爲我們return的是“redirect:toHello”,所以這裏會得到一個RedirectView
View view;
//org.springframework.web.servlet.view.RedirectView: name 'redirect:toHello'; URL [toHello]
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
view.render(mv.getModelInternal(), request, response);
}
/**
*第四步
*/
AbstractView.java
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
/**
*第五步
*/
RedirectView.java
/**
* Convert model to request parameters and redirect to the given URL.
* 將model中的數據轉換爲我們重定向請求request中的參數
*/
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws IOException {
//獲取重定向的的url:toHello
String targetUrl = createTargetUrl(model, request);
targetUrl = updateTargetUrl(targetUrl, model, request, response);
// 將之前保存在FlashMap中的內容讀出來來
// FlashMap [attributes={user=欄目名1, age=11}, targetRequestPath=null, targetRequestParams={}]
FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
// 方法中通過調用SessionFlashMapManager的updateFlashMaps方法將flashMap的數據保存到session的atrrbutes中,
// 這也就是爲什麼重定向還能獲取request的根本了,因爲放到session中了。
flashMapManager.saveOutputFlashMap(flashMap, request, response);
// 這裏將執行我們重定向請求
sendRedirect(request, response, targetUrl, this.http10Compatible);
}
通過面源碼的調用鏈,我們看到當我們執行return “redirect:toHello”後,DispatcherServlet會繼續調用processDispatchResult()方法去決定下一步做什麼(生成一個什麼樣的view),比如這裏因爲我們用的是return “redirect:toHello”,那麼下一步便會生成一個RedirectView,然後通過這個view去執行重定向操作(上面源碼都有)。
經過上面的調用鏈,便會執行發起重定向請求了,然後又會執行DispatcherServlet的doService方法,然後在調用doDispatch方法來找到處理toHello請求的方法(FlashMapController的toHello方法),重點來了,之前說Flashmap中的數據被保存到session中,在執行重定向請求後,是怎麼取出來的呢,就是在doService(調用controller的toHello方法之前)方法中取得,看下面源碼:
DispatcherServlet.java
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//這裏就是從session中獲取之前保存的FlashMap中的數據,大家可以debug跟進去看看
//FlashMap [attributes={user=欄目名1, age=11}, targetRequestPath=/toHello, targetRequestParams={}]
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE,
Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
doDispatch(request, response);
}
}
看到源碼中的reqeust.setAttributes方法了,這不就跟我們在學習jsp時一樣,只要在controller中使用這個方法,就可以在jsp頁面通過${}這種方式獲取到request中的值了。此處把FlashMap中保存的數據,都變成map放到attributes裏面了。所以hello.jsp就可以獲取到了。
上面講了這麼多,主要是通過源碼來解釋整個過程。
其實對於FlashMap的使用,簡單一句話就是:當執行重定向之前,將controller方法中保存在FlashMap中的數據保存到session中,當springmvc的DispatcherServlet攔截到重定向的url時,再從session中拿出FlashMap的數據放到request中,最後在調用處理這個請求的方法(toHello)。這時候這個方法中的reqeust的attributes中就已經存在FlashMap中的值了。
補充:
例子中獎了FlashMap和RedirectAttributes都可以實現重定向時數據的保存,兩者有什麼別呢?
1.RedirectAttributes可以直接在方法中注入進去,而FlashMap需要通過代碼獲得。
2.使用RedirectAttributes必須在sping-mvc.xml中開啓<mvc:annotation-driven/>才行,應該是因爲只有開啓這個才能是RequestMappingHandler起作用,才能識別出我們@Controller註解。
3.如果使用的@RequestMapping來映射url,那麼就直接使用RedirectAttributes就行,其他情況使用FlashMap即可