仿 Spring 手写 MVC 框架

一、发现问题

不知道有没有小伙伴曾经或现在还依然跟我一样,一直迷惑于 Spring MVC 框架是怎么实现只要添加一个 @Controller 就能把普通的类变成 Servlet 的?或者说怎么就把一个请求转进了普通的一个类里的?为什么我的类就必须继承 HttpServlet 还必须配置 @WebServlet 注解或配置进 web.xml ?如果是我,我该怎么实现?带着这几个疑问,我们一起来梳理一下解决思路。

二、思路分析

  1. HttpServlet 是不是必须继承的?

    答:当然是必须的。因为这是 web 容器(Tomcat)与请求处理类之间的桥梁。

  2. HttpServlet 是每个请求处理类都必须实现吗?

    答:不一定。如果我们是通过传统的一个请求对应一个 Servlet 的方式来处理请求,那么这些 Servlet 就必须都实现 HttpServlet。但是如果我们构建一个继承自 HttpServlet 类的 DispatcherServlet,然后通过这个请求分发类去调用散布在不同 Java 类中的某个可以处理当前请求的方法即可。这样一来就不用每个请求处理类都继承 HttpServlet 了。

  3. 构建一个继承自 HttpServlet 类的 DispatcherServlet 进行请求分发听着是可以,但是要怎么分发出去呢?

    答:首先在处理请求前创建一个容器,缓存下每个 URL 所对应的处理方法及其相关信息。这样一来,当我们接收到请求后,可根据 URL 找到对应的处理方法信息,这样我们就能通过反射来调用处理方法,实现请求分发。

三、代码实现

(1)编写核心模块 DispatcherServlet

  1. MyDispatcherServlet 类中重写了三个方法 doGetdoPostinit, 请求处理前的准备工作自然就放在了 init 方法中,而分发请求进行处理的工作放在 doGetdoPost 中任一方法即可。
  2. init 方法中主要包含了五个步骤:
    • 读取配置文件。
    • 扫描所有需要交由 IoC 容器进行实例化的类,即带有:@Service@Controller 注解的类。
    • 实例化所有 IoC 容器中的类。
    • 属性注入。为带有 @AutoWired 注解的属性进行赋值。
    • 记录请求与处理器(处理方法)间的映射关系。
  3. doPost 方法中,主要的处理逻辑:
    • 第一步,由于该示例中加入了 @Security 注解进行处理方法的权限登记,所以在找到对应请求处理器后的第一件事就是鉴权。
    • 第二步,组装处理器反射调用时用到的参数。
    • 第三步,考虑到处理器的参数可能会用到 HTTPServletRequestHTTPServletResponse 这两个对象,所以在这步做的简单的处理。
    • 第四步,通过反射执行处理器。

MyDispatcherServlet

import com.idol.framework.builder.ConfigBuilder;
import com.idol.framework.factory.BeanFactory;
import com.idol.framework.handler.Handler;
import com.idol.framework.handler.HandlerMapping;
import com.idol.framework.util.ScanUtil;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
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.InvocationTargetException;
import java.util.*;

/**
 * @author Supreme_Sir
 * @version 1.0
 * @className MyDispatcherServlet
 * @description 自定义请求分发处理器
 * @date 2020/10/31 13:52
 **/
public class MyDispatcherServlet extends HttpServlet {
    
    
    private BeanFactory beanFactory = BeanFactory.getInstance();
    private HandlerMapping handlerMapping = HandlerMapping.getInstance();

