歡迎光臨
每天分享高質量文章

看透 Spring MVC 原始碼分析與實踐 —— 俯視 Spring MVC

友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群討論技術和原始碼。
友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群討論技術和原始碼。
友情提示:歡迎關註公眾號【芋道原始碼】。?關註後,拉你進【原始碼圈】微信群討論技術和原始碼。

Spring MVC 之初體驗

環境搭建

在 IDEA 中新建一個 web 專案,用 Maven 管理專案的話,在 pom.xml 中加入 Spring MVC 和 Servlet 依賴即可。

  1.    org.springframework

  •    spring-webmvc

  •    4.3.9.RELEASE

  •    javax.servlet

  •    javax.servlet-api

  •    3.1.0

  •    provided

  • Spring MVC 簡單配置

    • 在 web.xml 中配置 Servlet

    • 建立 Spring MVC 的 xml 配置檔案

    • 建立 Controller 和 View

    1、web.xml

    1.    spring

  •    org.springframework.web.servlet.DispatcherServlet

  •    

  •    1

  •    spring

  •    *.do

  •  

  •     org.springframework.web.context.ContextLoaderListener

  •  

  •    contextConfigLocation

  •    classpath:config/applicationContext.xml

  • 2、spring-servlet.xml

    1. xml version="1.0" encoding="UTF-8"?>

    2.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"

    3.        xmlns:context="http://www.springframework.org/schema/context"

    4.   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

    5.       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd

    6.       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

    7.       http://www.springframework.org/schema/context href="http://www.springframework.org/schema/context/spring-context-3.0.xsd">http://www.springframework.org/schema/context/spring-context-3.0.xsd

    ">

  •    

  •     />

  •    

  •     base-package="controller">

  •    

  •     class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

  •   

  •    

  •     class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/jsp/" p:suffix=".jsp" />

  • 3、Controller

    1. package controller;

    2. import javax.servlet.http.HttpServletRequest;

    3. import org.springframework.stereotype.Controller;

    4. import org.springframework.web.bind.annotation.RequestMapping;

    5. import org.springframework.web.bind.annotation.RequestParam;

    6. import entity.User;

    7. @Controller  //類似Struts的Action

    8. public class TestController {

    9.    @RequestMapping("/test/login.do")  // 請求url地址對映,類似Struts的action-mapping

    10.    public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) {

    11.        // @RequestParam是指請求url地址對映中必須含有的引數(除非屬性 required=false, 預設為 true)

    12.        // @RequestParam可簡寫為:@RequestParam("username")

    13.        if (!"admin".equals(username) || !"admin".equals(password)) {

    14.            return "loginError"; // 跳轉頁面路徑(預設為轉發),該路徑不需要包含spring-servlet配置檔案中配置的字首和字尾

    15.        }

    16.        return "loginSuccess";

    17.    }

    18.    @RequestMapping("/test/login2.do")

    19.    public ModelAndView testLogin2(String username, String password, int age){

    20.        // request和response不必非要出現在方法中,如果用不上的話可以去掉

    21.        // 引數的名稱是與頁面控制元件的name相匹配,引數型別會自動被轉換

    22.        if (!"admin".equals(username) || !"admin".equals(password) || age < 5) {

    23.            return new ModelAndView("loginError"); // 手動實體化ModelAndView完成跳轉頁面(轉發),效果等同於上面的方法傳回字串

    24.        }

    25.        return new ModelAndView(new RedirectView("../index.jsp"));  // 採用重定向方式跳轉頁面

    26.        // 重定向還有一種簡單寫法

    27.        // return new ModelAndView("redirect:../index.jsp");

    28.    }

    29.    @RequestMapping("/test/login3.do")

    30.    public ModelAndView testLogin3(User user) {

    31.        // 同樣支援引數為表單物件,類似於Struts的ActionForm,User不需要任何配置,直接寫即可

    32.        String username = user.getUsername();

    33.        String password = user.getPassword();

    34.        int age = user.getAge();

    35.        if (!"admin".equals(username) || !"admin".equals(password) || age < 5) {

    36.            return new ModelAndView("loginError");

    37.        }

    38.        return new ModelAndView("loginSuccess");

    39.    }

    40.    @Resource(name = "loginService")  // 獲取applicationContext.xml中bean的id為loginService的,並註入

    41.    private LoginService loginService;  //等價於spring傳統註入方式寫get和set方法,這樣的好處是簡潔工整,省去了不必要得程式碼

    42.    @RequestMapping("/test/login4.do")

    43.    public String testLogin4(User user) {

    44.        if (loginService.login(user) == false) {

    45.            return "loginError";

    46.        }

    47.        return "loginSuccess";

    48.    }

    49. }

    @RequestMapping 可以寫在方法上,也可以寫在類上,上面程式碼方法上的 RequestMapping 都含有 /test , 那麼我們就可以將其抽出直接寫在類上,那麼方法裡面就不需要寫 /test 了。

    如下即可:

    1. @Controller

    2. @RequestMapping("/test")

    3. public class TestController {

    4.    @RequestMapping("/login.do")  // 請求url地址對映,類似Struts的action-mapping

    5.    public String testLogin(@RequestParam(value="username")String username, String password, HttpServletRequest request) {

    6.        // @RequestParam是指請求url地址對映中必須含有的引數(除非屬性 required=false, 預設為 true)

    7.        // @RequestParam可簡寫為:@RequestParam("username")

    8.        if (!"admin".equals(username) || !"admin".equals(password)) {

    9.            return "loginError"; // 跳轉頁面路徑(預設為轉發),該路徑不需要包含spring-servlet配置檔案中配置的字首和字尾

    10.        }

    11.        return "loginSuccess";

    12.    }

    13.    //省略其他的

    14. }

    上面的程式碼方法的引數中可以看到有一個 @RequestParam 註解,其實還有 @PathVariable 。這兩個的區別是啥呢?

    • @PathVariable 標記在方法的引數上,利用它標記的引數可以利用請求路徑傳值。

    • @RequestParam是指請求url地址對映中必須含有的引數(除非屬性 required=false, 預設為 true)

    看如下例子:

    1. @RequestMapping("/user/{userId}")  // 請求url地址對映

    2. public String userinfo(Model model, @PathVariable("userId") int userId,  HttpSession session) {

    3.         System.out.println("進入  userinfo  頁面");

    4.        //判斷是否有使用者登入

    5.        User user1 = (User) session.getAttribute("user");

    6.        if (user1 == null) {

    7.            return "login";

    8.        }

    9.        User user = userService.selectUserById(userId);

    10.        model.addAttribute("user", user);

    11.        return "userinfo";

    12.    }

    上面例子中如果瀏覽器請求的是 /user/1 的時候,就表示此時的使用者 id 為 1,此時就會先從 session 中查詢是否有 “user” 屬性,如果有的話,就代表使用者此時處於登入的狀態,如果沒有的話,就會讓使用者傳回到登入頁面,這種機制在各種網站經常會使用的,然後根據這個 id = 1 ,去查詢使用者的資訊,然後把查詢的 “user” 放在 model 中,然後傳回用戶詳情頁面,最後在頁面中用 $!{user.name} 獲取使用者的名字,同樣的方式可以獲取使用者的其他資訊,把所有的使用者詳情資訊展示出來。

    建立 Spring MVC 之器

    Spring MVC 核心 Servlet 架構圖如下:

    Java 中常用的 Servlet 我在另外一篇文章寫的很清楚了,有興趣的請看:透過原始碼詳解 Servlet ,這裡我就不再解釋了。

    這裡主要講 Spring 中的 HttpServletBean、FrameworkServlet、DispatcherServlet 這三個類的建立過程。

    透過上面的圖,可以看到這三個類直接實現三個介面:EnvironmentCapable、EnvironmentAware、ApplicationContextAware。下麵我們直接看下這三個介面的內部是怎樣寫的。

    EnvironmentCapable.java

    1. public interface EnvironmentCapable {

    2.    //傳回元件的環境,可能傳回 null 或者預設環境

    3.    @Nullable

    4.    Environment getEnvironment();

    5. }

    EnvironmentAware.java

    1. public interface EnvironmentAware extends Aware {

    2.    //設定元件的執行環境

    3.    void setEnvironment(Environment environment);

    4. }

    ApplicationContextAware.java

    1. public interface ApplicationContextAware extends Aware {

    2.    //設定執行物件的應用背景關係

    3.    //當類實現這個介面後,這個類可以獲取ApplicationContext中所有的bean,也就是說這個類可以直接獲取Spring配置檔案中所有有取用到的bean物件

    4.    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

    5. }

    怎麼使用這個這個介面呢?

    參考文章:org.springframework.context.ApplicationContextAware使用理解

    HttpServletBean

    這裡就直接看其中最重要的 init() 方法的程式碼了:

    1. /**

    2. * 將配置引數對映到此servlet的bean屬性,並呼叫子類初始化。

    3. *  如果 bean 配置不合法(或者需要的引數丟失)或者子類初始化發生錯誤,那麼就會丟擲 ServletException 異常

    4. */

    5. @Override

    6. public final void init() throws ServletException {

    7.   //日誌程式碼刪除了

    8.   // 從init引數設定bean屬性。

    9.   //獲得web.xml中的contextConfigLocation配置屬性,就是spring MVC的配置檔案

    10.   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

    11.   if (!pvs.isEmpty()) {

    12.      try {

    13.         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

    14.         //獲取伺服器的各種資訊

    15.         ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());

    16.         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));

    17.         //模板方法,可以在子類中呼叫,做一些初始化工作,bw代表DispatcherServelt

    18.         initBeanWrapper(bw);

    19.         //將配置的初始化值設定到DispatcherServlet中

    20.         bw.setPropertyValues(pvs, true);

    21.      }

    22.      catch (BeansException ex) {

    23.         //日誌程式碼

    24.         throw ex;

    25.      }

    26.   }

    27.   // Let subclasses do whatever initialization they like.

    28.   //模板方法,子類初始化的入口方法

    29.   initServletBean();

    30.   //日誌程式碼刪除了

    31. }

    FrameworkServlet

    其中重要方法如下:裡面也就兩句關鍵程式碼,日誌程式碼我直接刪掉了

    1. protected final void initServletBean() throws ServletException {

    2.        //日誌程式碼刪除了

    3.        long startTime = System.currentTimeMillis();

    4.        //就是 try 陳述句裡面有兩句關鍵程式碼

    5.        try {

    6.            //初始化 webApplicationContext

    7.            this.webApplicationContext = initWebApplicationContext();

    8.            //模板方法,

    9.            initFrameworkServlet();

    10.        }

    11.        catch (ServletException ex) {

    12.            this.logger.error("Context initialization failed", ex);

    13.            throw ex;

    14.        }

    15.        catch (RuntimeException ex) {

    16.            this.logger.error("Context initialization failed", ex);

    17.            throw ex;

    18.        }

    19.        //日誌程式碼刪除了

    20.    }

    再來看看上面程式碼中呼叫的 initWebApplicationContext() 方法

    1. protected WebApplicationContext initWebApplicationContext() {

    2.        //獲取 rootContext

    3.        WebApplicationContext rootContext =

    4.                WebApplicationContextUtils.getWebApplicationContext(getServletContext());

    5.        WebApplicationContext wac = null;

    6.        if (this.webApplicationContext != null) {

    7.            // 背景關係實體在構造時註入 - >使用它

    8.            wac = this.webApplicationContext;

    9.            if (wac instanceof ConfigurableWebApplicationContext) {

    10.                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;

    11.                if (!cwac.isActive()) {

    12.                // 如果背景關係尚未掃清 -> 提供諸如設定父背景關係,設定應用程式背景關係ID等服務

    13.                    if (cwac.getParent() == null) {

    14.            // 背景關係實體被註入沒有顯式的父類 -> 將根應用程式背景關係(如果有的話可能為null)設定為父級

    15.                        cwac.setParent(rootContext);

    16.                    }

    17.                    configureAndRefreshWebApplicationContext(cwac);

    18.                }

    19.            }

    20.        }

    21.        if (wac == null) {

    22.    // 當 WebApplicationContext 已經存在 ServletContext 中時,透過配置在 servlet 中的 ContextAttribute 引數獲取

    23.            wac = findWebApplicationContext();

    24.        }

    25.        if (wac == null) {

    26.            // 如果 WebApplicationContext 還沒有建立,則建立一個

    27.            wac = createWebApplicationContext(rootContext);

    28.        }

    29.        if (!this.refreshEventReceived) {

    30.            // 當 ContextRefreshedEvent 事件沒有觸發時呼叫此方法,模板方法,可以在子類重寫

    31.            onRefresh(wac);

    32.        }

    33.        if (this.publishContext) {

    34.            // 將 ApplicationContext 儲存到 ServletContext 中去

    35.            String attrName = getServletContextAttributeName();

    36.            getServletContext().setAttribute(attrName, wac);

    37.            if (this.logger.isDebugEnabled()) {

    38.                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +

    39.                        "' as ServletContext attribute with name [" + attrName + "]");

    40.            }

    41.        }

    42.        return wac;

    43.    }

    initWebApplicationContext 方法做了三件事:

    • 獲取 Spring 的根容器 rootContext

    • 設定 webApplicationContext 並根據情況呼叫 onRefresh 方法

    • 將 webApplicationContext 設定到 ServletContext 中

    這裡在講講上面程式碼中的 wac == null 的幾種情況:

    1)、當 WebApplicationContext 已經存在 ServletContext 中時,透過配置在 servlet 中的 ContextAttribute 引數獲取,呼叫的是 findWebApplicationContext() 方法

    1. protected WebApplicationContext findWebApplicationContext() {

    2.        String attrName = getContextAttribute();

    3.        if (attrName == null) {

    4.            return null;

    5.        }

    6.        WebApplicationContext wac =

    7.                WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);

    8.        if (wac == null) {

    9.            throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");

    10.        }

    11.        return wac;

    12.    }

    2)、如果 WebApplicationContext 還沒有建立,呼叫的是 createWebApplicationContext 方法

    1. protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {

    2.        //獲取建立型別

    3.        Class> contextClass = getContextClass();

    4.        //刪除了列印日誌程式碼

    5.        //檢查建立型別

    6.        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {

    7.            throw new ApplicationContextException(

    8.                    "Fatal initialization error in servlet with name '" + getServletName() +

    9.                    "': custom WebApplicationContext class [" + contextClass.getName() +

    10.                    "] is not of type ConfigurableWebApplicationContext");

    11.        }

    12.        //具體建立

    13.        ConfigurableWebApplicationContext wac =

    14.                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    15.        wac.setEnvironment(getEnvironment());

    16.        wac.setParent(parent);

    17.  //並設定的 contextConfigLocation 引數傳給 wac,預設是 WEB-INFO/[ServletName]-Servlet.xml

    18.        wac.setConfigLocation(getContextConfigLocation());

    19.        //呼叫的是下麵的方法

    20.        configureAndRefreshWebApplicationContext(wac);

    21.        return wac;

    22.    }

    23. protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {

    24.        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {

    25.            // The application context id is still set to its original default value

    26.            // -> assign a more useful id based on available information

    27.            if (this.contextId != null) {

    28.                wac.setId(this.contextId);

    29.            }

    30.            else {

    31.                // Generate default id...

    32.                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +

    33.                        ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());

    34.            }

    35.        }

    36.        wac.setServletContext(getServletContext());

    37.        wac.setServletConfig(getServletConfig());

    38.        wac.setNamespace(getNamespace());

    39.        wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    40.        // The wac environment's #initPropertySources will be called in any case when the context

    41.        // is refreshed; do it eagerly here to ensure servlet property sources are in place for

    42.        // use in any post-processing or initialization that occurs below prior to #refresh

    43.        ConfigurableEnvironment env = wac.getEnvironment();

    44.        if (env instanceof ConfigurableWebEnvironment) {

    45.            ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());

    46.        }

    47.        postProcessWebApplicationContext(wac);

    48.        applyInitializers(wac);

    49.        wac.refresh();

    50.    }

    裡面還有 doXXX() 方法,大家感興趣的可以去看看。

    DispatcherServlet

    DispatcherServlet 繼承自 FrameworkServlet,onRefresh 方法是 DispatcherServlet 的入口方法,在 initStrategies 方法中呼叫了 9 個初始化的方法。

    這裡分析其中一個初始化方法:initLocaleResolver() 方法

    1. private void initLocaleResolver(ApplicationContext context) {

    2.        try {

    3.            //在 context 中獲取

    4.            this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);

    5.            //刪除了列印日誌的程式碼

    6.        }

    7.        catch (NoSuchBeanDefinitionException ex) {

    8.            //使用預設的策略

    9.            this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);

    10.            //刪除了列印日誌的程式碼

    11.        }

    12.    }

    檢視預設策略程式碼:

    1. protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {

    2.        //呼叫 getDefaultStrategies 方法

    3.        List<T> strategies = getDefaultStrategies(context, strategyInterface);

    4.        if (strategies.size() != 1) {

    5.            throw new BeanInitializationException(

    6.                    "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");

    7.        }

    8.        return strategies.get(0);

    9.    }

    10.    /**

    11.     * Create a List of default strategy objects for the given strategy interface.

    12.     *

      The default implementation uses the "DispatcherServlet.properties" file (in the same

    13.     * package as the DispatcherServlet class) to determine the class names. It instantiates

    14.     * the strategy objects through the context's BeanFactory.

    15.     */

    16.    @SuppressWarnings("unchecked")

    17.    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {

    18.        String key = strategyInterface.getName();

    19.        //根據策略介面的名字從 defaultStrategies 獲取所需策略的型別

    20.        String value = defaultStrategies.getProperty(key);

    21.        if (value != null) {

    22.            //如果有多個預設值的話,就以逗號分隔為陣列

    23.            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);

    24.            List<T> strategies = new ArrayList<>(classNames.length);

    25.            //按獲取到的型別初始化策略

    26.            for (String className : classNames) {

    27.                try {

    28.                    Class> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());

    29.                    Object strategy = createDefaultStrategy(context, clazz);

    30.                    strategies.add((T) strategy);

    31.                }

    32.                catch (ClassNotFoundException ex) {

    33.                    throw new BeanInitializationException(

    34.                            "Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex);

    35.                }

    36.                catch (LinkageError err) {

    37.                    throw new BeanInitializationException(

    38.                            "Error loading DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]: problem with class file or dependent class", err);

    39.                }

    40.            }

    41.            return strategies;

    42.        }

    43.        else {

    44.            return new LinkedList<>();

    45.        }

    46.    }

    其他幾個方法大概也類似,我就不再寫了。

    小結

    主要講了 Spring MVC 自身建立過程,分析了 Spring MVC 中 Servlet 的三個層次:HttpServletBean、FrameworkServlet 和 DispatcherServlet。HttpServletBean 繼承自 Java 的 HttpServlet,其作用是將配置的引數設定到相應的屬性上;FrameworkServlet 初始化了 WebApplicationContext;DispatcherServlet 初始化了自身的 9 個元件。

    Spring MVC 之用

    分析 Spring MVC 是怎麼處理請求的。首先分析 HttpServletBean、FrameworkServlet 和 DispatcherServlet 這三個 Servlet 的處理過程,最後分析 doDispatcher 的結構。

    HttpServletBean

    參與了建立工作,並沒有涉及請求的處理。

    FrameworkServlet

    在類中的 service() 、doGet()、doPost()、doPut()、doDelete()、doOptions()、doTrace() 這些方法中可以看到都呼叫了一個共同的方法 processRequest() ,它是類在處理請求中最核心的方法。

    1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response)

    2.            throws ServletException, IOException {

    3.        long startTime = System.currentTimeMillis();

    4.        Throwable failureCause = null;

    5.        //獲取 LocaleContextHolder 中原來儲存的 LocaleContext

    6.        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();

    7.        //獲取當前請求的 LocaleContext

    8.        LocaleContext localeContext = buildLocaleContext(request);

    9.        //獲取 RequestContextHolder 中原來儲存的 RequestAttributes

    10.        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();

    11.        //獲取當前請求的 ServletRequestAttributes

    12.        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    13.        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    14.        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    15. //將當前請求的 LocaleContext 和 ServletRequestAttributes 設定到 LocaleContextHolder 和 RequestContextHolder

    16.        initContextHolders(request, localeContext, requestAttributes);

    17.        try {

    18.            //實際處理請求的入口,這是一個模板方法,在 Dispatcher 類中才有具體實現

    19.            doService(request, response);

    20.        }catch (ServletException ex) {

    21.            failureCause = ex;

    22.            throw ex;

    23.        }catch (IOException ex) {

    24.            failureCause = ex;

    25.            throw ex;

    26.        }catch (Throwable ex) {

    27.            failureCause = ex;

    28.            throw new NestedServletException("Request processing failed", ex);

    29.        }finally {

    30.            //將 previousLocaleContext,previousAttributes 恢復到 LocaleContextHolder 和 RequestContextHolder 中

    31.            resetContextHolders(request, previousLocaleContext, previousAttributes);

    32.            if (requestAttributes != null) {

    33.                requestAttributes.requestCompleted();

    34.            }

    35.            //刪除了日誌列印程式碼

    36.            //釋出了一個 ServletRequestHandledEvent 型別的訊息

    37.            publishRequestHandledEvent(request, response, startTime, failureCause);

    38.        }

    39.    }

    DispatcherServlet

    上一章中其實還沒把該類講清楚,在這個類中,裡面的智行處理的入口方法應該是 doService 方法,方法裡面呼叫了 doDispatch 進行具體的處理,在呼叫 doDispatch 方法之前 doService 做了一些事情:首先判斷是不是 include 請求,如果是則對 request 的 Attribute 做個快照備份,等 doDispatcher 處理完之後(如果不是非同步呼叫且未完成)進行還原 ,在做完快照後又對 request 設定了一些屬性。

    1. protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {

    2.        // Keep a snapshot of the request attributes in case of an include,

    3.        // to be able to restore the original attributes after the include.

    4.        Map<String, Object> attributesSnapshot = null;

    5.        if (WebUtils.isIncludeRequest(request)) {

    6.            attributesSnapshot = new HashMap<>();

    7.            Enumeration> attrNames = request.getAttributeNames();

    8.            while (attrNames.hasMoreElements()) {

    9.                String attrName = (String) attrNames.nextElement();

    10.                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)){

    11.                    attributesSnapshot.put(attrName, request.getAttribute(attrName));

    12.                }

    13.            }

    14.        }

    15.        // Make framework objects available to handlers and view objects.

    16.        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());

    17.        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);

    18.        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);

    19.        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    20.        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);

    21.        if (inputFlashMap != null) {

    22.            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));

    23.        }

    24.        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());

    25.        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

    26.        try {

    27.            //呼叫 doDispatch 方法

    28.            doDispatch(request, response);

    29.        }finally {

    30.            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {

    31.                // Restore the original attribute snapshot, in case of an include.

    32.                if (attributesSnapshot != null) {

    33.                    restoreAttributesAfterInclude(request, attributesSnapshot);

    34.                }

    35.            }

    36.        }

    37.    }

    doDispatch() 方法:

    1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

    2.        HttpServletRequest processedRequest = request;

    3.        HandlerExecutionChain mappedHandler = null;

    4.        boolean multipartRequestParsed = false;

    5.        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    6.        try {

    7.            ModelAndView mv = null;

    8.            Exception dispatchException = null;

    9.            try {

    10.                //檢查是不是上傳請求

    11.                processedRequest = checkMultipart(request);

    12.                multipartRequestParsed = (processedRequest != request);

    13.                // Determine handler for the current request.  根據 request 找到 Handler

    14.                mappedHandler = getHandler(processedRequest);

    15.                if (mappedHandler == null || mappedHandler.getHandler() == null) {

    16.                    noHandlerFound(processedRequest, response);

    17.                    return;

    18.                }

    19.    // Determine handler adapter for the current request.根據 Handler 找到對應的 HandlerAdapter

    20.                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    21.                // Process last-modified essay-header, if supported by the handler.

    22.                //處理 GET 、 HEAD 請求的 LastModified

    23.                String method = request.getMethod();

    24.                boolean isGet = "GET".equals(method);

    25.                if (isGet || "HEAD".equals(method)) {

    26.                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());

    27.                    if (logger.isDebugEnabled()) {

    28.                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);

    29.                    }

    30.                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {

    31.                        return;

    32.                    }

    33.                }

    34.                //執行相應的 Interceptor 的 preHandle

    35.                if (!mappedHandler.applyPreHandle(processedRequest, response)) {

    36.                    return;

    37.                }

    38.                // Actually invoke the handler. HandlerAdapter 使用 Handler 處理請求

    39.                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    40.                //如果需要非同步處理,直接傳回

    41.                if (asyncManager.isConcurrentHandlingStarted()) {

    42.                    return;

    43.                }

    44.                //當 view 為空時,根據 request 設定預設 view

    45.                applyDefaultViewName(processedRequest, mv);

    46.                //執行相應 Interceptor 的 postHandler

    47.                mappedHandler.applyPostHandle(processedRequest, response, mv);

    48.            }catch (Exception ex) {

    49.                dispatchException = ex;

    50.            }catch (Throwable err) {

    51.                // As of 4.3, we're processing Errors thrown from handler methods as well,

    52.                // making them available for @ExceptionHandler methods and other scenarios.

    53.                dispatchException = new NestedServletException("Handler dispatch failed", err);

    54.            }

    55.            //呼叫 processDispatchResult 方法處理上面處理之後的結果(包括處理異常,渲染頁面,發出完成通知觸發 Interceptor 的 afterCompletion)

    56.            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

    57.        }catch (Exception ex) {

    58.            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);

    59.        }catch (Throwable err) {

    60.            triggerAfterCompletion(processedRequest, response, mappedHandler,

    61.                    new NestedServletException("Handler processing failed", err));

    62.        }finally {

    63.          //判斷是否執行非同步請求

    64.            if (asyncManager.isConcurrentHandlingStarted()) {

    65.                // Instead of postHandle and afterCompletion

    66.                if (mappedHandler != null) {

    67.                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);

    68.                }

    69.            }else {

    70.                // Clean up any resources used by a multipart request. 刪除上傳請求的資源

    71.                if (multipartRequestParsed) {

    72.                    cleanupMultipart(processedRequest);

    73.                }

    74.            }

    75.        }

    76.    }

    Handler,HandlerMapping,HandlerAdapter 三個區別:

    • Handler:處理器,對應 MVC 的 C層,也就是 Controller 層,具體表現形式有很多種,可以是類,方法,它的型別是 Object,只要可以處理實際請求就可以是 Handler。

    • HandlerMapping:用來查詢 Handler 的。

    • HandlerAdapter :Handler 配接器,

    另外 View 和 ViewResolver 的原理與 Handler 和 HandlerMapping 的原理類似。

    小結

    本章分析了 Spring MVC 的請求處理的過程。

    相關文章

        看透 Spring MVC 原始碼分析與實踐 —— 網站基礎知識

    贊(0)

    分享創造快樂

    © 2024 知識星球   網站地圖