• 手写简易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分支

  • 相关阅读:
    lombk在IDEA中报ClassNotFoundException错误
    Groovy在不同JDK版本下的性能差异
    Groovy中那些神奇注解之InheritConstructors
    Groovy中那些神奇注解之ToString
    Groovy中那些神奇注解之Memoized
    写个自己的远程桌面
    JAVA的BIT数组
    基于JDK 8的Dubbo Admin
    走进Groovy (二)
    走进Groovy (一)
  • 原文地址:https://www.cnblogs.com/kkzhilu/p/12859492.html
Copyright © 2020-2023  润新知