重点
- 当目标方法返回是页面重定向时,如何处理
- 当目标方法返回是请求转发是,代码如何处理
四、处理页面重定向redirect
上一章我们介绍了经过当响应体经过@ResponseBody
修饰,返回结果为JSON时是如何处理的,
那么当我们的请求返回的是一个重定向,那么这个是如何处理的
@Controller
public class WebController {
@GetMapping(value = "/index1")
public String index1(User user, Model model) {
model.addAttribute("user", user);
return "redirect:/index1";
}
@GetMapping(value = "/index2")
public String index2() {
return "index2";
}
}
调用目标方法处理业务逻辑的之前已经介绍了,那么从调用返回值处理器开始介绍
之前测试的都是浏览器直接返回例如JSON / xml的数据,存在@ResponseBody
注解的直接通过RequestResponseBodyMethodProcessor
来处理,那么现在返回的是视图,这个时候就给借助ViewNameMethodReturnValueHandler
来处理了
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
ViewNameMethodReturnValueHandler
视图名方法返回值处理器:
- 判断是否可以应用此处理器的条件是:返回值类型为字符串
- 返回值处理方法,会判断是否为重定向
public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
@Nullable
private String[] redirectPatterns;
public void setRedirectPatterns(@Nullable String... redirectPatterns) {
this.redirectPatterns = redirectPatterns;
}
@Nullable
public String[] getRedirectPatterns() {
return this.redirectPatterns;
}
// 判断是否可以应用此返回值处理器的条件是: 返回值类型是字符串
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue instanceof CharSequence) {
// 获取视图名
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
// 判断是否为重定向视图
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else if (returnValue != null) {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
// 判断是否为重定向视图的条件为:
// 1.已经配置的重定向视图是否包含这个
// 2.返回字符串是否已 `redirect:` 开头
protected boolean isRedirectViewName(String viewName) {
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}
}
processDispatchResult()
上面方法返回值处理器执行完毕以后,然后又回到doDispatch()
,在该方法的末尾,会通过processDispatchResult()
处理结果
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 处理结果 继续调用
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
render()
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
// 获取视图名
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
// 解析视图名
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
resolveViewName()
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
// 遍历视图解析器
// 默认加载的视图解析器有5个,在第1个里面包含两外4个,并且在第1个视图解析器里面也会触发方法
// 使用后面4个视图解析器 所以这里遍历也就执行1遍
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) { // 如果能处理即返回
return view;
}
}
}
return null;
}
视图解析器
public interface ViewResolver {
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FanJzfYD-1652196565232)(E:\Gitee\spingboot\ViewResolver.png)]
ContentNegotiatingViewResolver
内容协商视图解析器
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
implements ViewResolver, Ordered, InitializingBean {
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
// 获取到请求能够接收的媒体类型
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
// 根据请求的媒体类型找到最合适的视图
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
// 省略部分代码...
}
getCandidateViews()
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
// 遍历视图解析器,上面有张图显示,在第1个视图解析器里面包含另外的4个视图解析器
// 所以上面
for (ViewResolver viewResolver : this.viewResolvers) {
// 视图解析器处理视图
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
// 存在则存入候选的视图
candidateViews.add(view);
}
// 内容协商
for (MediaType requestedMediaType : requestedMediaTypes) {
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
for (String extension : extensions) {
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
// 判断是否添加默认视图
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
View
public interface View {
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
@Nullable
default String getContentType() {
return null;
}
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
view.render()
上面也有1个render()
负责处理
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("View " + formatViewName() +
", model " + (model != null ? model : Collections.emptyMap()) +
(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
// 准备响应
prepareResponse(request, response);
// 处理合并输出Model
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
renderMergedOutputModel()
@Override
protected void renderMergedOutputModel(Map<String, Object> 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);
}
sendRedirect()
protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
String targetUrl, boolean http10Compatible) throws IOException {
String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
if (http10Compatible) {
HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
if (this.statusCode != null) {
response.setStatus(this.statusCode.value());
response.setHeader("Location", encodedURL);
}
else if (attributeStatusCode != null) {
response.setStatus(attributeStatusCode.value());
response.setHeader("Location", encodedURL);
}
else {
// Send status code 302 by default.
// 最重要的 如果是
response.sendRedirect(encodedURL);
}
}
else {
HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
response.setStatus(statusCode.value());
response.setHeader("Location", encodedURL);
}
}
五、处理请求转发forward
请求转发代码处理整体思路大体一直,只不过在最后处理肯定会有差别,下面就展示了renderMergedOutputModel()
看到方法中还是使用了我们Servler最熟悉的request.getRequestDispatcher(“success.jsp”).forward(request,response)
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
// 获取到转发器
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
// 这里直接进行请求转发
rd.forward(request, response);
}
}
六、注意
在doDispatch()
中通过适配器获取到返回视图的时候,通过IDEA打断点可以看到
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
如果返回是JSON,那么mv=null,因为返回结果已经写入到了response中
如果是页面重定向,那么mv会保存重定向的视图
如果是请求转发,那么会保留参数,可以在mv中很清晰的看到目标方法的参数被携带过去
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
// 这里直接进行请求转发
rd.forward(request, response);
}
}
# 六、注意
在`doDispatch()`中通过适配器获取到返回视图的时候,通过IDEA打断点可以看到
```java
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
如果返回是JSON,那么mv=null,因为返回结果已经写入到了response中
如果是页面重定向,那么mv会保存重定向的视图
如果是请求转发,那么会保留参数,可以在mv中很清晰的看到目标方法的参数被携带过去