Spring MVCjava
<!-- more -->ios
在 IDEA 中新建一個 web 項目,用 Maven 管理項目的話,在 pom.xml 中加入 Spring MVC 和 Servlet 依賴便可。web
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.9.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
在 web.xml 中配置 Servletspring
建立 Spring MVC 的 xml 配置文件api
建立 Controller 和 View數組
一、web.xml瀏覽器
<!-- Spring MVC配置 --> <!-- ====================================== --> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 能夠自定義servlet.xml配置文件的位置和名稱,默認爲WEB-INF目錄下,名稱爲[<servlet-name>]-servlet.xml,如spring-servlet.xml <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-servlet.xml</param-value> 默認 </init-param> --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <!-- Spring配置 --> <!-- ====================================== --> <listener> <listenerclass> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- 指定Spring Bean的配置文件所在目錄。默認配置在WEB-INF目錄下 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:config/applicationContext.xml</param-value> </context-param>
二、spring-servlet.xmlspring-mvc
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context <a href="http://www.springframework.org/schema/context/spring-context-3.0.xsd">http://www.springframework.org/schema/context/spring-context-3.0.xsd</a>"> <!-- 啓用spring mvc 註解 --> <context:annotation-config /> <!-- 設置使用註解的類所在的jar包 --> <context:component-scan base-package="controller"></context:component-scan> <!-- 完成請求和註解POJO的映射 --> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> <!-- 對轉向頁面的路徑解析。prefix:前綴, suffix:後綴 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/jsp/" p:suffix=".jsp" /> </beans>
三、Controller服務器
package controller; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import entity.User; @Controller //相似Struts的Action public class TestController { @RequestMapping("/test/login.do") // 請求url地址映射,相似Struts的action-mapping public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) { // @RequestParam是指請求url地址映射中必須含有的參數(除非屬性 required=false, 默認爲 true) // @RequestParam可簡寫爲:@RequestParam("username") if (!"admin".equals(username) || !"admin".equals(password)) { return "loginError"; // 跳轉頁面路徑(默認爲轉發),該路徑不須要包含spring-servlet配置文件中配置的前綴和後綴 } return "loginSuccess"; } @RequestMapping("/test/login2.do") public ModelAndView testLogin2(String username, String password, int age){ // request和response沒必要非要出如今方法中,若是用不上的話能夠去掉 // 參數的名稱是與頁面控件的name相匹配,參數類型會自動被轉換 if (!"admin".equals(username) || !"admin".equals(password) || age < 5) { return new ModelAndView("loginError"); // 手動實例化ModelAndView完成跳轉頁面(轉發),效果等同於上面的方法返回字符串 } return new ModelAndView(new RedirectView("../index.jsp")); // 採用重定向方式跳轉頁面 // 重定向還有一種簡單寫法 // return new ModelAndView("redirect:../index.jsp"); } @RequestMapping("/test/login3.do") public ModelAndView testLogin3(User user) { // 一樣支持參數爲表單對象,相似於Struts的ActionForm,User不須要任何配置,直接寫便可 String username = user.getUsername(); String password = user.getPassword(); int age = user.getAge(); if (!"admin".equals(username) || !"admin".equals(password) || age < 5) { return new ModelAndView("loginError"); } return new ModelAndView("loginSuccess"); } @Resource(name = "loginService") // 獲取applicationContext.xml中bean的id爲loginService的,並注入 private LoginService loginService; //等價於spring傳統注入方式寫get和set方法,這樣的好處是簡潔工整,省去了沒必要要得代碼 @RequestMapping("/test/login4.do") public String testLogin4(User user) { if (loginService.login(user) == false) { return "loginError"; } return "loginSuccess"; } }
@RequestMapping 能夠寫在方法上,也能夠寫在類上,上面代碼方法上的 RequestMapping 都含有 /test
, 那麼咱們就能夠將其抽出直接寫在類上,那麼方法裏面就不須要寫 /test
了。session
以下便可:
@Controller @RequestMapping("/test") public class TestController { @RequestMapping("/login.do") // 請求url地址映射,相似Struts的action-mapping public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) { // @RequestParam是指請求url地址映射中必須含有的參數(除非屬性 required=false, 默認爲 true) // @RequestParam可簡寫爲:@RequestParam("username") if (!"admin".equals(username) || !"admin".equals(password)) { return "loginError"; // 跳轉頁面路徑(默認爲轉發),該路徑不須要包含spring-servlet配置文件中配置的前綴和後綴 } return "loginSuccess"; } //省略其餘的 }
上面的代碼方法的參數中能夠看到有一個 @RequestParam
註解,其實還有 @PathVariable
。這兩個的區別是啥呢?
@PathVariable
標記在方法的參數上,利用它標記的參數能夠利用請求路徑傳值。
@RequestParam是指請求url地址映射中必須含有的參數(除非屬性 required=false, 默認爲 true)
看以下例子:
@RequestMapping("/user/{userId}") // 請求url地址映射 public String userinfo(Model model, @PathVariable("userId") int userId, HttpSession session) { System.out.println("進入 userinfo 頁面"); //判斷是否有用戶登陸 User user1 = (User) session.getAttribute("user"); if (user1 == null) { return "login"; } User user = userService.selectUserById(userId); model.addAttribute("user", user); return "userinfo"; }
上面例子中若是瀏覽器請求的是 /user/1
的時候,就表示此時的用戶 id 爲 1,此時就會先從 session 中查找是否有 「user」 屬性,若是有的話,就表明用戶此時處於登陸的狀態,若是沒有的話,就會讓用戶返回到登陸頁面,這種機制在各類網站常常會使用的,而後根據這個 id = 1 ,去查找用戶的信息,而後把查找的 「user」 放在 model 中,而後返回用戶詳情頁面,最後在頁面中用 $!{user.name}
獲取用戶的名字,一樣的方式能夠獲取用戶的其餘信息,把全部的用戶詳情信息展現出來。
Spring MVC 核心 Servlet 架構圖以下:
Java 中經常使用的 Servlet 我在另一篇文章寫的很清楚了,有興趣的請看:經過源碼詳解 Servlet ,這裏我就再也不解釋了。
這裏主要講 Spring 中的 HttpServletBean、FrameworkServlet、DispatcherServlet 這三個類的建立過程。
經過上面的圖,能夠看到這三個類直接實現三個接口:EnvironmentCapable、EnvironmentAware、ApplicationContextAware。下面咱們直接看下這三個接口的內部是怎樣寫的。
EnvironmentCapable.java
public interface EnvironmentCapable { //返回組件的環境,可能返回 null 或者默認環境 @Nullable Environment getEnvironment(); }
EnvironmentAware.java
public interface EnvironmentAware extends Aware { //設置組件的運行環境 void setEnvironment(Environment environment); }
ApplicationContextAware.java
public interface ApplicationContextAware extends Aware { //設置運行對象的應用上下文 //當類實現這個接口後,這個類能夠獲取ApplicationContext中全部的bean,也就是說這個類能夠直接獲取Spring配置文件中全部有引用到的bean對象 void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
怎麼使用這個這個接口呢?
參考文章:org.springframework.context.ApplicationContextAware使用理解
HttpServletBean
這裏就直接看其中最重要的 init() 方法的代碼了:
/** * 將配置參數映射到此servlet的bean屬性,並調用子類初始化。 * 若是 bean 配置不合法(或者須要的參數丟失)或者子類初始化發生錯誤,那麼就會拋出 ServletException 異常 */ @Override public final void init() throws ServletException { //日誌代碼刪除了 // 從init參數設置bean屬性。 //得到web.xml中的contextConfigLocation配置屬性,就是spring MVC的配置文件 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); //獲取服務器的各類信息 ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); //模板方法,能夠在子類中調用,作一些初始化工做,bw表明DispatcherServelt initBeanWrapper(bw); //將配置的初始化值設置到DispatcherServlet中 bw.setPropertyValues(pvs, true); } catch (BeansException ex) { //日誌代碼 throw ex; } } // Let subclasses do whatever initialization they like. //模板方法,子類初始化的入口方法 initServletBean(); //日誌代碼刪除了 }
FrameworkServlet
其中重要方法以下:裏面也就兩句關鍵代碼,日誌代碼我直接刪掉了
protected final void initServletBean() throws ServletException { //日誌代碼刪除了 long startTime = System.currentTimeMillis(); //就是 try 語句裏面有兩句關鍵代碼 try { //初始化 webApplicationContext this.webApplicationContext = initWebApplicationContext(); //模板方法, initFrameworkServlet(); } catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } //日誌代碼刪除了 }
再來看看上面代碼中調用的 initWebApplicationContext() 方法
protected WebApplicationContext initWebApplicationContext() { //獲取 rootContext WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // 上下文實例在構造時注入 - >使用它 wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // 若是上下文還沒有刷新 -> 提供諸如設置父上下文,設置應用程序上下文ID等服務 if (cwac.getParent() == null) { // 上下文實例被注入沒有顯式的父類 -> 將根應用程序上下文(若是有的話可能爲null)設置爲父級 cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // 當 WebApplicationContext 已經存在 ServletContext 中時,經過配置在 servlet 中的 ContextAttribute 參數獲取 wac = findWebApplicationContext(); } if (wac == null) { // 若是 WebApplicationContext 尚未建立,則建立一個 wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // 當 ContextRefreshedEvent 事件沒有觸發時調用此方法,模板方法,能夠在子類重寫 onRefresh(wac); } if (this.publishContext) { // 將 ApplicationContext 保存到 ServletContext 中去 String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }
initWebApplicationContext 方法作了三件事:
獲取 Spring 的根容器 rootContext
設置 webApplicationContext 並根據狀況調用 onRefresh 方法
將 webApplicationContext 設置到 ServletContext 中
這裏在講講上面代碼中的 wac == null 的幾種狀況:
1)、當 WebApplicationContext 已經存在 ServletContext 中時,經過配置在 servlet 中的 ContextAttribute 參數獲取,調用的是 findWebApplicationContext() 方法
protected WebApplicationContext findWebApplicationContext() { String attrName = getContextAttribute(); if (attrName == null) { return null; } WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } return wac; }
2)、若是 WebApplicationContext 尚未建立,調用的是 createWebApplicationContext 方法
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { //獲取建立類型 Class<?> contextClass = getContextClass(); //刪除了打印日誌代碼 //檢查建立類型 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } //具體建立 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); //並設置的 contextConfigLocation 參數傳給 wac,默認是 WEB-INFO/[ServletName]-Servlet.xml wac.setConfigLocation(getContextConfigLocation()); //調用的是下面的方法 configureAndRefreshWebApplicationContext(wac); return wac; } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value // -> assign a more useful id based on available information if (this.contextId != null) { wac.setId(this.contextId); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); } } wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); // The wac environment's #initPropertySources will be called in any case when the context // is refreshed; do it eagerly here to ensure servlet property sources are in place for // use in any post-processing or initialization that occurs below prior to #refresh ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); wac.refresh(); }
裏面還有 doXXX() 方法,你們感興趣的能夠去看看。
DispatcherServlet
DispatcherServlet 繼承自 FrameworkServlet,onRefresh 方法是 DispatcherServlet 的入口方法,在 initStrategies 方法中調用了 9 個初始化的方法。
這裏分析其中一個初始化方法:initLocaleResolver() 方法
private void initLocaleResolver(ApplicationContext context) { try { //在 context 中獲取 this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); //刪除了打印日誌的代碼 } catch (NoSuchBeanDefinitionException ex) { //使用默認的策略 this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); //刪除了打印日誌的代碼 } }
查看默認策略代碼:
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) { //調用 getDefaultStrategies 方法 List<T> strategies = getDefaultStrategies(context, strategyInterface); if (strategies.size() != 1) { throw new BeanInitializationException( "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]"); } return strategies.get(0); } /** * Create a List of default strategy objects for the given strategy interface. * <p>The default implementation uses the "DispatcherServlet.properties" file (in the same * package as the DispatcherServlet class) to determine the class names. It instantiates * the strategy objects through the context's BeanFactory. */ @SuppressWarnings("unchecked") protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { String key = strategyInterface.getName(); //根據策略接口的名字從 defaultStrategies 獲取所需策略的類型 String value = defaultStrategies.getProperty(key); if (value != null) { //若是有多個默認值的話,就以逗號分隔爲數組 String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<>(classNames.length); //按獲取到的類型初始化策略 for (String className : classNames) { try { Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } catch (ClassNotFoundException ex) { throw new BeanInitializationException( "Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex); } catch (LinkageError err) { throw new BeanInitializationException( "Error loading DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]: problem with class file or dependent class", err); } } return strategies; } else { return new LinkedList<>(); } }
其餘幾個方法大概也相似,我就再也不寫了。
主要講了 Spring MVC 自身建立過程,分析了 Spring MVC 中 Servlet 的三個層次:HttpServletBean、FrameworkServlet 和 DispatcherServlet。HttpServletBean 繼承自 Java 的 HttpServlet,其做用是將配置的參數設置到相應的屬性上;FrameworkServlet 初始化了 WebApplicationContext;DispatcherServlet 初始化了自身的 9 個組件。
分析 Spring MVC 是怎麼處理請求的。首先分析 HttpServletBean、FrameworkServlet 和 DispatcherServlet 這三個 Servlet 的處理過程,最後分析 doDispatcher 的結構。
HttpServletBean
參與了建立工做,並無涉及請求的處理。
FrameworkServlet
在類中的 service() 、doGet()、doPost()、doPut()、doDelete()、doOptions()、doTrace() 這些方法中能夠看到都調用了一個共同的方法 processRequest() ,它是類在處理請求中最核心的方法。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; //獲取 LocaleContextHolder 中原來保存的 LocaleContext LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); //獲取當前請求的 LocaleContext LocaleContext localeContext = buildLocaleContext(request); //獲取 RequestContextHolder 中原來保存的 RequestAttributes RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); //獲取當前請求的 ServletRequestAttributes ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); //將當前請求的 LocaleContext 和 ServletRequestAttributes 設置到 LocaleContextHolder 和 RequestContextHolder initContextHolders(request, localeContext, requestAttributes); try { //實際處理請求的入口,這是一個模板方法,在 Dispatcher 類中才有具體實現 doService(request, response); }catch (ServletException ex) { failureCause = ex; throw ex; }catch (IOException ex) { failureCause = ex; throw ex; }catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); }finally { //將 previousLocaleContext,previousAttributes 恢復到 LocaleContextHolder 和 RequestContextHolder 中 resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } //刪除了日誌打印代碼 //發佈了一個 ServletRequestHandledEvent 類型的消息 publishRequestHandledEvent(request, response, startTime, failureCause); } }
DispatcherServlet
上一章中其實還沒把該類講清楚,在這個類中,裏面的智行處理的入口方法應該是 doService 方法,方法裏面調用了 doDispatch 進行具體的處理,在調用 doDispatch 方法以前 doService 作了一些事情:首先判斷是否是 include 請求,若是是則對 request 的 Attribute 作個快照備份,等 doDispatcher 處理完以後(若是不是異步調用且未完成)進行還原 ,在作完快照後又對 request 設置了一些屬性。
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)){ attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // Make framework objects available to handlers and view objects. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); try { //調用 doDispatch 方法 doDispatch(request, response); }finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
doDispatch() 方法:
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. 根據 request 找到 Handler mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request.根據 Handler 找到對應的 HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. //處理 GET 、 HEAD 請求的 LastModified String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //執行相應的 Interceptor 的 preHandle if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. HandlerAdapter 使用 Handler 處理請求 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //若是須要異步處理,直接返回 if (asyncManager.isConcurrentHandlingStarted()) { return; } //當 view 爲空時,根據 request 設置默認 view applyDefaultViewName(processedRequest, mv); //執行相應 Interceptor 的 postHandler mappedHandler.applyPostHandle(processedRequest, response, mv); }catch (Exception ex) { dispatchException = ex; }catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } //調用 processDispatchResult 方法處理上面處理以後的結果(包括處理異常,渲染頁面,發出完成通知觸發 Interceptor 的 afterCompletion) processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); }catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); }finally { //判斷是否執行異步請求 if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } }else { // Clean up any resources used by a multipart request. 刪除上傳請求的資源 if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
Handler,HandlerMapping,HandlerAdapter 三個區別:
Handler:處理器,對應 MVC 的 C層,也就是 Controller 層,具體表現形式有不少種,能夠是類,方法,它的類型是 Object,只要能夠處理實際請求就能夠是 Handler。
HandlerMapping:用來查找 Handler 的。
HandlerAdapter :Handler 適配器,
另外 View 和 ViewResolver 的原理與 Handler 和 HandlerMapping 的原理相似。
本章分析了 Spring MVC 的請求處理的過程。
注:本文首發於 zhisheng 的博客,可轉載但務必請註明原創地址爲:http://www.54tianzhisheng.cn/2017/07/14/Spring-MVC02/