zoukankan      html  css  js  c++  java
  • 实现一套山寨springMVC

    重复造轮子没有意义,但是通过现已存在的轮子,模仿着思路去实现一套,还是比较cool的。花了三天,终于深夜搞定!收益都在代码里,我干了,您随意!

     

    一、简单思路

     

    简单介绍:

    1、所有的请求交给TyDispatcher处理,TyDispatcher主要负责读取配置、分发请求、初始化handlerMapping等功能;

    2、根据handlerMapping与请求url,找到对应的处理方法,并由该处理方法处理业务逻辑;

     

    二、建项目

    1、新建maven项目

    选择webapp结尾的。然后继续创建,创建完成后,有个设置,因为本次项目是基于tomcat的,右击项目properties-->project facets-->Dynamic Web Module

    project facets就是指项目特性的意思,如果不选择这个,eclipse会把项目当做是普通的javase项目,那自然也就无法在tomcat等应用服务器上部署。

    进去之后设置路径为src/main/webapp,对应maven的工程目录结构。

    2、配置tomcat

    新建server这种就不多说了,将项目添加到tomcat中,若是添加不了,说明不是web项目,按照上步操作,其他基本没啥问题了。

    项目结构如图:

     

     

    三、代码

    1、注解类

    模仿着springMVC实现了@TyController、@TyRequestMapping以及@TyRequestParam三个注解。

    a、@TyController

     

    package com.ty.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author Taoyong
     * @date 2018年6月6日
     * 天下没有难敲的代码!
     */
    //只能在类上使用该注解
    @Target(ElementType.TYPE)
    //表示在运行时使用
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TyController {
    
        String value() default "";
    }

     

     

    b、@TyRequestMapping

    package com.ty.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author Taoyong
     * @date 2018年6月6日
     * 天下没有难敲的代码!
     */
    //该注解只能在方法上使用
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TyRequestMapping {
    
        String value() default "";
    }

     

    c、@TyRequestParam

    package com.ty.annotation;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author Taoyong
     * @date 2018年6月6日
     * 天下没有难敲的代码!
     */
    //该注解只能在参数上注解
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TyRequestParam {
    
        String value() default "";
    }

     

     

     2、TyDispatcher(初始化相关basePackage下的controller、分发请求、处理客户端请求参数等等)

    package com.ty.dispatcher;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Properties;
    
    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.ty.annotation.TyController;
    import com.ty.annotation.TyRequestMapping;
    import com.ty.annotation.TyRequestParam;
    
    /**
     * @author Taoyong
     * @date 2018年6月5日
     * 天下没有难敲的代码!
     * 此类的主要作用为初始化相关配置以及分发请求
     * 首先要将此servlet配置在web.xml中,在容器启动的时候,TyDispatcher初始化,并且在整个容器的生命周期中,只会创建一个TyDispatcher实例
     * TyDispatcher在web.xml中的配置信息将会包装成ServletConfig类
     */
    
    public class TyDispatcher extends HttpServlet {
    
        private static final long serialVersionUID = 9003485862460547072L;
        
        //自动扫描的basePackage
        private Properties property = new Properties();
        
        //保存所有需要自动创建实例对象的类的名字
        private List<String> classNameList = new ArrayList<>();
        
        //IOC容器,是一个键值对(数据格式:{"TestController": Object})
        public Map<String, Object> ioc = new HashMap<>();
        
        /*
         * 用于存放url与method对象映射关系的容器,数据格式为{"/base/TySpringMVCTest": Method}
         */    
        private Map<String, Method> handlerMappingMap = new HashMap<>();
        
        //用于将请求url与controller实例对象建立映射关系(数据格式:{"": Controller})
        public Map<String, Object> controllerContainer = new HashMap<>();
        
        /*
         * init方法主要用于初始化相关配置,比如basePackage
         * 
         */
        @Override
        public void init(ServletConfig config) throws ServletException {
            //配置basePackage,该包下所有的controller全部自动创建实例
    
            initBaseScan(config);
            
            //扫描basePackage下所有应该被实例化的类
            scanBasePackage(property.getProperty("scanPackage"));
            
            //根据classNameList中的文件去创建实例对象(即实例化basePackage下的controller)
            createInstance(classNameList);
            
            //初始化handlerMapping
            initHandlerMapping(ioc);
        }
    
        private void initBaseScan(ServletConfig config) {
            /*
             * servlet会将web.xml中的配置封装成ServletConfig对象。
             * 从该对象获取<init-param>所配置的初始化参数
             */        
            String location = config.getInitParameter("contextConfigLocation");
            //通过类加载器去读取该文件中的数据,并封装成流,然后通过property去加载流
            InputStream input = this.getClass().getClassLoader().getResourceAsStream(location);
            try {
                property.load(input);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(input != null) {
                    try {
                        input.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        
        private void scanBasePackage(String basePackage) {
            /*
             * 找到basePackage下所有的resource(resource即为图片、声音、文本等等数据)
             * 另外需要将com.ty.controller转换成/com/ty/controller/这种相对路径形式
             * \.是为了防止转义
             */
            URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\.", "/"));
            //此时file相当于一个总文件夹
            File totalFolder = new File(url.getFile());
            /*
             * 列出总文件夹下的文件夹以及文件
             */
            for(File file: totalFolder.listFiles()) {
                if(file.isDirectory()) {
                    //通过递归去找到最后一层级的文件,其实也就是.class文件(编译后)
                    scanBasePackage(basePackage + file.getName());
                } else {
                    //要将编译后文件的.class后缀去掉
                    classNameList.add(basePackage + "." + file.getName().replaceAll(".class", ""));
                } 
            }
        }
        
        private void createInstance(List<String> classNameList) {
            if(classNameList == null || classNameList.size() == 0) {
                return;
            }
            
            for(String className: classNameList) {
                try {
                    //根据className获取Class对象,通过反射去实例化对象
                    Class<?> clazz = Class.forName(className);
                    
                    //只需要将basePackage下的controller实例化即可
                    if(clazz.isAnnotationPresent(TyController.class)) {
                        Object obj = clazz.newInstance();
                        //这点跟spring容器稍有不同的是就是key值虽为类名,但是首字母并没有小写
                        ioc.put(clazz.getSimpleName(), obj);
                    }                
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        
        /*
         * controller现已实例化,但是还要对其方法的url(controller上注解的url+方法上的url)与方法对象建立起映射关系
         * ioc数据格式:{"TestController": Object}
         */
        private void initHandlerMapping(Map<String, Object> ioc) {
            if(ioc.isEmpty()) {
                return;
            }
            
            for(Entry<String, Object> entry: ioc.entrySet()) {
                Class<?> clazz = entry.getValue().getClass();
                //如果ioc容器中的对象不是controller对象,不进行处理
                if(!clazz.isAnnotationPresent(TyController.class)) {
                    continue;
                }
                
                /*
                 * controller上配置的@TyRequestMapping的值。因为controller类上使用@TyRequestMapping注解
                 * 是类维度的,所以通过clazz.getAnnotation获取value值
                 */            
                String baseURL = clazz.getAnnotation(TyController.class).value();
                
                //通过clazz获取到该类下所有的方法数组
                Method[] methods = clazz.getMethods();
                for(Method method: methods) {
                    //判断Method对象上是否存在@TyRequestMapping的注解,若有,取其value值
                    if(!method.isAnnotationPresent(TyRequestMapping.class)) {
                        continue;
                    }
                    
                    String methodURL =  method.getAnnotation(TyRequestMapping.class).value();
                    //数据格式:{"/controller/methodURL": Method}
                    handlerMappingMap.put(baseURL + methodURL, method);
                    //并且需要将url对应controller的映射关系保存
                    controllerContainer.put(baseURL + methodURL, entry.getValue());
                }
            }
        }
        
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doPost(req, resp);
        }
    
        /*
         * 此类用于处理客户端的请求,所有核心的分发逻辑由doPost控制,包括解析客户端请求参数等等
         * 
         */
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws 
        ServletException, IOException {
            /*
             * 根据JDK的说明,若请求url为http://localhost:8080/TySpringMVC/controller/testMethod
             * 则requestURL为/TySpringMVC/controller/testMethod。所以应该将项目名去掉
             */
            String requestURL = req.getRequestURI();
            //contextPath的路径为/TySpringMVC
            String contextPath = req.getContextPath();
            //requestMappingURL为/controller/testMethod
            String requestMappingURL = requestURL.replaceAll(contextPath, "");
            Method method = handlerMappingMap.get(requestMappingURL);
            
            if(method == null) {
                /*
                 * 通过Writer.write向与客户端连接的I/O通道写数据。并且这地方注意设置编码,要保证客户端解析编码与
                 * 代码中编码格式一致。这也是出现乱码的根本原因所在
                 */
                resp.setCharacterEncoding("UTF-8");
                String errorMessage = "404 请求URL不存在!";
                resp.getWriter().write(errorMessage);
                return;
            }
            
            /*
             * 获取前端入参,例如url为http://localhost:8080/TySpringMVC/sss?name=ty
             * map:{"name": ["ty","s"}
             */
            Map<String, String[]> paramMap = req.getParameterMap();
    
            //获取该method的所有参数对象数组。注:这地方找了好久api,才找到此方法。。。
            Parameter[] params = method.getParameters();
            //用于将前端参数封装,并且供method执行
            Object[] paramArr = new Object[params.length];
            for(int i = 0; i < params.length; i++) {
                //这两个if是用于解决method中的参数类型为HttpServletRequest或HttpServletResponse
                if("HttpServletRequest".equals(params[i].getType().getSimpleName())) {
                    paramArr[i] = req;
                    continue;
                }
                    
                if("HttpServletResponse".equals(params[i].getType().getSimpleName())) {
                    paramArr[i] = resp;
                    continue;
                }
                    
                if(params[i].isAnnotationPresent(TyRequestParam.class)) {
                    String paramKey = params[i].getAnnotation(TyRequestParam.class).value();
                    //客户端传过来的参数会是[小可爱]这种形式,因此需要去除[以及],并且需要使用转义符
                    String value = Arrays.toString(paramMap.get(paramKey)).replaceAll("\[", "").replaceAll("\]", "");
                    paramArr[i] = value;
                }    
            }
            
            //开始调用反射来执行method
            try {
                method.invoke(controllerContainer.get(requestMappingURL), paramArr);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void destroy() {
            super.destroy();
        }
        
    }

     

    3、tySpringMVC.properties(主要用来配置basePackage)

    scanPackage=com.ty.controller

     

    4、BaseCntroller(用来测试的controller)

    package com.ty.controller;
    
    import java.io.IOException;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.ty.annotation.TyController;
    import com.ty.annotation.TyRequestMapping;
    import com.ty.annotation.TyRequestParam;
    
    /**
     * @author Taoyong
     * @date 2018年6月7日
     * 天下没有难敲的代码!
     */
    @TyController("/baseController")
    public class BaseController {
    
        @TyRequestMapping("/firstMethod")
        public void firstMethod(HttpServletRequest req, HttpServletResponse resp, @TyRequestParam("name") String name) {
            try {
                resp.setCharacterEncoding("UTF-8");
                resp.getWriter().write("我不管,我最帅,我是你们的" + name);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

     

    四、测试

    1、成功场景(控制层中通过@TyRequestParam注解拿到前台的入参,并打印)

     

    2、请求不存在

     

     当然,水平有限,有什么错误的地方

     

    所有源代码已经上传到github中:https://github.com/ali-mayun/springMVC

     

    如果想给予我更多的鼓励,求打

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【阿里马云】!

  • 相关阅读:
    一个故事告诉你,数据分析如何给企业带来价值
    【CS231n】斯坦福大学李飞飞视觉识别课程笔记(九):最优化笔记(上)
    区块链P2P网络详细讲解
    互联网协议入门
    BitTorrent DHT 协议中文
    基于侧链的P2P网络设计
    【转】P2P-BT对端管理协议
    P2P网络与BitTorrent技术简介
    【COCOS2DX-LUA 脚本开发之四】使用tolua++编译pk创建自定义类
    爱创课堂每日一题第五十四天- 列举IE 与其他浏览器不一样的特性?
  • 原文地址:https://www.cnblogs.com/alimayun/p/9142225.html
Copyright © 2011-2022 走看看