zoukankan      html  css  js  c++  java
  • 手写简易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分支

  • 相关阅读:
    平衡二叉树之RB树
    平衡二叉树之AVL树
    实现哈希表
    LeetCode Median of Two Sorted Arrays
    LeetCode Minimum Window Substring
    LeetCode Interleaving String
    LeetCode Regular Expression Matching
    PAT 1087 All Roads Lead to Rome
    PAT 1086 Tree Traversals Again
    LeetCode Longest Palindromic Substring
  • 原文地址:https://www.cnblogs.com/kkzhilu/p/12859492.html
Copyright © 2011-2022 走看看