SpringMVC中的HTTP跳轉

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 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的默認環境
發佈了51 篇原創文章 · 獲贊 11 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章