Today to talk about a simple topic in JavaWeb, but the feeling is relatively rare, because this skill point, some friends may not have heard!

1. The origin

Speaking of Web request parameter passing, what can you think of to pass parameters?

Parameters can be placed in the address bar, but the length of the address bar parameters is limited, and in some cases we may not want them exposed in the address bar. Parameters can be placed in the request body, which is nothing to say.

Imagine this scenario, boys and girls:

In an e-commerce project, there is a request to submit an order. This request is a POST request with the parameters in the request body. After the user submits successfully, in order to prevent the repeat submission of order requests caused by the user refreshing the browser page, we generally redirect the user to a page showing the order, so that even if the user refreshes the page, the order request will not be repeated submission.

The code might look something like this:

@Controller
public class OrderController {
    @PostMapping("/order")
    public String order(OrderInfo orderInfo) {
        // Other processing logic
        return "redirect:/orderlist"; }}Copy the code

This section of code I believe we all understand it! If not, check out the free SpringMVC tutorial recorded by Songo (hardcore! Songge made another set of free videos, play! .

But here’s the problem: What if I want to pass parameters?

Redirect :/ orderList? Return “redirect:/ orderList? Return “redirect:/ orderList? xxx=xxx”; , this method of parameter transmission has two defects:

  • The length of the address bar is finite, which means that the parameters that can be placed in the address bar are limited.
  • You don’t want to put special parameters in the address bar.

So what to do? Is there any way to pass parameters?

There are! This is the flashMap that Songo is going to introduce to you today. It is designed to solve the problem of passing parameters during redirection.

2.flashMap

If you need to pass parameters during redirection, but don’t want to put them in the address bar, you can use flashMap to pass parameters.

First we define a simple page with a post request submit button as follows:

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/order" method="post">
    <input type="submit" value="Submit">
</form>
</body>
</html>
Copy the code

The request is then received on the server and the redirection is complete:

@Controller
public class OrderController {
    @PostMapping("/order")
    public String order(HttpServletRequest req) {
        FlashMap flashMap = (FlashMap) req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
        flashMap.put("name"."A little Rain in the South");
        return "redirect:/orderlist";
    }

    @GetMapping("/orderlist")
    @ResponseBody
    public String orderList(Model model) {
        return (String) model.getAttribute("name"); }}Copy the code

We get the flashMap property in the Order interface, and then we store the parameters that need to be passed, and those parameters will eventually be automatically put into the Model of the redirection interface by SpringMVC, so that we can get that property in the OrderList interface.

Of course, this is a crude notation, but we can also simplify this step by using RedirectAttributes:

@Controller
public class OrderController {
    @PostMapping("/order")
    public String order(RedirectAttributes attr) {
        attr.addFlashAttribute("site"."www.javaboy.org");
        attr.addAttribute("name"."Wechat official account: A little Rain in Jiangnan");
        return "redirect:/orderlist";
    }

    @GetMapping("/orderlist")
    @ResponseBody
    public String orderList(Model model) {
        return (String) model.getAttribute("site"); }}Copy the code

There are two ways to add parameters to RedirectAttributes:

  • AddFlashAttribute: Puts parameters into a flashMap.
  • AddAttribute: Put the parameters in the URL address.

FlashMap allows you to redirect without passing parameters through the address bar.

A redirection is a new request made by the browser. How can this new request retrieve the parameters saved in the previous request? So let’s take a look at the source code of SpringMVC.

3. Source code analysis

First there is a key class called FlashMapManager, as follows:

public interface FlashMapManager {
	@Nullable
	FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
	void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}
Copy the code

The two methods are immediately obvious:

  • RetrieveAndUpdate: This method is used to recover the parameters and remove the recovered and timeout parameters from the save medium.
  • SaveOutputFlashMap: Saves the parameters.

The FlashMapManager implementation class is as follows:

From this inherited class, we can basically determine the default save medium session. The specific save logic is in the AbstractFlashMapManager class.

The whole process of parameter transfer can be divided into three steps:

The first step is to set the parameters to outputFlashMap in two ways: The code req.getAttribute(dispatcherservlet.output_flash_map_attribute) just gets the outputFlashMap object and puts the parameters in it; The second approach is to add RedirectAttributes parameters to the interface, and then put the parameters that need to be passed into RedirectAttributes, so that when the processor is done, it automatically sets them to outputFlashMap. The specific logic in RequestMappingHandlerAdapter# getModelAndView method:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
	/ / to omit...
	if (model instanceofRedirectAttributes) { Map<String, ? > flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);if(request ! =null) { RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); }}return mav;
}
Copy the code

As you can see, if model is an instance of RedirectAttributes, the outputFlashMap attribute is obtained using the getOutputFlashMap method, and the associated attributes are set.

This is the first step, which is to save the parameters that need to be passed to the flashMap.

