這篇博文討論的問題是從ModelAndView如何渲染到頁面。java
首先要知道每一個請求處理完以後都會返回一個ModelAndView對象。web
這裏我分6種狀況來分析,表明6種返回類型:spring
我先貼出個人測試的後臺代碼:數組
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置掃描的包 --> <context:component-scan base-package="com"></context:component-scan> <!-- 配置視圖解析器,將方法的返回值映射到一個實際的物理視圖 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/pages/result/"></property> <property name="suffix" value=".jsp"></property> </bean> <mvc:default-servlet-handler/> <mvc:annotation-driven></mvc:annotation-driven> </beans>
package com.mmc.modelandview; import java.util.Date; import java.util.Map; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.view.InternalResourceView; import org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView; import com.mmc.common.CommonParam; @Controller public class TestModelAndView { @RequestMapping("testModelAndView") public ModelAndView testModelAndView(){ ModelAndView modelAndView=new ModelAndView(CommonParam.SUCCESS); modelAndView.addObject("time", new Date()); return modelAndView; } @RequestMapping("testModel") public String testModel(Model model){ model.addAttribute("time",new Date()); return CommonParam.SUCCESS; } @RequestMapping("testMap") public String testMap(Map<String,Object> map){ map.put("person", "lixiaolu"); return CommonParam.SUCCESS; } @RequestMapping("testView") public View testView(View view){ view=new JasperReportsPdfView(); return view; } @RequestMapping("testString") public String testString(){ return CommonParam.SUCCESS; } @RequestMapping("testForward") public String testForward(){ return "forward:/hello"; } @RequestMapping("testVoid") public void testVoid(){ System.out.println("執行testVoid方法"); } }
第一種:返回值是ModelAndView類型:spring-mvc
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } //檢查view有沒有設置,若是沒有給他設置一個默認的 applyDefaultViewName(request, mv);
//傳給咱們的攔截器,也就是說在攔截器裏咱們能夠操做視圖的映射 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; }
//就是去處理映射了 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
這個方法進去以後,通過一些判斷,會執行到一個render(mv, request, response);方法,這個方法就是渲染視圖的方法。這個方法進去以後緩存
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.resolveLocale(request);
response.setLocale(locale); View view; if (mv.isReference()) { // We need to resolve the view name.
//返回一個View對象
view = resolveViewName(mv.getViewName(), 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.isDebugEnabled()) { logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); } try {
//渲染視圖 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'", ex); } throw ex; } }
@Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isTraceEnabled()) { logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + " and static attributes " + this.staticAttributes); } //將我ModelAndView中modelAndView.addObject("time", new Date());的值放入mergeModel中 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); //判斷是否讀取本地緩存 prepareResponse(request, response);
//進一步處理,下面貼出它的代碼 renderMergedOutputModel(mergedModel, request, response); }@Overriprotected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine which request handle to expose to the RequestDispatcher.
//肯定哪些請求處理暴露給RequestDispatcher。
HttpServletRequest requestToExpose = getRequestToExpose(request); // Expose the model object as request attributes.
//把model裏面的值放入request中
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. if (logger.isDebugEnabled()) { logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.forward(requestToExpose, response); } }
當要轉發的地址,和要攜帶的信息都放入到requestToExpose裏以後,就調用rd.forward(requestToExpose, response);轉發請求到頁面。mvc
InvocableHandlerMethod類中有下面的方法:jsp
public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //mavContainer對象初始化時,會初始化一個默認的BindingAwareModelMap屬性。將默認的這個BindingAwareModelMap屬性放入arg
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder("Invoking ["); sb.append(this.getBeanType().getSimpleName()).append("."); sb.append(getMethod().getName()).append("] method with arguments "); sb.append(Arrays.asList(args)); logger.trace(sb.toString()); }
//執行個人業務方法,把arg數組傳入,把我裏面的mavContainer默認的modelMap賦上值 Object returnValue = invoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]"); } return returnValue; }
而後將model取出來,和我返回的String一塊兒構建ModelAndView對象。async
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; }
//取出model ModelMap model = mavContainer.getModel();
//構建對象 ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; }
須要分析的就是咱們加入model中的參數是怎麼加入到modelAndView對象的。
實際上是和Map類型也是同樣的,將
mavContainer對象初始化時,會初始化一個默認的BindingAwareModelMap屬性。將默認的這個BindingAwareModelMap屬性放入arg,而後把arg傳入咱們的業務方法,而後業務方法接收到這個參數,往這個參數放值。就像個人例子裏那樣
public String testModel(Model model){ model.addAttribute("time",new Date()); return CommonParam.SUCCESS; }
放好以後,model就有值了,而後根據這個model和我返回的字符串一塊兒構建ModelAndView對象。
這種我還沒搞懂怎麼用
這個類ServletInvocableHandlerMethod的方法裏:
public final void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //這裏返回個人String類型的值 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(this.responseReason)) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); try {
//進一步處理返回值 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); } throw ex; } }
而後到ViewNameMethodReturnValueHandler類的這個方法裏:
@Override public void handleReturnValue( Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { return; } else if (returnValue instanceof String) {
//把返回值賦給mavContainer的ViewName 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()); } }
而後經過mavContainer對象來構造一個ModelAndView對象,而後渲染視圖。
第五種特別篇:返回值中帶有redirect的字符串
在ViewNameMethodReturnValueHandler類中的handleReturnValue方法中:
@Override public void handleReturnValue( Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { return; } else if (returnValue instanceof String) {
//一樣是把帶有redirect的字符串賦給mavContainer對象。 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()); } }
而後在DispatcherServlet類中有這個方法:
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { for (ViewResolver viewResolver : this.viewResolvers) {
//獲取view,View是一個接口,這裏的view實際類型是RedirectView View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } return null; }
而後根據View類型的不一樣,調用的也是不一樣的renderMergedOutputModel方法,這是RedirectView類的方法:
@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); 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); if (flashMapManager == null) { throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set"); } flashMapManager.saveOutputFlashMap(flashMap, request, response); } sendRedirect(request, response, targetUrl, this.http10Compatible); }
而後就是response.sendRedirect(encodedRedirectURL);
第五種特別篇之帶forward的字符串:
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.resolveLocale(request); response.setLocale(locale); View view; if (mv.isReference()) { // We need to resolve the view name.
//選擇不一樣的類型的視圖處理器構建視圖對象
view = resolveViewName(mv.getViewName(), 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.isDebugEnabled()) { logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); } try { view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'", ex); } throw ex; } }
這個是DispatcherServlet中的resolveViewName方法。
而後是選擇了AbstractCachingViewResolver這個視圖處理器的resolveViewName方法,去建立視圖。而後調用了他的子類UrlBasedViewResolver的建立視圖方法:
@Override 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.
//得到一個forwardUrl
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類的renderMergedOutputModel方法:
@Override 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. if (logger.isDebugEnabled()) { logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); }
//調用轉發方法 rd.forward(requestToExpose, response); } }
這時咱們須要考慮兩個問題,第一:返回值是Void也就是沒有返回值,他的ModalAndView對象是什麼樣的?第二:他將轉到哪一個頁面,仍是會直接報錯?
帶着這兩個問題,咱們看源碼:
以DispatcherServlet中的這句代碼往下看,
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
一直到ServletInvocableHandlerMethod類的這個方法
public final void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(this.responseReason)) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); try {
//主要看這裏,handleReturnValue,處理返回值,那咱們點進去看看他怎麼處理null值 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); } throw ex; } }
然而結果就是他看到return是null,什麼都沒處理就返回了,只好再往下看。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } //最後發現view爲null時,這裏處理了 applyDefaultViewName(request, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Error err) { triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
接下來是組裝地址的方法:這個是處理地址組裝的類UrlBasedViewResolver
@Override protected View loadView(String viewName, Locale locale) throws Exception {
//進行組裝 AbstractUrlBasedView view = buildView(viewName); View result = applyLifecycleMethods(viewName, view); return (view.checkResource(locale) ? result : null); }
protected AbstractUrlBasedView buildView(String viewName) throws Exception { AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
//對應的我最上方spring.mvc裏面的prefix和suffix的配置的地址,把它們鏈接起來 view.setUrl(getPrefix() + viewName + getSuffix()); String contentType = getContentType(); if (contentType != null) { view.setContentType(contentType); } view.setRequestContextAttribute(getRequestContextAttribute()); view.setAttributesMap(getAttributesMap()); if (this.exposePathVariables != null) { view.setExposePathVariables(exposePathVariables); } return view; }
到此,要轉向哪個地址,ModelAndView對象是什麼樣都已經大體清楚了。而後就是調用rd.forward(requestToExpose, response);方法,渲染到頁面
裏面代碼不少,思路也不是很清楚。有意義的地方只是給小夥伴們提供了幾個方向,要想看springMvc是如何渲染視圖的,能夠從他的返回類型入手,而後debug一步步的看。源碼我是在這裏:
https://mvnrepository.com/search?q=spring-ui&p=2 下載的。我這裏用的是4.0.0的版本。最後在總結一下吧。
首先,要構造一個ModelAndView對象,這個對象的構建思路是,我建立一個數組,把一個對象放在這個數組裏,而後把數組給你,你往這個數組裏放值,而後我去取這個數組裏的值。而後獲取的值我拿來
建立一個ModelAndView對象。
第二步,去渲染視圖,有不少的渲染視圖的處理器,系統會去判斷選擇一個處理器,來處理你的請求,大概的不一樣就是,我最終是調doFord仍是doRedirect,個人返回值的header應該設置什麼等等。