手动实现 SpringMVC

先看整体架构

因为我们这个 MVC 框架要依赖 IOC/DI 容器,所以我们在 pom 文件里要将自己的 Spring 框架引入进来。

实现 MVC 的整体功能

首先我们定义两个 MVC 专用的注解,RequestMapping 用来做 url 匹配,RequestParam 做参数转换:

packagecom.paul.annotation;importjava.lang.annotation.*;@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RequestMapping {Stringvalue()default"";}

packagecom.paul.annotation;importjava.lang.annotation.*;@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceRequestParam {Stringvalue()default"";}

我们知道 SpringMVC 的核心是 DispatcherServlet,用来做核心路由控制,我们也定义这样一个类,并且在初始化方法里初始化一个 IOC/DI 容器,看过前面文章的同学应该知道,初始化容器后我们已经将 Bean 放到容器中而且完成了依赖注入。

package com.paul.servlet;importcom.paul.annotation.RequestMapping;importcom.paul.annotation.RequestParam;importorg.springframework.ioc.annotation.Controller;importorg.springframework.ioc.bean.AnnotationApplicationContext;importorg.springframework.ioc.bean.ApplicationContext;importjavax.servlet.ServletConfig;importjavax.servlet.ServletException;importjavax.servlet.ServletRequest;importjavax.servlet.ServletResponse;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.lang.annotation.Annotation;importjava.lang.reflect.InvocationTargetException;importjava.lang.reflect.Method;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;//实现 servlet,和我们以前使用 servlet 一样。publicclassDispatcherServletextendsHttpServlet{// 完整路径和 方法的 mappingprivateMap handleMapping =newConcurrentHashMap<>();// 类路径和controller 的 mappingprivateMap controllerMapping =newConcurrentHashMap<>();        privateMap beanFactory =newConcurrentHashMap<>();    @Override    publicvoidinit(ServletConfig config) throws ServletException {super.init(config);//实例化 IOC 容器ApplicationContext ctx =newAnnotationApplicationContext("applicationContext.xml");this.beanFactory = ((AnnotationApplicationContext) ctx).beanFactory;//上一步已经完成了 Controller,service,respostry,autowired 等注解的扫描和注入//遍历容器,将 requestmapping 注解的路径和对应的方法以及 contoller 实例对应起来for(Map.Entry entry:beanFactory.entrySet()){Objectinstance = entry.getValue();            Class clazz = instance.getClass();if(clazz.isAnnotationPresent(Controller.class)){                RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);StringclassPath = requestMapping.value();                Method[] methods = clazz.getMethods();for(Method method:methods){if(method.isAnnotationPresent(RequestMapping.class)){                        RequestMapping requestMapping2 = method.getAnnotation(RequestMapping.class);StringmethodPath = requestMapping2.value();StringrequestPath = classPath + methodPath;                        handleMapping.put(requestPath,method);                        controllerMapping.put(requestPath,instance);                    }else{continue;                    }                }            }else{continue;            }        }    }    @Override    protectedvoiddoGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);    }//根据上一步获取到的 mapping,根据 url 找到对应的 controller 和方法去执行。@Override    protectedvoiddoPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Stringuri = req.getRequestURI();//  /paul-mvc/com.paul.controller/method-com.paul.controllerStringcontext = req.getContextPath();// /paul-vmcStringpath = uri.replace(context,"");// /com.paul.controller/method-com.paul.controllerMethod m = (Method) handleMapping.get(path);//从容器里拿到controller 实例Objectinstance = controllerMapping.get(path);Object[] args =  handle(req,resp,m);for(Objecta:args){            System.out.println("Object:"+a);        }try{            m.invoke(instance,args);        }catch(IllegalAccessException e) {            e.printStackTrace();        }catch(InvocationTargetException e) {            e.printStackTrace();        }    }    privatestaticObject[] handle(HttpServletRequest req, HttpServletResponse resp,Method method){//拿到当前执行的方法有哪些参数Class[] paramClazzs = method.getParameterTypes();//根据参数的个数,new 一个参数的数据Object[] args =newObject[paramClazzs.length];        int args_i =0;        int index =0;for(Class paramClazz:paramClazzs){if(ServletRequest.class.isAssignableFrom(paramClazz)){                args[args_i++] = req;            }if(ServletResponse.class.isAssignableFrom(paramClazz)){                args[args_i++] = resp;            }//判断requestParam  注解Annotation[] paramAns = method.getParameterAnnotations()[index];if(paramAns.length >0){                System.out.println("my");for(Annotation paramAn:paramAns){if(RequestParam.class.isAssignableFrom(paramAn.getClass())){                        System.out.println("13mj");                        RequestParam rp = (RequestParam) paramAn;                        args[args_i++] = req.getParameter(rp.value());                    }                }            }            index ++;        }returnargs;    }}

测试代码

新建一个 WebDemo,Maven web 项目。

先来看我们需要测试的 Controller 和 Service。

@Controller@RequestMapping("/user")publicclassUserController{@AutowiredprivateUserService userService;@RequestMapping("/query")publicvoidget(HttpServletRequest request, HttpServletResponse response, @RequestParam("name")String name,@RequestParam("age")String age){try{            PrintWriter pw = response.getWriter();            String res = userService.query(name,age);            pw.write(res);        }catch(IOException e) {            e.printStackTrace();        }    }}

importorg.springframework.ioc.annotation.Service;@Service("userService")publicclassUserServiceImplimplementsUserService{@OverridepublicStringquery(String name, String age){return"name="+name+"age="+age;    }}

在 resources 目录我们需要写一个名字为 applicationContext 的配置文件来指明扫描包路径。

<?xml version="1.0"encoding="UTF-8"?>

在 web.xml 中配置我们自己的 DispatcherServlet。

<!DOCTYPE web-app PUBLIC

        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

        "http://java.sun.com/dtd/web-app_2_3.dtd" >Archetype Created Web ApplicationDispatcherServletcom.paul.servlet.DispatcherServlet0DispatcherServlet/

在浏览器中测试结果:

结果和我们想的一样。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容