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

     

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

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

  • 相关阅读:
    微信二维码 场景二维码 用于推送事件,关注等 注册用户 ,经过测试
    简单的 Helper 封装 -- CookieHelper
    简单的 Helper 封装 -- SecurityHelper 安全助手:封装加密算法(MD5、SHA、HMAC、DES、RSA)
    Java反射机制
    Windows Azure Web Site (13) Azure Web Site备份
    Windows Azure Virtual Machine (1) IaaS用户手册
    Windows Azure Web Site (1) 用户手册
    Windows Azure Web Site (12) Azure Web Site配置文件
    Windows Azure Web Site (11) 使用源代码管理器管理Azure Web Site
    Windows Azure Web Site (10) Web Site测试环境
  • 原文地址:https://www.cnblogs.com/alimayun/p/9142225.html
Copyright © 2011-2022 走看看