转载

带你一步一步手写一个简单的Spring MVC

如果对 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 。

谢谢阅读!

原文  https://juejin.im/post/5db39e5e5188256ecf348e12
正文到此结束
Loading...