源码分析系列教程(05) - 手写SpringMVC

简介: 源码分析系列教程(05) - 手写SpringMVC

代码已上传到GitHub,有兴趣的同学可以下载来看看:https://github.com/ylw-github/Java-CodeAnalysis-Demo

本文主要讲解如何手写SpringMVC,SpringMVC是基于Servlet来写的,所以本文会先回顾一下SpringMVC的基本流程和Servlet的基本知识,最后在手写SpringMVC(基于XML技术+反射+注解+Servlet)来实现。

1. Spring MVC运行流程

流程分析:

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器。
  3. 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
  5. 执行处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
  9. ViewReslover解析后返回具体View
  10. DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
  11. DispatcherServlet响应用户。

2. Servlet

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。

Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势:

  • 性能明显更好。
  • Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
  • Servlet 是独立于平台的,因为它们是用 Java 编写的。
  • 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
  • Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。

2.1 Servlet的生命周期

生命周期:Servlet 加载—>实例化—>服务—>销毁。

  • init方法:在Servlet的生命周期中,仅执行一次init方法。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init()。
  • service方法:它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能。
  • destroy方法:仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。

3.手写SpringMVC

1 web.xml加载:

为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息。通过web.xml中加载我们自己写的MyDispatcherServlet和读取配置文件。

2 初始化阶段:

在前面我们提到DispatcherServlet的initStrategies方法会初始化9大组件,但是这里将实现一些SpringMVC的最基本的组件而不是全部,按顺序包括:

  1. 加载配置文件
  2. 扫描用户配置包下面所有的类
  3. 拿到扫描到的类,通过反射机制,实例化。并且放到ioc容器中(Map的键值对 beanName-bean) beanName默认是首字母小写
  4. 初始化HandlerMapping,这里其实就是把url和method对应起来放在一个k-v的Map中,在运行阶段取出

3 运行阶段:

每一次请求将会调用doGet或doPost方法,所以统一运行阶段都放在doDispatch方法里处理,它会根据url请求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller中的url对应的方法,并得到结果返回。按顺序包括以下功能:

  1. 异常的拦截
  2. 获取请求传入的参数并处理参数
  3. 通过初始化好的handlerMapping中拿出url对应的方法名,反射调用

下面来实现,首先来看看包结构:

1.添加Maven依赖:

<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

2.ExtIndexController(最终调用结果):

package com.ylw.springmvc.controller;
import com.ylw.springmvc.core.ExtController;
import com.ylw.springmvc.core.ExtRequestMapping;
@ExtController
@ExtRequestMapping("/ext")
public class ExtIndexController {
  //ext/test/?name=122&age=6440654
  @ExtRequestMapping("/test")
  public String test(String name,Integer age) {
    System.out.println("手写springmvc框架...");
    return "index";
  }
}

3.ExtController注解和ExtRequestMapping注解:

---ExtController注解:
package com.ylw.springmvc.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtController {
}
---ExtRequestMapping注解:
package com.ylw.springmvc.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtRequestMapping {
  String value() default "";
}

4.ExtDispatcherServlet:

package com.ylw.springmvc.core;
import com.ylw.springmvc.util.ClassUtil;
import org.apache.commons.lang.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 1.自定义DispatcherServlet
 * <br>
 * 2.servlet init()方法初始化###只会执行一次
 * <br>
 * ----- 2.1获取当前包下所有的类
 * <br>
 * ----- 2.2初始化当前包下所有的类,使用Java反射机制初始化对象存放在SpringMVC容器中key(beanId)-value( 当前实例对象)
 * <br>
 * ----- 2.3初始化HandlerMapping方法,将url和方法对应上
 * <br>
 * ------ 2.3.1使用Java反射技术读取类的信息,存放在map集合中key为url请求地址,value为对应方法
 * <br>
 * ------ 2.3.2使用Java反射技术读取类的信息,存放在map集合中key为url请求地址,value为对应实例对象
 * <br>
 * 3.servlet get或者post请求
 * <br>
 * ------ 3.1获取请求地址,使用Java反射技术找到对应的方法和实例对象进行执行
 * <br>
 */
