手写简易SpringMVC

手写简易SpringMVC


手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器

必备知识:
Servlet相关理解和使用,Maven,Java 反射,Java自定义注解

配置Web类型结构

结构如图所示:

在这里插入图片描述
注意 要设置 webapp为web moudle -> IDEA 有蓝色小圈圈为准,resource 配置为资源文件

配置Web.xml,配置Artifacts,配置文件

在这里插入图片描述
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <servlet>
        <servlet-name>KerwinCodes</servlet-name>
        <servlet-class>com.mycode.servlet.MyDispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>application.properties</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>KerwinCodes</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>
// 配置包扫描的路径
scanPackage=com.mycode

编码阶段

  1. 第一步毋庸置疑,我们需要创建必要的注解,如MyController,MyRequestMapping等等
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {

    String value() default "";
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {

    String value() default "";
}

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {

    String value();
}
  1. 需要思考我们如何才能实现SpringMVC
A.参考真正的SpringMVC, 它是基于Spring的基础上,因此我们需要自行实现IOC容器
B.想要实现IOC容易,管理Bean,我们就需要根据包扫描的路径进行全项目扫描
C.全项目扫描后,利用反射,同时根据注解判断是否是Bean,然后注入到Map容器中即可

D.遍历容器,获取存储的Bean中的方法,配合RequestMapping注解,得到 url - method映射,同时得到 url - object映射,存储到新的Map集合总,便于后续反射调用

E.页面请求时候,判断request.url 映射的到底是哪一个bean,哪一个方法 同时获取方法的参数,解析request的参数,即可匹配路径调用方法

F.万事俱备,到底如何运行?
Servlet -> init方法,doGet方法,doPost方法  实质就是Servlet生命周期中初始化和真正执行策略的方法,我们只需要重写方法,然后让doGet,doPost 都调用我们的方法即可

  1. 核心代码如下:
package com.mycode.servlet;

import com.mycode.annotation.MyController;
import com.mycode.annotation.MyRequestMapping;

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.*;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * ******************************
 * author:      柯贤铭
 * createTime:   2019/9/5 11:53
 * description:  MyDispatcherServlet
 * version:      V1.0
 * ******************************
 */
public class MyDispatcherServlet extends HttpServlet{

    /** 配置信息 **/
    private Properties properties = new Properties();

    /** 所有类的Class地址 **/
    private List<String> classNames = new ArrayList<>();

    /** Bean容器 **/
    private Map<String, Object> iocFactory = new HashMap<>();

    /** HandlerMapping - 方法**/
    private Map<String, Method> handleMapping = new HashMap<>();

    /** HandlerMapping - 对象**/
    private Map<String, Object> controllers = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {

        // 1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        // 2.初始化所有相关联的类,扫描用户设定的包下面所有的类
        doScanner(properties.getProperty("scanPackage"));

        // 3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v  beanName-bean)
        doInstance();

        // 4.初始化HandlerMapping(将url和method对应上)
        initHandlerMapping();
    }

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 加载配置文件索取包扫描路径
    private void doLoadConfig (String fileUrl)  {
        try {
            properties.load(this.getClass().getClassLoader().getResourceAsStream(fileUrl));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 扫描目录下所有的类
    private void doScanner (String rootPath) throws ServletException {
        URL url = this.getClass().getClassLoader().getResource( "/" + rootPath.replaceAll("\.", "/"));
        File file = new File(Objects.requireNonNull(url).getFile());
        if (!file.isDirectory()) {
            throw new ServletException("Base Package is wrong.");
        }

        for (File current : Objects.requireNonNull(file.listFiles())) {
            if (current.isDirectory()) {
                doScanner(rootPath + "." + current.getName());
            } else {
                String className = rootPath + "." + current.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    // 拿到所有的classNames 通过反射创建其对象 - 放入ioc容器中
    private void doInstance () {
        if (classNames.isEmpty()) {
            return;
        }

        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(MyController.class)) {
                    iocFactory.put(clazz.getSimpleName(), clazz.newInstance());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 初始化HandlerMapping(将url和method对应上)
    private void initHandlerMapping () {
        if (iocFactory.isEmpty()) {
            return;
        }

        for (String key : iocFactory.keySet()) {
            Class<? extends Object> clazz = iocFactory.get(key).getClass();
            if (!clazz.isAnnotationPresent(MyController.class)) {
                continue;
            }

            // 类 url
            MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
            String baseUrl = annotation.value();

            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(MyRequestMapping.class)) {
                    String mappingUrl = method.getAnnotation(MyRequestMapping.class).value();

                    // 获取匹配方法及对象 方便之后通过反射调用
                    handleMapping.put(baseUrl + mappingUrl, method);
                    controllers.put(baseUrl + mappingUrl, iocFactory.get(key));
                }
            }
        }
    }

    // 中央处理器
    private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (iocFactory.isEmpty() || handleMapping.isEmpty() || controllers.isEmpty()) {
            return;
        }

        String url = request.getRequestURI();

        // 如果不存在url
        if (!handleMapping.containsKey(url)) {
            response.getWriter().write("Do Not Get Url : 404 ERROR");
            return;
        }

        // HandleMapping 的方法
        Method method = handleMapping.get(url);

        // 获取方法的参数列表
        Class<?>[] parameterTypes = method.getParameterTypes();

        //获取请求的参数
        Map<String, String[]> parameterMap = request.getParameterMap();

        //保存参数值
        Object [] paramValues= new Object[parameterTypes.length];

        // 方法的参数列表
        for (int i = 0; i< parameterTypes.length; i++){
            //根据参数名称,做某些处理
            String requestParam = parameterTypes[i].getSimpleName();

            if (requestParam.equals("HttpServletRequest")){
                //参数类型已明确,这边强转类型
                paramValues[i] = request;
                continue;
            }
            if (requestParam.equals("HttpServletResponse")){
                paramValues[i] = response;
                continue;
            }
            if(requestParam.equals("String")){
                for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
                    String value = Arrays.toString(param.getValue()).replaceAll("\[|\]", "").replaceAll(",\s", ",");
                    paramValues[i] = value;
                }
            }
        }

        method.invoke(controllers.get(url), paramValues);
    }
}
  1. 测试代码:
@MyController
@MyRequestMapping("/test")
public class TestController {

    @MyRequestMapping("/doTest")
    public void test1 ( HttpServletRequest request, HttpServletResponse response,
                        @MyRequestParam("param") String param){
        System.out.println(param);
        try {
            response.getWriter().write( "doTest method success! param:"+param);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @MyRequestMapping("/doTest2")
    public void test2(HttpServletRequest request, HttpServletResponse response){
        try {
            response.getWriter().println("doTest2 method success!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试结果:

http://localhost:8080/ -> Do Not Get Url : 404 ERROR

http://localhost:8080/test/doTest2 -> doTest2 method success!

http://localhost:8080/test/doTest?param=asdasdad -> doTest method success! param:asdasdad

源码地址:https://github.com/kkzhilu/KerwinCodes code_springmvc分支

原文地址:https://www.cnblogs.com/kkzhilu/p/12859492.html