1. 包含、转发和重定向
1.1. include
假设AServlet向BServlet发送了include请求,那么此时的处理流程为:
include前,AServlet往响应的输出流中写数据include时,BServlet可以接着往响应的输出流中写数据,并且无法设置响应状态码和响应头include后,AServlet接着往响应的输出流中写数据,并将数据响应给浏览器
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 先往输出流中写数据
response.getOutputStream().write("<include>".getBytes(StandardCharsets.UTF_8));
// 然后让BServlet继续往输出流中写数据
request.getRequestDispatcher("/b").include(request, response);
// 接着往输出流中写数据
response.getOutputStream().write("</include>".getBytes(StandardCharsets.UTF_8));
// 打印BServlet中设置的请求属性、状态码和响应头
System.out.println("attrName=" + request.getAttribute("attrName"));
System.out.println("status=" + response.getStatus());
System.out.println("headerName=" + response.getHeader("headerName"));
}
}
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 设置请求属性、状态码和响应头
// 注意,由于当前请求是include请求,因此状态码和响应头实际上并没有设置成功
request.setAttribute("attrName", "attrValue");
response.setStatus(404);
response.setHeader("headerName", "headerValue");
// 往输出流中写数据
response.getOutputStream().write("b".getBytes(StandardCharsets.UTF_8));
}
}
访问AServlet,此时浏览器页面和控制台上的打印结果分别为:
<include>b</include>
--------------------------------------------------------
attrName=attrValue
status=200
headerName=null
1.2. forward
假设AServlet向BServlet发送了forward请求,那么此时的处理流程为:
forward前,AServlet可以设置响应状态码和响应头,但往输出流中写的数据会被忽略forward时,BServlet负责往响应的输出流中写数据,并将数据响应给浏览器forward后,响应就已经处理完成了,此时AServlet对响应对象的写操作会被忽略,但仍然可以读取响应头等数据
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 设置请求属性、状态码和响应头
request.setAttribute("attrName", "attrValue");
response.setStatus(404);
response.setHeader("headerName", "headerValue");
// 往输出流中写数据
// 注意,由于稍后会forward到BServlet,因此这里写入的数据会被忽略
response.getOutputStream().write("before forward:".getBytes(StandardCharsets.UTF_8));
// 转发到BServlet,由BServlet全权负责处理请求和响应
request.getRequestDispatcher("/b").forward(request, response);
// 执行到这里,浏览器就停止转圈了,说明响应已经处理完成,此时无法再写入数据(会报错)和设置响应头
// response.getOutputStream().write("after forward".getBytes(StandardCharsets.UTF_8));
// response.addHeader("a", "b");
// 不过此时依旧可以读取请求和响应中的内容,包括BServlet给请求和响应设置的内容
// System.out.println("attrName=" + request.getAttribute("attrName"));
// System.out.println("status=" + response.getStatus());
// System.out.println("headerName=" + response.getHeader("headerName"));
}
}
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 读取AServlet设置的内容
System.out.println("attrName=" + request.getAttribute("attrName"));
System.out.println("status=" + response.getStatus());
System.out.println("headerName=" + response.getHeader("headerName"));
// 可以覆盖掉AServlet设置的内容
response.setStatus(233);
// 给浏览器响应数据
response.getOutputStream().write("b".getBytes(StandardCharsets.UTF_8));
}
}
访问AServlet,此时浏览器页面和控制台上的打印结果分别为:
b(此时响应的状态码是233,且有响应头headerName=headerValue)
--------------------------------------------------------
attrName=attrValue
status=404
headerName=headerValue
1.3. redirect
DispatcherServlet允许一个请求给另一个请求传递参数;我们在进行重定向时,有时候会用到这个功能:
- 在进行重定向前,
DispatcherServlet会把要传递给目标路径的参数保存到Session中 - 用户请求重定向的路径时,
DispatcherServlet会从Session中读取(并删除)前一个请求传的参数,将其保存到当前请求域中
@Controller
@RequestMapping
public class RedirectDemoController {
/**
* 重定向时的目标方法
*/
@ResponseBody
@GetMapping("/hello")
public String hello(HttpServletRequest request) {
// 读取前一个请求传递给当前请求的参数并打印
Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
System.out.println(inputFlashMap);
// 给浏览器响应数据
return "hello";
}
/**
* 通过返回视图名称来进行重定向
* 本方法接收RedirectAttributes参数,我们可以通过该参数来给目标路径传递数据
*/
@GetMapping("/redirect")
public String redirect(RedirectAttributes ra) {
// 添加普通属性;该键值对会直接作为请求参数拼接到目标路径中
ra.addAttribute("param", "value");
// 添加Flash属性;该键值对会被保存到FlashMap中,而FlashMap会被保存到Session中
ra.addFlashAttribute("flashName", "flashValue");
// 重定向到上面的hello()方法中
return "redirect:hello";
}
/**
* 通过返回ModelAndView来进行重定向
* 本方法的功能和上面的方法是一样的
*/
@GetMapping("/redirect2")
public ModelAndView redirect2(RedirectAttributes ra) {
ra.addFlashAttribute("flashName", "flashValue");
ModelAndView mav = new ModelAndView();
mav.addObject("param2", "value2");
mav.setViewName("redirect:hello");
return mav;
}
}
访问上面的/redirect或/redirect2接口,发现浏览器地址栏上的路径是/hello?param=value,且控制台打印如下:
FlashMap [attributes={flashName=flashValue}, targetRequestPath=/hello, targetRequestParams={param=[value]}]
值得一提的是,如果是第一次访问,则重定向的路径为会带上
JsessionId,比如:/hello;jsessionid=xxx?param=value
2. FlashMap
/**
* FlashMap用于存放前一个请求给后一个请求传递的参数,本质上是一个HashMap
*
* FlashMap中还会保存目标请求的路径及其请求参数,确保FlashMap中的数据不会被无关的请求误获取到
* 比如,重定向时,A需要向B传递数据,而浏览器在请求B前,先向C发送了请求,这种情况下,C不会误获取到B的数据
*/
public final class FlashMap extends HashMap<String, Object> implements Comparable<FlashMap> {
/**
* 目标请求的路径;可以是绝对路径或相对路径
*/
@Nullable
private String targetRequestPath;
/**
* 目标请求的请求参数
*/
private final MultiValueMap<String, String> targetRequestParams = new LinkedMultiValueMap<>(3);
/**
* 该FlashMap的过期时间戳(单位:毫秒);-1代表不过期
*/
private long expirationTime = -1;
// 省略简单的get/set方法
/**
* 指定该FlashMap几秒后过期
*/
public void startExpirationPeriod(int timeToLive) {
this.expirationTime = System.currentTimeMillis() + timeToLive * 1000;
}
/**
* 判断该FlashMap是否已过期
*/
public boolean isExpired() {
return (this.expirationTime != -1 && System.currentTimeMillis() > this.expirationTime);
}
/**
* 实现Comparable接口中的方法,用于排序
* 在调用本方法前,需要确保this和other能够匹配同一个请求,否则compare操作是没意义的
* 本方法主要在同一个请求匹配到多个FlashMap时使用,用于获取与该请求匹配度最高的FlashMap
* 显然,有明确声明目标路径的、请求参数个数多的FlashMap的匹配度最高
*/
@Override
public int compareTo(FlashMap other) {
int thisUrlPath = (this.targetRequestPath != null ? 1 : 0);
int otherUrlPath = (other.targetRequestPath != null ? 1 : 0);
if (thisUrlPath != otherUrlPath) {
return otherUrlPath - thisUrlPath;
} else {
return other.targetRequestParams.size() - this.targetRequestParams.size();
}
}
// 省略equals()/hashCode()/toString()方法
}
3. FlashMapManager
/**
* 保存和获取FlashMap的策略接口
*/
public interface FlashMapManager {
/**
* 将指定的FlashMap保存起来,并自动给FlashMap设置过期时间
* 在进行重定向之前,需要先调用本方法将当前请求产生的FlashMap保存下来,以便于在重定向之后,目标请求能够获取到
*/
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
/**
* 获取到之前请求传递给当前请求的FlashMap,并将其删除;还会顺便把过期了的FlashMap也删除掉
* 本方法需要在每次处理请求之前调用
*/
@Nullable
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
}
4. AbstractFlashMapManager
4.1. 成员变量和简单方法
/**
* 抽象的FlashMapManager,一般作为FlashMapManager实现类的基类
* 本类实现了处理FlashMap的完整流程,但并不关心FlashMap存储在哪里,子类可以自行选择怎么存储
*/
public abstract class AbstractFlashMapManager implements FlashMapManager {
/**
* 为避免并发修改,在每次更新FlashMap集合前,都需要先加锁;该常量是默认使用的锁
*/
private static final Object DEFAULT_FLASH_MAPS_MUTEX = new Object();
/**
* FlashMap的存活时间;默认保存后180秒过期
*/
private int flashMapTimeout = 180;
/**
* 和路径相关的工具组件
*/
private UrlPathHelper urlPathHelper = UrlPathHelper.defaultInstance;
// 省略上面两个字段的set/get方法
/**
* 获取更新FlashMap集合前需要获取的锁
* 默认返回共享的静态的DEFAULT_FLASH_MAPS_MUTEX
* 子类最好重写该方法来返回一个更具体的锁;或者返回null,代表不需要加锁
*/
@Nullable
protected Object getFlashMapsMutex(HttpServletRequest request) {
return DEFAULT_FLASH_MAPS_MUTEX;
}
/**
* 抽象方法,用于获取当前用户的FlashMap集合(比如从Session中获取)
*/
@Nullable
protected abstract List<FlashMap> retrieveFlashMaps(HttpServletRequest request);
/**
* 抽象方法,更新当前用户的FlashMap集合(比如重新保存到Session中)
*/
protected abstract void updateFlashMaps(
List<FlashMap> flashMaps, HttpServletRequest request, HttpServletResponse response);
}
4.2. saveOutputFlashMap()
public abstract class AbstractFlashMapManager implements FlashMapManager {
/**
* 保存指定的FlashMap
*/
@Override
public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
// 如果FlashMap中没有数据,则直接返回
if (CollectionUtils.isEmpty(flashMap)) {
return;
}
// 先对FlashMap的目标路径进行url解码,并将相对路径转成绝对路径
String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
// 设置目标路径
flashMap.setTargetRequestPath(path);
// 设置过期时间
flashMap.startExpirationPeriod(getFlashMapTimeout());
// 获取到已保存的FlashMap集合,然后将该FlashMap放到集合中,并将该集合重新保存下来;必要时先加锁
Object mutex = getFlashMapsMutex(request);
if (mutex != null) {
synchronized (mutex) {
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
allFlashMaps = (allFlashMaps != null ? allFlashMaps : new CopyOnWriteArrayList<>());
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);
}
}
/**
* 对请求路径进行url解码;并将相对路径(不以"/"开头)转成绝对路径(以"/"开头)
*/
@Nullable
private String decodeAndNormalizePath(@Nullable String path, HttpServletRequest request) {
if (path != null && !path.isEmpty()) {
// 进行url解码;编码方式默认取request.getCharacterEncoding();如果没有,则取ISO-8859-1
path = getUrlPathHelper().decodeRequestString(request, path);
// 如果该路径是相对路径,则将其转成绝对路径(将当前请求的路径的最后一层替换成path即可)
// StringUtils.cleanPath()方法比较复杂,且相对来说没那么重要,感兴趣的可以自行查看源码
if (path.charAt(0) != '/') {
// 上面提到的JsessionId是在这里拼接上的
String requestUri = getUrlPathHelper().getRequestUri(request);
path = requestUri.substring(0, requestUri.lastIndexOf('/') + 1) + path;
path = StringUtils.cleanPath(path);
}
}
return path;
}
}
4.3. retrieveAndUpdate()
public abstract class AbstractFlashMapManager implements FlashMapManager {
/**
* 获取与当前请求匹配的FlashMap并删除,同时删除过期的FlashMap
*/
@Override
@Nullable
public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {
// 先获取到所有的FlashMap;如果一个都没有,则直接返回null
List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
if (CollectionUtils.isEmpty(allFlashMaps)) {
return null;
}
// 从中获取到已过期的FlashMap,将其保存起来,准备删除
// 这里底层是在调用FlashMap的isExpire()方法来判断是否过期
List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps);
// 获取到与当前请求最匹配的FlashMap;如果有,则也将它存到待删除集合中
FlashMap match = getMatchingFlashMap(allFlashMaps, request);
if (match != null) {
mapsToRemove.add(match);
}
// 如果确实有要删除的FlashMap,则将它们从FlashMap集合中移除,然后重新保存FlashMap集合;必要时先加锁
if (!mapsToRemove.isEmpty()) {
Object mutex = getFlashMapsMutex(request);
if (mutex != null) {
synchronized (mutex) {
// 加锁后重新读取FlashMap集合,确保读取的是最新的
allFlashMaps = retrieveFlashMaps(request);
if (allFlashMaps != null) {
allFlashMaps.removeAll(mapsToRemove);
updateFlashMaps(allFlashMaps, request, response);
}
}
} else {
allFlashMaps.removeAll(mapsToRemove);
updateFlashMaps(allFlashMaps, request, response);
}
}
// 返回匹配到的FlashMap
return match;
}
/**
* 获取到与目标请求匹配的FlashMap
*/
@Nullable
private FlashMap getMatchingFlashMap(List<FlashMap> allMaps, HttpServletRequest request) {
// 依次判断这些FlashMap能否与当前请求匹配,如果能,则将其存放到临时集合中
List<FlashMap> result = new ArrayList<>();
for (FlashMap flashMap : allMaps) {
if (isFlashMapForRequest(flashMap, request)) {
result.add(flashMap);
}
}
// 如果匹配到了多个FlashMap,则返回排序后的第一个;否则返回null
if (!result.isEmpty()) {
Collections.sort(result);
return result.get(0);
}
return null;
}
/**
* 判断目标FlashMap能否与当前请求匹配
*/
protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest request) {
String expectedPath = flashMap.getTargetRequestPath();
// 如果FlashMap指定了目标路径,则目标路径必须与当前请求的路径一样
if (expectedPath != null) {
String requestUri = getUrlPathHelper().getOriginatingRequestUri(request);
if (!requestUri.equals(expectedPath) && !requestUri.equals(expectedPath + "/")) {
return false;
}
}
// 当前请求中的请求参数必须能完全覆盖该FlashMap中的请求参数
MultiValueMap<String, String> actualParams = getOriginatingRequestParams(request);
MultiValueMap<String, String> expectedParams = flashMap.getTargetRequestParams();
for (Map.Entry<String, List<String>> entry : expectedParams.entrySet()) {
List<String> actualValues = actualParams.get(entry.getKey());
if (actualValues == null) {
return false;
}
for (String expectedValue : entry.getValue()) {
if (!actualValues.contains(expectedValue)) {
return false;
}
}
}
return true;
}
}
5. SessionFlashMapManager
/**
* 本类继承自AbstractFlashMapManager,是FlashMapManager接口的唯一非抽象实现
* 本类使用Session作为FlashMap的存储介质
*/
public class SessionFlashMapManager extends AbstractFlashMapManager {
/**
* 将FlashMap集合对应的Session属性名称
*/
private static final String FLASH_MAPS_SESSION_ATTRIBUTE = SessionFlashMapManager.class.getName() + ".FLASH_MAPS";
/**
* 当前用户的FlashMap集合;如果没有Session,则返回null,否则从中获取FLASH_MAPS_SESSION_ATTRIBUTE属性对应的值
*/
@Override
@Nullable
protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
HttpSession session = request.getSession(false);
return (session != null ? (List<FlashMap>) session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE) : null);
}
/**
* 更新当前用户的FlashMap集合;如果该集合没有元素,则直接移除Session的FLASH_MAPS_SESSION_ATTRIBUTE属性
*/
@Override
protected void updateFlashMaps(List<FlashMap> flashMaps, HttpServletRequest request, HttpServletResponse response) {
WebUtils.setSessionAttribute(request, FLASH_MAPS_SESSION_ATTRIBUTE, (!flashMaps.isEmpty() ? flashMaps : null));
}
/**
* 重写父类的方法,用session.getAttribute(SESSION_MUTEX_ATTRIBUTE)作为锁(如果为null,则用Session本身作为锁)
*/
@Override
protected Object getFlashMapsMutex(HttpServletRequest request) {
return WebUtils.getSessionMutex(request.getSession());
}
}
6. SpringMVC处理重定向
6.1. 请求的预处理
我们知道,FrameworkServlet的processRequest()方法是真正开始处理请求和响应的方法
而processRequest()方法主要负责在doService()方法前后做一些初始化/收尾工作(如解析LocaleContext、记录处理结果等)
因此,doService()方法是真正处理请求和响应的方法;DispatcherServlet重写了该方法,其处理流程如下:
public class DispatcherServlet extends FrameworkServlet {
/**
* 我们知道,如果A向B发送了include请求,那么B是无法设置响应状态码和响应头的
* 但是,此时B依旧可以给请求域添加属性,或者覆盖掉已有的属性值,这有可能会影响到A的处理逻辑
*
* 鉴于这种情况,DispatcherServlet在处理include请求前,会先将Request的属性保存起来,等include请求处理完成后再进行恢复
* 该字段就相当于这个功能的开关;如果为false,那么B对Request的普通属性的操作对A来说就是可见的
*/
private boolean cleanupAfterInclude = true;
/**
* 底层使用的FlashMapManager组件;该组件会在DispatcherServlet初始化时被初始化
*/
@Nullable
private FlashMapManager flashMapManager;
/**
* 是否解析请求路径
*
* 这里涉及到Spring 5.3开始引入的PathPattern组件,该组件用于路径匹配,从而替换AntPathMatcher组件
* AntPathMatcher是比较通用的路径匹配器,而PathPattern则是专门为Http请求路径而设计的,因此效率更高
* PathPattern从Spring 6.0开始才默认开启,因此这里暂不用管该组件,了解一下即可
*/
private boolean parseRequestPath;
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// 如果当前是include请求,则先将此时的Request属性保存下来,方便后续恢复
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
// 遍历当前请求域中所有的属性
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
// 如果cleanupAfterInclude为true,或者属性名以"org.springframework.web.servlet"开头,则将属性保存到快照中
// 也就是说,不管cleanupAfterInclude取何值,DispatcherServlet设置的特殊属性都必须在include请求之后恢复原貌
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// 将Mvc容器、LocaleResolver等组件添加到请求域中,方便后续获取
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
// 重点!如果FlashMapManager组件不为null,则将前一个请求传过来的数据保存到请求域中
if (this.flashMapManager != null) {
// 取出前一个请求传过来的数据并删除;如果有,则将其作为输入的FlashMap存放到请求域中
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
// 同时,为当前请求创建输出的FlashMap,以便于在进行重定向时,将当前请求设置的Flash属性保存下来
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
// 将FlashMapManager组件也放到请求域中
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
// 如果需要解析请求路径,则先将前一个解析结果保存下来,然后解析当前请求的路径,并将解析结果存回请求域中
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
// 调用doDispatch()方法进一步处理
try {
doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// 如果当前是include请求,那么在处理完成后,将一些必要的请求属性恢复原貌
// 这里的外层还会判断当前是否开启了异步处理,我目前不太清除这么做的用意
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
// 如果开启了请求路径解析,则在处理完请求后将之前的解析结果放回请求域中
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
}
6.2. 重定向前的处理1
/**
* 该组件负责解析控制器方法中的RedirectAttributes类型的参数
*/
public class RedirectAttributesMethodArgumentResolver implements HandlerMethodArgumentResolver {
/**
* 支持处理RedirectAttributes类型的参数
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
}
/**
* 解析RedirectAttributes类型的参数;该方法的返回值会作为目标控制器方法的传参
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "RedirectAttributes argument only supported on regular handler methods");
// 创建RedirectAttributesModelMap(RedirectAttributes的实现类)对象
ModelMap redirectAttributes;
if (binderFactory != null) {
DataBinder dataBinder = binderFactory.createBinder(webRequest, null, DataBinder.DEFAULT_OBJECT_NAME);
redirectAttributes = new RedirectAttributesModelMap(dataBinder);
} else {
redirectAttributes = new RedirectAttributesModelMap();
}
// 将该对象作为重定向模型存放到ModelAndViewContainer对象中,然后将其返回
// 控制器方法中接收到的RedirectAttributes类型参数,其实就是这里创建的redirectAttributes对象
// 控制器方法对RedirectAttributes类型参数的处理,最终都会反映到这里的mavContainer对象中
mavContainer.setRedirectModel(redirectAttributes);
return redirectAttributes;
}
}
/**
* 该组件负责处理控制器方法返回的View名称
*/
public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
/**
* 重定向视图名称的模板;只要符合任意一个模板的规则,就认为该View名称是重定向的
*/
@Nullable
private String[] redirectPatterns;
// 省略redirectPatterns字段的get/set方法
/**
* 支持处理void或字符串类型的View名称
*/
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
/**
* 处理View名称;这里的mavContainer和上面提到的resolveArgument()方法中的mavContainer参数是同一个对象
*/
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 如果是字符串类型,则将View名称设置到mavContainer中,并判断该View名称是否为重定向视图
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
} else if (returnValue != null) {
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
/**
* 判断View名称是否为重定向视图
*/
protected boolean isRedirectViewName(String viewName) {
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}
}
6.3. 重定向前的处理2
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
/**
* 控制器方法的返回值被处理完成后,会调用本方法,将ModelAndViewContainer转成ModelAndView
*/
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
// 获取mavContainer中的模型对象
// 我们知道,当前视图是重定向视图,因此这里返回的是mavContainer底层的重定向模型(即RedirectAttributes对象)
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
// 判断模型对象是否为RedirectAttributes类型;如果是,则获取到其中的Flash属性,并将其存放到当前请求的输出FlashMap中
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
}
public class RedirectView extends AbstractUrlBasedView implements SmartView {
/**
* 通过ModelAndView获取到视图后,会调用视图的render()方法进行渲染,最终会执行到这里
*/
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// 获取到目标url,同时将模型中的普通属性作为请求参数拼接到该url中
String targetUrl = createTargetUrl(model, request);
// 从Mvc容器中获取RequestDataValueProcessor组件,并用该组件来处理目标url
// 目前并没有RequestDataValueProcessor接口的实现类,因此该方法可以不管,了解即可
targetUrl = updateTargetUrl(targetUrl, model, request, response);
// 获取到当前请求的输出FlashMap,并为其设置目标路径和请求参数,然后通过FlashMapManager组件将其保存起来
RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
// 进行重定向操作
sendRedirect(request, response, targetUrl, this.http10Compatible);
}
}