手写简易springmvc

为了提高自己对springmvc框架的理解,决定模仿springmvc手写一个简单demo
1.创建servlet项目
新建一个maven项目,选中webapp项目的结构
手写简易springmvc_第1张图片
2.下载完成后添加servlet依赖

  
      javax.servlet
      javax.servlet-api
      3.0.1
      provided
    

3.新增自定义servlet类

package com.txxzctest.springmvcdemo;

import javax.servlet.http.HttpServlet;

/**
 * @author Huang
 */
public class MyDispatcherServlet extends HttpServlet{
}

.
4.删掉原有web.xml内容,修改为自己的servlet内容




  
    MySpringMVC
    com.txxzctest.springmvcdemo.MyDispatcherServlet
    1
  
  
    MySpringMVC
    /*
  

        

5.自定义servlet中重写init、doGet、doPost方法

  @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("启动成功!");
        super.init(config);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("get方法测试成功!");
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post方法测试成功!");
        super.doPost(req, resp);
    }

6.tomcat启动服务
手写简易springmvc_第2张图片
看到当前servlet项目启动成功
在这里插入图片描述

7.新增三个自定义的注解
NewController(自定义Controller)

package com.txxzctest.springmvcdemo.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NewController {
    /**
     * 表示给controller注册别名
     * @return
     */
    String value() default "";
}

NewRequestMapping(自定义RequestMapping)

package com.txxzctest.springmvcdemo.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NewRequestMapping {
    /**
     * 表示给requestmapping注册别名
     * @return
     */
    String value() default "";
}

8.扫描包下所有类,并加入到类集合里

首先需要找到相关包下的class文件,web.xml里设置包名

 
    MySpringMVC
    com.txxzctest.springmvcdemo.MyDispatcherServlet
    
      packName
      com.txxzctest.springmvcdemo
    
    1
  `

修改servlet中启动的逻辑,让其启动时扫描包下所有class文件

//类集合
    private List CLASSNAME = new ArrayList<>();
 //包名
    private String packName;

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("启动成功!");
        //获取包位置
        packName = config.getInitParameter("packName");
        //扫描包下所有class文件
        doScan(packName);
    }

    private void doScan(String packageName) {
        URL url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if(file.isDirectory()){
                //递归读取包
                doScan(packageName+"."+file.getName());
            }else{
                String className =packageName +"." +file.getName().replace(".class", "");
                //加入到集合中
                CLASSNAME.add(className);
            }
        }
    }

9.装载自定义controller到自定义ioc中

  //ioc容器
    private Map IOC = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("启动成功!");
        //获取包位置
        packName = config.getInitParameter("packName");
        //扫描包下所有class文件
        doScan(packName);
        //装载实例到ioc
        doInstant();

    }

    private void doInstant() {
        if (CLASSNAME.isEmpty()) {
            return;
        }
        for (String className : CLASSNAME) {
            try {
                Class clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(NewController.class)) {
                    IOC.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
                } else {
                    continue;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 把字符串的首字母小写
     *
     * @param name
     * @return
     */
    private String toLowerFirstWord(String name) {
        char[] charArray = name.toCharArray();
        charArray[0] += 32;
        return String.valueOf(charArray);
    }

10.初始化请求路由处理器

 //url-controller map方便后面访问时使用反射
    private Map controllerMap = new HashMap<>();
    //url-method map方便后面访问时使用反射
    private Map urlMap = new HashMap<>();


    @Override

    public void init(ServletConfig config) throws ServletException {
        System.out.println("启动成功!");
        //获取包位置
        packName = config.getInitParameter("packName");
        //扫描包下所有class文件
        doScan(packName);
        //装载实例到ioc
        doInstant();
        //初始化HandlerMapping(将url和method对应上)
        initHandlerMapping();

    }

    private void initHandlerMapping() {
        if (IOC.isEmpty()) {
            return;
        }
        for (String className : IOC.keySet()) {
            try {
                Class clazz = IOC.get(className).getClass();
                //不是controller组件跳过
                if (!clazz.isAnnotationPresent(NewController.class)) {
                    continue;
                }
                //获取controller 基本路径
                String controllerUrl = "";
                if(clazz.isAnnotationPresent(NewRequestMapping.class)) {
                    NewRequestMapping annotation = clazz.getAnnotation(NewRequestMapping.class);
                    controllerUrl = annotation.value();
                }
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (!method.isAnnotationPresent(NewRequestMapping.class)) {
                        continue;
                    }
                    String methodUrl = method.getAnnotation(NewRequestMapping.class).value();
                    String url = (controllerUrl + "/" + methodUrl).replaceAll("/+", "/");
                    urlMap.put(url, method);
                    controllerMap.put(url, clazz.newInstance());
                    System.out.println(url + "," + method);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

11.重写get和post方法,根据请求路由通过反射执行controller下的对应方法。

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

    private void doDispath(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (urlMap.isEmpty()) {
            resp.getWriter().write("404 NOT FOUND!");
            return;
        }

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        //拼接url并把多个/替换成一个
        url = url.replace(contextPath, "").replaceAll("/+", "/");

        if (!this.urlMap.containsKey(url)) {
            resp.getWriter().write("404 NOT FOUND!");
            return;
        }

        Method method = this.urlMap.get(url);
        //获取方法的参数列表
        Class[] parameterTypes = method.getParameterTypes();

        //获取请求的参数
        Map parameterMap = req.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] = req;
                continue;
            }
            if (requestParam.equals("HttpServletResponse")) {
                paramValues[i] = resp;
                continue;
            }
        }
        //利用反射机制来调用
        try {
            method.invoke(this.controllerMap.get(url), paramValues);//obj是method所对应的实例 在ioc容器中
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

编写测试用例

package com.txxzctest.springmvcdemo.controller;

import com.txxzctest.springmvcdemo.annotation.NewController;
import com.txxzctest.springmvcdemo.annotation.NewRequestMapping;

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

@NewController
@NewRequestMapping("/test")
public class TestController {
    @NewRequestMapping("/method")
    public void test(HttpServletRequest request, HttpServletResponse response) {
        try {
            response.getWriter().write("test success");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

访问该路由,测试成功
在这里插入图片描述

gitee 地址 :https://gitee.com/ChinaRage/easyspringmvcdemo

你可能感兴趣的:(手写简易springmvc)