In the second step, the redirection view is RedirectView, and its renderMergedOutputModel method calls the saveOutputFlashMap method of FlashMapManager, Save outputFlashMap to session as follows:

protected void renderMergedOutputModel(Map
       
         model, HttpServletRequest request, HttpServletResponse response)
       ,> throws IOException {
	String targetUrl = createTargetUrl(model, request);
	targetUrl = updateTargetUrl(targetUrl, model, request, response);
	// Save flash attributes
	RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
	// Redirect
	sendRedirect(request, response, targetUrl, this.http10Compatible);
}
Copy the code

RequestContextUtils. SaveOutputFlashMap method is invoked to eventually FlashMapManager saveOutputFlashMap method, will outputFlashMap preserved. Let’s take a quick look at the save logic:

public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
	if (CollectionUtils.isEmpty(flashMap)) {
		return;
	}
	String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
	flashMap.setTargetRequestPath(path);
	flashMap.startExpirationPeriod(getFlashMapTimeout());
	Object mutex = getFlashMapsMutex(request);
	if(mutex ! =null) {
		synchronized(mutex) { List<FlashMap> allFlashMaps = retrieveFlashMaps(request); allFlashMaps = (allFlashMaps ! =null ? allFlashMaps : newCopyOnWriteArrayList<>()); allFlashMaps.add(flashMap); updateFlashMaps(allFlashMaps, request, response); }}else{ List<FlashMap> allFlashMaps = retrieveFlashMaps(request); allFlashMaps = (allFlashMaps ! =null ? allFlashMaps : new ArrayList<>(1)); allFlashMaps.add(flashMap); updateFlashMaps(allFlashMaps, request, response); }}Copy the code

Before saving the flashMap, you will set two properties, one is the REdirected URL address, and the other is the expiration time. The default expiration time is 180 seconds. These two properties will be used when loading the flashMap in the third step. The flashMap is then put into the collection and stored into the session by calling the updateFlashMaps method.

Step 3, after the redirect request reaches the DispatcherServlet#doService method, The FlashMapManager#retrieveAndUpdate method is called to retrieve the outputFlashMap from the Session and set it to the Request property (which will eventually be converted to the property in the Model) for use as follows:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	/ / to omit...
	if (this.flashMapManager ! =null) {
		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);
	}
	/ / to omit...
}
Copy the code

Notice that the outputFlashMap that we got here has changed its name to inputFlashMap, but it’s the same thing.

AbstractFlashMapManager#retrieveAndUpdate AbstractFlashMapManager#retrieveAndUpdate:

public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {
	List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
	if (CollectionUtils.isEmpty(allFlashMaps)) {
		return null;
	}
	List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps);
	FlashMap match = getMatchingFlashMap(allFlashMaps, request);
	if(match ! =null) {
		mapsToRemove.add(match);
	}
	if(! mapsToRemove.isEmpty()) { Object mutex = getFlashMapsMutex(request);if(mutex ! =null) {
			synchronized (mutex) {
				allFlashMaps = retrieveFlashMaps(request);
				if(allFlashMaps ! =null) { allFlashMaps.removeAll(mapsToRemove); updateFlashMaps(allFlashMaps, request, response); }}}else{ allFlashMaps.removeAll(mapsToRemove); updateFlashMaps(allFlashMaps, request, response); }}return match;
}
Copy the code
  • The retrieveFlashMaps method is first called to retrieve all flashmaps from the session.
  • Call the getExpiredFlashMaps method to get all expired Flashmaps. The default expiration time of flashMaps is 180s.
  • GetMatchingFlashMap getMatchingFlashMap getMatchingFlashMap getMatchingFlashMap getMatchingFlashMap getMatchingFlashMap getMatchingFlashMap getMatchingFlashMap getMatchingFlashMap getMatchingFlashMap getMatchingFlashMap getMatchingFlashMap The default parameters must be the same. Generally we do not need to configure default parameters, so this can be ignored. If you want to set it, first set the flashMap like this:flashMap.addTargetRequestParam("aa", "bb");And then add this parameter to the redirection address bar:return "redirect:/orderlist? aa=bb";Can.
  • Put the obtained matched FlashMap object into the mapsToRemove collection (this matched FlashMap is about to expire and will be emptied in the collection).
  • Empty all mapsToRemove data in allFlashMaps collection, and call updateFlashMaps method to update the FlashMap in session.
  • Finally, the matched flashMap is returned.

This is the whole method to get flashMap, overall is very easy, there is no difficulty.

4. Summary

Ok, today I shared with my friends the SpringMVC flashMap. I wonder if you have used this thing in your work. If you happen to have the requirements that Songo mentioned earlier, using flashMaps is really quite handy. If you need to download the case of this article, friends can reply 20210302 in the background of the public account [Jiangnan little Rain], well, today and you talk so much ~