    @Override
    public void init(ServletConfig config) throws ServletException {
    
    
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        try {
    
    
            // 加载配置文件
            Properties configProperties = ConfigBuilder.doLoadConfig(contextConfigLocation);
            // 扫描注解
            List<String> classPathList = ScanUtil.doScan((String) configProperties.getProperty("scanPackage"), 
                new ArrayList<String>());
            // 初始化相关 Bean
            beanFactory.doCreateBeans(classPathList);
            // 实现依赖注入
            beanFactory.doAutoWired();
            // 构造处理器映射器——HandlerMapping,以建立 URL 与处理方法间的映射关系
            handlerMapping.initHandlerMapping();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }

        System.out.println("my mvc 初始化完成");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    
    
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");

        Handler matchedHandler = handlerMapping.getMatchedHandler(req);
        if(matchedHandler == null) {
    
    
            resp.getWriter().write("对应的请求处理器没有被找到~!");
            return;
        }

        String name = req.getParameter("name");
        if (!matchedHandler.getUserPermission().contains(name)) {
    
    
            resp.getWriter().write("对不起,您没有访问该网络地址的权限~!");
            return;
        }

        int paramCount = matchedHandler.getMethod().getParameters().length;
        Object[] args = new Object[paramCount];

        /*
        说明:
        web 容器(Tomcat)考虑到如果一个 URL 请求如下时:http://localhost:8080/demo/query?name=lisi&name=zhangsan
        即同一个参数名多次出现的情况可能存在,所以就将同一个参数名下的值封装进一个数组中(["lisi", "zhangsan"])
        (当然如果参数名只出现一次也会封装进字符串数组),因此 parameterMap 的 value 泛型为 String[]。
         */
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry entry : parameterMap.entrySet()) {
    
    
            Map<String, Integer> indexMapping = matchedHandler.getParamIndexMapping();
            if (!indexMapping.containsKey(entry.getKey())) {
    
    
                continue;
            }
            Integer index = indexMapping.get(entry.getKey());
            args[index] = StringUtils.join((String[]) entry.getValue(), ",");
        }

        // 判断是否需要 HttpServletRequest 对象作为方法参数
        if (matchedHandler.isNeedRequest()) {
    
    
            Integer requestIndex = matchedHandler.getParamIndexMapping().get("HttpServletRequest");
            args[requestIndex] = req;
        }

        // 判断是否需要 HttpServletResponse 对象作为方法参数
        if (matchedHandler.isNeedResponse()) {
    
    
            Integer responseIndex = matchedHandler.getParamIndexMapping().get("HttpServletResponse");
            args[responseIndex] = resp;
        }

        try {
    
    
            matchedHandler.getMethod().invoke(matchedHandler.getObject(), args);
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        } catch (InvocationTargetException e) {
    
    
            e.printStackTrace();
        }

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    
    
        doPost(req, resp);
    }
}

(2)框架使用

说明:@Controller@RequestMapping()@AutoWired 这三个注解和 Spring MVC 中的注解使用方式相似,@Security() 注解中的值表示可以访问当前处理器的用户名,如果有多个,使用逗号进行分隔即可。

DemoController

import com.idol.framework.annotation.AutoWired;
import com.idol.framework.annotation.Controller;
import com.idol.framework.annotation.RequestMapping;
import com.idol.framework.annotation.Security;
import com.idol.website.service.IDemoService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author Supreme_Sir
 * @version 1.0
 * @className DemoController
 * @description
 * @date 2020/10/31 22:37
 **/
@Controller
@RequestMapping("/demo")
public class DemoController {
    
    
    @AutoWired
    private IDemoService demoService;

    @RequestMapping("/query")
    @Security({
    
    "zhangSan"})
    public void getName(HttpServletRequest req, HttpServletResponse resp, String name) throws IOException {
    
    
        resp.getWriter().write("查询用户名:" + demoService.getName(name));
        return;
    }

    @RequestMapping("/insert")
    @Security({
    
    "liSi"})
    public void insertName(HttpServletRequest req, HttpServletResponse resp, String name) throws IOException {
    
    
        resp.getWriter().write("添加用户名:" + demoService.getName(name));
        return;
    }

    @RequestMapping("/delete")
    @Security({
    
    "wangWu"})
    public void deleteName(HttpServletRequest req, HttpServletResponse resp, String name) throws IOException {
    
    
        resp.getWriter().write("删除用户名:" + demoService.getName(name));
        return;
    }

    @RequestMapping("/modify")
    @Security({
    
    "zhaoLiu"})
    public void modifyName(HttpServletRequest req, HttpServletResponse resp, String name) throws IOException {
    
    
        resp.getWriter().write("修改用户名:" + demoService.getName(name));
        return;
    }
}

源码

源码下载

--------------------- 哪来的天生优秀,都是一步一个坑踩过来的。 ---------------------

猜你喜欢

转载自blog.csdn.net/Supreme_Sir/article/details/109445930