您的位置:首页 > 理论基础 > 计算机网络

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的默认环境
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: