帶你一步一步手寫一個簡單的Spring MVC

FrancisQ發表於2019-10-26

如果對 servlet 和 Spring MVC 的基本原理還不是很理解的同學可以先看我的另一篇文章帶你一步一步手撕Spring MVC原始碼加手繪流程圖。再回過來看這篇文章你就會豁然開朗。

本文主要涉及程式碼實現,很多要點會在程式碼註釋中說明,請仔細閱讀。

所有程式碼已經在https://github.com/FrancisQiang/spring-mvc-custom上託管,感興趣的同學可以自行 fork 。

手寫 MVC 之 FrameworkServlet

我在上一篇文章中提到過 FrameworkServlet 是 Spring MVC 最重要的類 DispatcherServlet直接父類 ,它的主要作用是:提供了載入某個對應的 web 應用程式環境的功能,還有將 GET、POST、DELETE、PUT等方法統一交給 DispatcherServlet 處理

總之就是將一些 doXxx 方法統一交給 DispatcherServlet 的某個方法處理,這個方法是什麼呢?其實就是大名鼎鼎的 doDispatch()

我們可以實現一個最簡單的 FrameworkServlet 類。

// 這裡必須繼承 HttpServlet 如果不懂的可以去看上一篇文章
public class FrameworkServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            // 使用doDispatch方法處理請求
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            // 使用doDispatch方法處理請求
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception{
        // 重點在這裡,主要由 DispatcherServlet 實現
        // 當然你這裡可以使用抽象方法並且將 FrameworkServlet 定義成抽象類
        // 在 Spring MVC 中的原始碼也是這麼做的
    }
    
}
複製程式碼

當然為了能夠繼承 HttpServlet 你需要在maven專案中的pom.xml進行導包,你也可以自己進行其他方式的導包。

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>
複製程式碼

也就是說,FrameworkServlet 僅僅提供一個模板方法(不瞭解設計模式的同學可以去看一下,這是一個框架常用但很簡單的設計模式),最終實現的還是在 DispatcherServlet 中的 doDispatch() 方法。

手寫 MVC 之 DispatcherServlet 原始版本

首先我們需要繼承 FrameworkServlet 類

public class DispatcherServlet extends FrameworkServlet{
    @Override
    public void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 處理請求
    }
}

複製程式碼

很簡單就是繼承然後再實現,但是我們似乎忘了一件事情,我們需要在 web.xml 中配置我們的 DispatcherServlet

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <!-- 配置我們的DispatcherServlet -->
  <servlet>
    <!-- 指定類 -->
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>com.lgq.servlet.DispatcherServlet</servlet-class>
    <!-- 配置初始化引數,這裡需要標註我們的spring mvc配置檔案路徑,後面我會講 -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>springmvc.xml</param-value>
    </init-param>
    <!-- 初始化策略 -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!-- DispatcherServlet核心 攔截所有請求 -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
複製程式碼

這個時候我們需要建立一個 MVC 的配置檔案 springmvc.xml (注意路徑很重要),其實就是一個spring配置檔案,我們需要它的主要原因就是 我們需要將我們後面寫的 處理器對映,處理器介面卡載入到 IOC 容器中去,並且我們在 Servlet 進行初始化時初始化容器 獲取相應的 bean

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
        <!-- 很簡單,現在什麼都沒實現 -->
</beans>
複製程式碼

為了使用 Spring 的 IOC 功能我們需要在 maven 的 pom.xml 中匯入相應的包。匯入 core 和 context 包

請記住:MVC 的功能是在 IOC 的基礎上的,所以在 spring 中 IOC 是核心功能。

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>5.2.0.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.0.RELEASE</version>
</dependency>
複製程式碼

手寫 MVC 之 HandlerMapping

為了你更好理解我首先將我上一篇部落格中的圖片貼上來。

帶你一步一步手寫一個簡單的Spring MVC

也就是說當使用者傳送一個請求的時候我們需要在 DispatcherServlet 中的 doDispatch()遍歷 HandlerMapping 的集合,然後從單個對映中再獲取到能夠處理請求的處理器並最終呼叫處理方法返回。拋開 Handler 不管,現在我們需要做的就是建立一個 HandlerMapping 類(這個類必定包含一個獲取處理器的方法和一個對映關係),然後在 DispatcherServlet 中定義一個 HandlerMapping集合

public interface HandlerMapping {
    // 獲取處理器
    // 對映關係需要子類自己去實現,有些對映關係就不是一個欄位 如SimpleHandlerMapping
    Object getHandler(HttpServletRequest request) throws Exception;
}
複製程式碼

改造一下 DispatcherServlet 的程式碼

public class DispatcherServlet extends FrameworkServlet{
    // 定義一個 HandlerMapping 集合
    private List<HandlerMapping> handlerMappings = null;

    @Override
    public void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
       // 處理請求
    }

}
複製程式碼

手寫 MVC 之 初始化

如果沒有讀過我上一篇文章的同學肯定會有疑問。這個 HandlerMapping 集合哪來的?

答案就在 Servlet 中,我們知道 Servlet 中是有初始化函式的,而且它的子類 GenericServlet 自己實現了一個 init() 無參方法,我們只需要實現這個初始化方法,那麼我們就能在 請求時或者web容器初始化時(取決於你在 web.xml 中的 load-on-startup 配置引數)。

下面我們就來實現一下 HandlerMapping集合 的初始化。

public class DispatcherServlet extends FrameworkServlet{

    private List<HandlerMapping> handlerMappings = null;
    
    // 初始化 IOC 容器
    private void initBeanFactory(ServletConfig config) {
        // 獲取web.xml中配置的contextConfigLocation 即spring-mvc檔案路徑
        String contextLocation = config.getInitParameter("contextConfigLocation");
        // 初始化容器
        BeanFactory.initBeanFactory(contextLocation);
    }

    // 初始化handlerMappings
    private void initHandlerMappings() {
        // 通過型別去獲取例項集合 你可以看下面BeanFactory的原始碼
        handlerMappings = BeanFactory.getBeansOfType(HandlerMapping.class);
    }
    
    // 在初始化servlet的時候進行 工廠的初始化 handlerMapping初始化
    @Override
    public void init(ServletConfig config) throws ServletException {
        initBeanFactory(config);
        initHandlerMappings();
    }

    private Object getHandler(HttpServletRequest request) throws Exception {
        if (handlerMappings != null && handlerMappings.size() > 0) {
            // 遍歷處理器對映集合並獲取相應的處理器
            for (HandlerMapping handlerMapping: handlerMappings) {
                Object handler = handlerMapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

    @Override
    public void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
       // 首先我們要遍歷 handlerMapping 集合並獲取相應的處理器
       // 獲取處理器
       Object handler = getHandler(request);
       // 處理請求。。。。
       // 如果沒有介面卡就是上面的圖的話 我們可以直接使用
       // handler.handleRequest() 這樣的方法直接處理請求
    }
}
複製程式碼

我們發現在進行 HandlerMapping集合 的初始化之前我還進行了 IOC 容器的初始化,目的就是將預先定義好的處理器對映等元件交給IOC容器管理,然後再需要的時候直接取就好,這裡我直接給出 BeanFactory 中的所有原始碼,其中我就是靠著 Spring 本身的 IOC 容器去實現的。

public class BeanFactory {

    // 這個就是 Spring 中很重要的 上下文類 其中包含了 Bean工廠 
    private static ClassPathXmlApplicationContext context;

    // 通過配置檔案路徑進行 IOC 容器的初始化
    public static void initBeanFactory(String contextConfigLocation) {
        context = new ClassPathXmlApplicationContext(contextConfigLocation);
    }
    // 通過型別獲取bean集合
    public static <T> List<T> getBeansOfType(Class<?> clazz) {
        ArrayList<T> result = new ArrayList<>();
        Map<String, ?> beansOfType = context.getBeansOfType(clazz);
        for (Object object : beansOfType.values()) {
            result.add((T) object);
        }
        return result;
    }
    // 通過型別獲取beanName集合
    public static List<String> getBeanNamesOfType(Class<Object> objectClass) {
        String[] beanNamesForType = context.getBeanNamesForType(objectClass);
        if (beanNamesForType == null) {
            return null;
        } else {
            return new ArrayList<>(Arrays.asList(beanNamesForType));
        }
    }
    // 通過beanName獲取bean型別
    public static Class<?> getType(String beanName) {
        return context.getType(beanName);
    }
    // 通過型別獲取例項bean
    public static Object getBean(Class<?> clazz) {
        return context.getBean(clazz);
    }

}
複製程式碼

當然,需要初始化的不僅僅是我們的 handlerMappings,我們還需要初始化一個 HandlerAdapter 集合,在上面的圖中我們是直接通過 handler 處理請求的,但是在 MVC 中是需要通過 HandlerAdapter 中間這一層的,具體原因我在上一篇文章中提到了,你可以去回顧一下,這裡主要涉及實現。

手寫 MVC 之 HandlerAdapter

首先我們需要明確的就是這是一個介面卡,如果研究過設計模式的同學可以瞭解到 介面卡模式需要通過繼承或者持有來實現對一個物件進行適配,而 Spring MVC 中也是一種介面卡的最佳實踐,我仿照了它的程式碼也實現了一個,你可以看一下。

public interface HandlerAdapter {

    // 處理請求返回檢視資訊
    // 你可以看見這裡傳入了一個 handler 這裡就是介面卡模式的一種實現
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;
    
    // 判斷該介面卡是否支援該處理器
    boolean support(Object handler);

}
複製程式碼

在這裡的 ModelAndView 其實就是最終處理之後會返回的 檢視模型,玩過 Spring MVC 的人應該很熟悉了吧。我是這麼定義 ModelAndView 的。

public class ModelAndView {
    // 裡面的model其實就是一個 map
    private Map<String,Object> model = new HashMap<>();
    
    public Map<String, Object> getModel() {
        return model;
    }
    // 新增屬性
    public void addAttribute(String attributeName, String attributeValue) {
        model.put(attributeName, attributeValue);
    }
}
複製程式碼

現在我們可以理一下思路了

這個思路很重要!!!

  • 首先我們會在Servlet 進行初始化的時候初始化我們的IOC容器並且獲取 HandlerMapping 和 HandlerAdapter 的集合
  • 使用者在傳送請求的時候會到達我們的 doDispatch 方法,我們在這個方法裡會先遍歷 HandlerMapping 集合獲取能夠處理該請求的處理器
  • 然後我們會再次遍歷 HandlerAdapter 集合,獲取能夠適配該處理器的處理器介面卡
  • 然後我們會呼叫介面卡的 handleRequest 並返回相應的 ModelAndView 檢視資訊

帶你一步一步手寫一個簡單的Spring MVC

手寫 MVC 之 完整初始化流程和完整處理流程

在實現完 HandlerAdapter 處理器和理清上面的處理流程思路之後我們就可以實現完整的DispatcherServlet了。

public class DispatcherServlet extends FrameworkServlet{
    // 兩個重要的元件
    private List<HandlerMapping> handlerMappings = null;
    private List<HandlerAdapter> handlerAdapters = null;
    
    // 初始化 IOC 容器
    private void initBeanFactory(ServletConfig config) {
        // 獲取web.xml中配置的contextConfigLocation 即spring-mvc檔案路徑
        String contextLocation = config.getInitParameter("contextConfigLocation");
        // 初始化容器
        BeanFactory.initBeanFactory(contextLocation);
    }

    // 初始化handlerMappings
    private void initHandlerMappings() {
        // 通過型別去獲取例項集合 你可以看下面BeanFactory的原始碼
        handlerMappings = BeanFactory.getBeansOfType(HandlerMapping.class);
    }
    // 初始化handlerAdapters
    private void initHandlerAdapters() {
        handlerAdapters = BeanFactory.getBeansOfType(HandlerAdapter.class);
    }
    
    // 在初始化servlet的時候進行 工廠的初始化 handlerMapping
    // 和HandlerAdapter的初始化
    @Override
    public void init(ServletConfig config) throws ServletException {
        initBeanFactory(config);
        initHandlerMappings();
        initHandlerAdapters();
    }
    // 獲取處理器
    private Object getHandler(HttpServletRequest request) throws Exception {
        if (handlerMappings != null && handlerMappings.size() > 0) {
            // 遍歷處理器對映集合並獲取相應的處理器
            for (HandlerMapping handlerMapping: handlerMappings) {
                Object handler = handlerMapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }
    // 通過處理器獲取相應的處理器介面卡
    private HandlerAdapter getHandlerAdapter(Object handler) {
        if (handlerAdapters != null && handlerAdapters.size() > 0) {
            // 遍歷處理器介面卡集合獲取能夠適配處理器的介面卡
            for (HandlerAdapter handlerAdapter: handlerAdapters) {
                if (handlerAdapter.support(handler)) {
                    return handlerAdapter;
                }
            }
        }
        return null;
    }

    @Override
    public void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
       // 首先我們要遍歷 handlerMapping 集合並獲取相應的處理器
       // 獲取處理器
       Object handler = getHandler(request);
       if (handler != null) {
            // 獲取處理器對應的處理器介面卡
            HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
            if (handlerAdapter != null) {
                // 呼叫介面卡的處理方法返回ModelAndView物件
                ModelAndView modelAndView = handlerAdapter.handleRequest(request, response, handler);
                // 這裡簡單實現了一下將ModelAndView物件中的model
                // 作為字串返回頁面
                if (modelAndView != null) {
                    Map<String, Object> model = modelAndView.getModel();
                    PrintWriter writer = response.getWriter();
                    for (String string: model.keySet()) {
                        writer.write(string);
                    }
                    writer.close();
                }

            }
        }
    }
}
複製程式碼

我們可以檢視 DispatcherServlet 的類結構幫助理解

帶你一步一步手寫一個簡單的Spring MVC

手寫 MVC 之 實現一個簡單的處理流程

寫到這裡我們會發現,我們到現在為止還沒有具體的 HandlerMapping,Handler,HandlerAdapter 的實現類,所以需要處理請求我們得構造一些簡單的類去實現。

SimpleHandler

我們首先實現一個 SimpleHandler

public interface SimpleHandler {
    // 很簡單的處理請求
    void handleRequest(HttpServletRequest request, HttpServletResponse response)throws Exception;
}
複製程式碼

我們還需要一個實現類,很簡單,你們一看就懂。

public class AddBookHandler implements SimpleHandler{
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception{
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("新增書本成功!");
        writer.close();
    }
}
public class DeleteBookHandler implements SimpleHandler{
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("刪除書本成功!");
        writer.close();
    }
}
複製程式碼

上面兩個處理器就是簡單實現了返回兩個不同的資訊比如 新增書本成功 和 刪除書本成功。而我們知道因為單一職責原則,我們的處理器只是用來處理請求的,而我們還需要一個排程的類,那就是HandlerMapping

SimpleHandlerMapping

public class SimpleHandlerMapping implements HandlerMapping {
    @Override
    public Object getHandler(HttpServletRequest request) throws Exception {
        // 獲取請求的uri
        String uri = request.getRequestURI();
        // 根據對映規則獲取對應的handler
        if ("/addBook".equals(uri)) {
            return new AddBookHandler();
        } else if ("/deleteBook".equals(uri)) {
            return new DeleteBookHandler();
        }
        return null;
    }
}
複製程式碼

跟它名字一樣,這是一個非常簡單的處理器對映,甚至連一個map欄位都沒有,處理對映是用過if else語句來處理的

當然,有了 處理器對映,處理器,還得要一個 處理器介面卡

SimpleHandlerAdapter

public class SimpleHandlerAdapter implements HandlerAdapter {
    // 處理請求並返回結果,這裡為null是因為在處理器處理請求的
    // 時候僅僅是寫入一些字串就返回了
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 強轉為 SimpleHandler然後呼叫處理器的處理方法
        ((SimpleHandler)handler).handleRequest(request, response);
        return null;
    }
    // 判斷是否是 SimpleHandler 然後返回
    @Override
    public boolean support(Object handler) {
        return handler instanceof SimpleHandler;
    }
}
複製程式碼

這樣我們就寫完了整個簡單處理流程,但是我們還少了幾個步驟,那就是在 springmvc.xml 配置檔案中配置相應的處理器,介面卡,處理器對映,這樣才能將這些類交給 IOC 容器管理,並且在我們 DispatcherServlet 初始化時進行自動裝配

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="simpleHandlerMapping" name="simpleHandlerMapping" class="com.lgq.handler.SimpleHandlerMapping"/>
    
    <bean id="simpleHandlerAdapter" name="simpleHandlerAdapter"
          class="com.lgq.adapter.SimpleHandlerAdapter"/>
</beans>
複製程式碼

這個時候我們就可以配置 Tomcat 進行試驗了,這裡我使用了 Tomcat 的 maven 外掛,只需要在 maven 的 pom.xml 中配置外掛然後修改啟動引數就行,這裡我不做過多介紹,不會的可以自行百度。

<plugin>
  <groupId>org.apache.tomcat.maven</groupId>
  <artifactId>tomcat7-maven-plugin</artifactId>
  <version>2.2</version>
  <configuration>
    <port>8080</port>
    <path>/</path>
  </configuration>
</plugin>
複製程式碼

這樣我們就能愉快地訪問測試了。

帶你一步一步手寫一個簡單的Spring MVC

手寫 MVC 之 註解形式的處理流程

在我們的日常應用開發中,我們都會接觸到 @Controller 和 @RequestMapping 這些註解,而它到底怎麼實現的呢?我在這裡稍微概括一下,想要知道具體細節可以閱讀我的上一篇文章

在初始化 Servlet 的時候 ,會先將帶有 @Controller 註解的類裝入 IOC 容器,然後會對這個類進行方法的判斷,判斷是否有 @RequestMapping 註解,如果有那麼就會將這個方法封裝成一個 HandlerMethod,注意這個 HandlerMethod 也是一個處理器,這個 HandlerMethod 中的會持有該註解標註的方法,最終會通過反射進行呼叫

既然知道了處理流程,那麼我們開始手寫一個吧!

自定義註解

首先我們要自己定義 @Controller@RequestMapping 註解。如果對自定義註解不熟悉的話可以回去複習一下哦。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    String value() default "";
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String value() default "";
}
複製程式碼

HandlerMethod

HandlerMethod 中持有 bean例項,bean型別和被@RequestMapping 標註的方法引用,這是反射必須有的三個要素。這裡我直接使用了 Lombok 註解來簡化 getter setter方法。

@Data
@AllArgsConstructor
public class HandlerMethod {
    private Object bean;
    private Class<?> beanType;
    private Method method;
}
複製程式碼
<!-- lombok配置 -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.8</version>
  <scope>provided</scope>
</dependency>
複製程式碼

RequestMappingHanlderMapping

這是一個非常重要的類,因為涉及到了 從IOC容器中獲取相應標註了Controller註解的類

public class RequestMappingHandlerMapping implements HandlerMapping {
    // 存放了 @RequestMapping中的url和
    // 被@RequestMapping標註了的方法的封裝類 HandlerMethod 例項
    private Map<String, HandlerMethod> urlMap = new HashMap<>();
    // 該初始化方法很重要,這個需要在 springmvc.xml 配置檔案
    // 中配置 init-method 引數
    public void init() {
        // 首先從IOC容器中獲取所有物件的beanNames
        List<String> beanNames = BeanFactory.getBeanNamesOfType(Object.class);
        // 遍歷beanNames通過beanName獲取相應的clazz
        if (beanNames != null) {
            for (String beanName: beanNames) {
                Class<?> clazz = BeanFactory.getType(beanName);
                // 判斷clazz物件是否是處理器,如果是則處理
                if (clazz != null && isHandler(clazz)) {
                    // 獲取該類所有方法
                    Method[] methods = clazz.getDeclaredMethods();
                    // 遍歷所有方法並判斷存在註解RequestMapping的方法
                    for (Method method : methods) {
                        if (method.isAnnotationPresent(RequestMapping.class)) {
                            // 獲取該方法的註解並獲取該註解的名稱value 後面作為map的key
                            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                            String value = requestMapping.value();
                            // 通過beanName, clazz, method創將handlerMethod
                            HandlerMethod handlerMethod = new HandlerMethod(beanName, clazz, method);
                            // 存入對映
                            urlMap.put(value, handlerMethod);
                        }
                    }
                }
            }
        }

    }
    
    // 判斷類上的註解是否存在 Controller 或者 RequestMapping
    private boolean isHandler(Class<?> clazz) {
        return clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(RequestMapping.class);
    }

    @Override
    public Object getHandler(HttpServletRequest request) throws Exception {
        // 通過初始化方法呼叫的init生成的urlMap中尋找對應的處理器
        return urlMap.get(request.getRequestURI());
    }
}

