SpringMVC中的HTTP跳转
2015-12-11 23:37
399 查看
SpringMVC中的HTTP跳转
项目开发中经常会碰到需要进行HTTP跳转的场景,比如用户请求一个需要登录之后才可以看到的页面,而此时需要跳转到登录页面,待登录成功之后在跳转会现在的页面。那么SpringMVC是怎样实现这样的跳转的呢?今天就让我们仔细的研究一下。一,Servlet中forward与redirect
在原生的Servlet技术中有两种跳转方式,分别为forward和redirect。它们有着各自的特点和用途
1,forward重定向是在容器内部实现的同一个Web应用程序的重定向,所以forward方法只能重定向到同一个Web应用程序中的一个资源,redirect方法可以重定向到任何URL。
2,forward重定向后浏览器地址栏URL不变,redirect重定向后浏览器地址栏URL改变。
3,forward可以将请求中包含的数据传递到跳转后的地址。redirect则不能,如果想传递参数,只能放在请求行中。
4,对于客户端来说,forward重定向的过程不可见。而redirect则需要客户端配合,再次请求跳转后的地址。
二,301与302跳转
做服务端的不能不知道301和302跳转,这里的301、302指的是HTTP的响应的状态码。301的意思是原地址永久性的替换为新地址,302指当前的地址只是暂时的替换为新地址。它们实现的结果都是一样的,浏览器根据返回的新地址再次发起请求。但是,对于搜索引擎来说则意义大不相同,懂得SEO的人都会慎用301与302跳转。最后,这里说的跳转所指的都是上文说的redirect。
三,spring中redirect的实现
在spring中,当我们想进行redirect跳转的时候可以让Controller的方法返回“redirect:”开头的字符串,或者直接返回RedirectView。
若Controller方法返回的是“redirect:”开头的字符串(只要返回类型是String,都是同一个结果处理器),则对应的结果处理器是ViewNameMethodReturnValueHandler,来看看它是如何发现并处理这个跳转结果的:
public void handleReturnValue( Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { return; } else if (returnValue instanceof String) { String viewName = (String) returnValue; mavContainer.setViewName(viewName); if (isRedirectViewName(viewName)) { mavContainer.setRedirectModelScenario(true); } } else { // should not happen throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } }上面方法中的isRedirectViewName就是判断返回的字符串是否以"redirect:"开头,如果是则设置一个跳转的标志到ModelAndViewContainer 中。若Controller方法返回的直接是RedirectView,则对应的结果处理器就是ViewMethodReturnValueHandler,它和ViewNameMethodReturnValueHandler的处理过程差不多,代码如下:
public void handleReturnValue( Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { return; } else if (returnValue instanceof View){ View view = (View) returnValue; mavContainer.setView(view); if (view instanceof SmartView) { if (((SmartView) view).isRedirectView()) { mavContainer.setRedirectModelScenario(true); } } } else { // should not happen throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } }
最终,它们都要调用到RedirectView的renderMergedOutputModel方法来讲跳转的信息写入到response中。
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws IOException { String targetUrl = createTargetUrl(model, request); targetUrl = updateTargetUrl(targetUrl, model, request, response); FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request); if (!CollectionUtils.isEmpty(flashMap)) { UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build(); flashMap.setTargetRequestPath(uriComponents.getPath()); flashMap.addTargetRequestParams(uriComponents.getQueryParams()); } FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); flashMapManager.saveOutputFlashMap(flashMap, request, response); sendRedirect(request, response, targetUrl.toString(), this.http10Compatible); }
renderMergedOutputModel最后悔调用sendRedirect来设置response。
protected void sendRedirect( HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) throws IOException { String encodedRedirectURL = response.encodeRedirectURL(targetUrl); if (http10Compatible) { if (this.statusCode != null) { response.setStatus(this.statusCode.value()); response.setHeader("Location", encodedRedirectURL); } else { // Send status code 302 by default. response.sendRedirect(encodedRedirectURL); } } else { HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl); response.setStatus(statusCode.value()); response.setHeader("Location", encodedRedirectURL); } }HTTP跳转响应非常简单,只需要设置跳转的状态码和需要跳转的地址。由上面代码可知,默认情况下spring发送的是302跳转,如果想发送301跳转可以在返回的RedirectView中设置HTTP的状态码。通过ResponseStatus注解指定返回的状态码是没有用的。
四,spring对forward调整的处理
在spring中,可以通过返回以“forward:”开头的字符串的方式实现forward调整。在获取view之前,它与返回redirect字符串的处理过程都是一样的。而它们对应的View类型不同,上面说到处理redirect的view是RedirectView,而处理forward的view是InternalResourceView。导致它们不同原因是UrlBasedViewResolver创建View时做了区分,如下:
protected View createView(String viewName, Locale locale) throws Exception { // If this resolver is not supposed to handle the given view, // return null to pass on to the next resolver in the chain. if (!canHandle(viewName, locale)) { return null; } // Check for special "redirect:" prefix. if (viewName.startsWith(REDIRECT_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible()); return applyLifecycleMethods(viewName, view); } // Check for special "forward:" prefix. if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); return new InternalResourceView(forwardUrl); } // Else fall back to superclass implementation: calling loadView. return super.createView(viewName, locale); }
从InternalResourceView的名字可以直观的看出,forward是要获取内部资源。来看看它的renderMergedOutputModel方法:
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine which request handle to expose to the RequestDispatcher. HttpServletRequest requestToExpose = getRequestToExpose(request); // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, requestToExpose); // Expose helpers as request attributes, if any. exposeHelpers(requestToExpose); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(requestToExpose, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(requestToExpose, 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(requestToExpose, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.include(requestToExpose, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. exposeForwardRequestAttributes(requestToExpose); if (logger.isDebugEnabled()) { logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.forward(requestToExpose, response); } }
上面的方法先获取一个RequestDispatcher,然后通过它将请求转发到了新的路径上。这些都是利用Servlet的标准,具体内部的实现可以在tomcat的源码中查找。
注:本文所述都是基于spring3.1.2的默认环境
相关文章推荐
- http://detectmobilebrowsers.com/
- tinyhttpd 剖析
- QTcpServer与QTcpSocket通讯
- 问题之~Exuberant ctags (http://ctags.sf.net) not found in PATH
- 一些技术大牛的博客集锦(转)http://www.cnblogs.com/newpanderking/p/4366174.html
- OSI 七层协议参考模型 与 TCP/IP协议 实现网络模型 对比
- TCP并发server,每个客户一个子进程
- LTE物理层详解--- 基本介绍 http://blog.csdn.net/zzsfqiuyigui/article/details/7589251
- 上curl java 模拟http请求
- VMware WorkStation 配置ubuntu 14.4网络(NAT模式)
- 黑马程序员---网络编程(TCP、MyIE、URL)
- 无线网络
- 网络迟延
- Deep Learning论文笔记之(七)深度网络高层特征可视化
- CentOS6.5 下在Nginx中添加SSL证书以支持HTTPS协议访问
- Deep Learning论文笔记之(五)CNN卷积神经网络代码理解
- Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现
- 使用Ant包装时,包javax.servlet.http有没有搞错
- Deep Learning论文笔记之(三)单层非监督学习网络分析
- TCP/IP详解卷1 读书笔记:第二十三章 TCP保活定时器