SpringMVC中的HTTP跳转

来源:互联网 发布:下载免费炒股软件 编辑:程序博客网 时间:2024/06/14 07:04

SpringMVC中的HTTP跳转

   项目开发中经常会碰到需要进行HTTP跳转的场景,比如用户请求一个需要登录之后才可以看到的页面,而此时需要跳转到登录页面,待登录成功之后在跳转会现在的页面。那么SpringMVC是怎样实现这样的跳转的呢?今天就让我们仔细的研究一下。

一,Servlet中forwardredirect

    在原生的Servlet技术中有两种跳转方式,分别为forwardredirect。它们有着各自的特点和用途 

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 happenthrow 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 happenthrow 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的默认环境
0 0
原创粉丝点击