public class ExtDispatcherServlet extends HttpServlet {
    // mvc bean key=beanid ,value=对象
    private ConcurrentHashMap<String, Object> mvcBeans = new ConcurrentHashMap<String, Object>();
    // mvc 请求方法 key=requestUrl,value=对象
    private ConcurrentHashMap<String, Object> mvcBeanUrl = new ConcurrentHashMap<String, Object>();
    // mvc 请求方法 key=requestUrl,value=方法
    private ConcurrentHashMap<String, String> mvcMethodUrl = new ConcurrentHashMap<String, String>();
    /**
     * 初始化自定义SpringMVC容器
     */
    public void init() throws ServletException {
        try {
            // 1.获取当前包下所有的类
            List<Class<?>> classes = ClassUtil.getClasses("com.ylw.springmvc.controller");
            // 2.初始化当前包下所有的类,使用Java反射机制初始化对象存放在SpringMVC容器中key(beanId)-value(
            // 当前实例对象)
            findClassMVCBeans(classes);
            // 3.初始化HandlerMapping方法,将url和方法对应上
            handlerMapping(mvcBeans);
        } catch (Exception e) {
        }
    }
    // 2.初始化当前包下所有的类,使用Java反射机制初始化对象存放在SpringMVC容器中key(beanId)-value(
    // 当前实例对象)
    public void findClassMVCBeans(List<Class<?>> classes)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        mvcBeans = new ConcurrentHashMap<String, Object>();
        for (Class<?> classInfo : classes) {
            ExtController extController = classInfo.getDeclaredAnnotation(ExtController.class);
            if (extController != null) {
                // 默认类名小写 作为bean的名称
                String beanId = ClassUtil.toLowerCaseFirstOne(classInfo.getSimpleName());
                mvcBeans.put(beanId, ClassUtil.newInstance(classInfo));
            }
        }
    }
    // 3.初始化HandlerMapping方法,将url和方法对应上
    public void handlerMapping(ConcurrentHashMap<String, Object> mvcBeans) {
        // 遍历mvc bean对象
        for (Map.Entry<String, Object> entry : mvcBeans.entrySet()) {
            // springmvc 注入object对象
            Object mvcObject = entry.getValue();
            // 判断类上是否有@ExtRequestMapping注解
            Class<? extends Object> classInfo = mvcObject.getClass();
            String requestBaseUrl = null;
            ExtRequestMapping classExtRequestMapping = classInfo.getAnnotation(ExtRequestMapping.class);
            if (classExtRequestMapping != null) {
                requestBaseUrl = classExtRequestMapping.value();
            }
            // 遍历当前类的所有方法,判断方法上是否有注解
            Method[] declaredMethods = classInfo.getDeclaredMethods();
            for (Method method : declaredMethods) {
                ExtRequestMapping methodExtRequestMapping = method.getDeclaredAnnotation(ExtRequestMapping.class);
                if (methodExtRequestMapping != null) {
                    String httpRequestUrl = methodExtRequestMapping.value();
                    mvcBeanUrl.put(requestBaseUrl + httpRequestUrl, mvcObject);
                    mvcMethodUrl.put(requestBaseUrl + httpRequestUrl, method.getName());
                }
            }
        }
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            // TODO: handle exception
        }
    }
    public void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        // 1.获取请求url地址
        String requestUrl = req.getRequestURI();
        // 2.使用请求url查找对应mvc 控制器bean
        Object object = mvcBeanUrl.get(requestUrl);
        if (object == null) {
            resp.getWriter().println("http ext not found  controller 404");
            return;
        }
        // 3.获取对应的请求方法
        String methodName = mvcMethodUrl.get(requestUrl);
        if (StringUtils.isEmpty(methodName)) {
            resp.getWriter().println("http ext not found Method 404");
            return;
        }
        // 4.使用java反射技术执行方法
        Class<? extends Object> classInfo = object.getClass();
        String resultPage = (String) methodInvoke(classInfo, object, methodName);
        // 5.视图展示
        viewdisplay(resultPage, req, resp);
    }
    // 执行方法
    public Object methodInvoke(Class<? extends Object> classInfo, Object object, String methodName) {
        try {
            Method method = classInfo.getMethod(methodName);
            Object result = method.invoke(object);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    // 视图展示
    public void viewdisplay(String pageName, HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        // 获取后缀信息
        String suffix = ".jsp";
        // 页面目录地址
        String prefix = "/";
        req.getRequestDispatcher(prefix + pageName + suffix).forward(req, res);
    }
}

其它:OnRefresh 是FrameworkServlet类中的提供的模块方法,在其之类DispatchServlet中进行了重写,主要用于刷新Spring在web功能实现中所必须使用的全局变量。

总结

目录
相关文章
|
6月前
|
JSON 前端开发 Java
❤️最后的大爆发❤️五万字总结SpringMVC教程——三部曲封神之作(建议收藏)
❤️最后的大爆发❤️五万字总结SpringMVC教程——三部曲封神之作(建议收藏)
185 0
❤️最后的大爆发❤️五万字总结SpringMVC教程——三部曲封神之作(建议收藏)
|
12月前
|
设计模式 前端开发 Java
SpringMvc框架入门使用(详细教程)
SpringMvc框架入门使用(详细教程)
100 0
|
2天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
8 1
|
3天前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
11 1
|
24天前
|
XML Java 应用服务中间件
SpringMvc的具体操作,如何配置springMvc(完整教程)
一个完整的Spring MVC配置教程,包括引入依赖、配置Tomcat、设置DispatcherServlet、编写Spring配置文件、创建Controller以及测试结果。
74 0
SpringMvc的具体操作,如何配置springMvc(完整教程)
|
6月前
|
前端开发 Java 数据库连接
Springmvc保姆级丝滑教程(一文搞懂系列)(2)
Springmvc保姆级丝滑教程(一文搞懂系列)
|
6月前
|
JSON 前端开发 Java
Springmvc保姆级丝滑教程(一文搞懂系列)(1)
Springmvc保姆级丝滑教程(一文搞懂系列)
203 0
|
开发框架 前端开发 Java
初识SpringMVC -- SpringMVC快速入门保姆级教程(一)
初识SpringMVC -- SpringMVC快速入门保姆级教程(一)
145 0
|
前端开发 JavaScript Java
REST风格 -- SpringMVC快速入门保姆级教程(四)
REST风格 -- SpringMVC快速入门保姆级教程(四)
108 0
|
小程序 Java 程序员
SpringMVC bean加载控制 -- SpringMVC快速入门保姆级教程(二)
SpringMVC bean加载控制 -- SpringMVC快速入门保姆级教程(二)