複製程式碼

這裡面還需要配置 springmvc.xml 的配置檔案,這裡先講 RequestMappingHandlerAdapter 這個類,等會配置檔案一起給出。

RequestMappingHandlerAdapter

public class RequestMappingHandlerAdapter implements HandlerAdapter{
    // 處理相應的請求並返回 ModelAndView
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ModelAndView modelAndView = null;
        HandlerMethod handlerMethod = (HandlerMethod)handler;
        Method method = handlerMethod.getMethod();
        if (method != null) {
            // 通過反射呼叫方法,並將返回結果封裝成ModelAndView物件
            modelAndView = (ModelAndView)method.invoke(BeanFactory.getBean(((HandlerMethod) handler).getBeanType()));
        }
        return modelAndView;
    }
    // 是否是HandlerMethod
    @Override
    public boolean support(Object handler) {
        return handler instanceof HandlerMethod;
    }
}
複製程式碼

springmvc.xml 配置檔案

寫好了這些類我們還需要進行配置,因為把它們裝入 IOC 是一件非常非常重要的事情

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="simpleHandlerMapping" name="simpleHandlerMapping" class="com.lgq.handler.SimpleHandlerMapping"/>
    <!-- 配置RequestMappingHandlerMapping -->
    <!-- 這裡還配置了 init-method -->
    <!-- 你還需要注意的是 這裡使用了懶載入,因為在初始化過程中我們 -->
    <!-- 需要使用 IOC 容器,所以需要等到IOC初始化完成再初始化這個bean -->
    <bean id="requestMappingHandlerMapping" name="requestMappingHandlerMapping"
          class="com.lgq.handler.RequestMappingHandlerMapping" lazy-init="true" init-method="init"/>
    <!-- 配置RequestMappingHandlerAdapter -->
    <bean id="requestMappingHandlerAdapter" name="RequestMappingHandlerAdapter"
          class="com.lgq.adapter.RequestMappingHandlerAdapter"/>

    <bean id="simpleHandlerAdapter" name="simpleHandlerAdapter"
          class="com.lgq.adapter.SimpleHandlerAdapter"/>
    <!-- 讓IOC掃描我們後面需要寫的測試Controller -->
    <context:component-scan base-package="com.lgq.handler"/>
    
</beans>
複製程式碼

寫一個測試Controller並實驗

// 這個是spring本身的註解
// 這裡主要就是將這個類載入到容器中
// 因為我們本身寫的註解會跟spring本身
// 的@Controller有衝突,這裡所以使用@Component
// 對應著上面配置檔案中的
// <context:component-scan base-package="com.lgq.handler"/>
@Component
// 這是我們自己寫的註解哦
@Controller
public class HelloWorldHandler {
    // 這是我們寫的註解哦
    @RequestMapping(value = "/helloWorld")
    public ModelAndView helloWorld() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addAttribute("hello!!!", "World!!!");
        return modelAndView;
    }
}
複製程式碼

我們來訪問測試一下

帶你一步一步手寫一個簡單的Spring MVC

成功!!!

太棒了,我們自己實現了一個簡單的 Spring MVC。

這是我們整個專案的目錄

帶你一步一步手寫一個簡單的Spring MVC

總結

在這個專案中我們沒有實現了 訊息轉換器 這個功能,其實我們再進行 write 的時候就已經悄悄的實現了一下訊息轉換(因為我們再返回ModelAndView之前就response給瀏覽器了)。當然,在我的上一篇文章中有對 HttpMessageConvert 這個類進行原始碼解析,感興趣的同學可以去看一下。

其實整個Spring MVC 的基礎流程並不複雜,無非就是一個分發器去處理請求,其中進行了一些職責的分離,比如說 Handler ,HandlerAdapter,HandlerMapping等等,去掉這些就是一個處理請求的過程。希望你們在閱讀完這篇文章能對 Spring MVC 整體流程有個清晰的概念,這裡我再將整個流程圖掛上來供你理解。

帶你一步一步手寫一個簡單的Spring MVC

本專案所有程式碼已經在https://github.com/FrancisQiang/spring-mvc-custom上託管,感興趣的同學可以自行 fork 。

謝謝閱讀!

相關文章