由于最近太忙,作为一名程序员,整整6天没写一行代码,天天瞎扎腾网络和服务器的事,好在端午前终于把由于网络和服务器问题导致的集群问题解决了。终于抽出一点时间来写文章啦。
在上篇文章中

这里写目录标题

  • 1、SpringMVC的使用
  • 2、SpringMVC原理图
  • 3、预备知识Servlet
  • 4、实现SpringMVC三部曲
    • 4.1加载期
    • 4.2初始化
    • 4.3、运行期
    • 4.4、手写实现原理图
    • 4.5、代码实现
      • 4.5.1、代码结构
      • 4.5.1、Controller注解
      • 4.5.2、RequestMapping 注解
      • 4.5.3、DispatcherServlet
      • 4.5.4、ClassUtil 工具类
      • 4.5.5、实现效果

1、SpringMVC的使用

首先来回忆一下,我们使用SpringMVC的时候。在springMVC项目或者SpringBoot项目中请求如下的hutao这个方法
http:[ip:端口]/xxxxx/demo/hutao
我们是如何找到这个控制器类的嗯?当然是通过DomeController 上面的@RequestMapping中的demo。那我们又是如何找到是hutao这个方法的呢?当然也是通过@RequestMapping中的hutao。

@Controller
@RequestMapping("/demo")
public class DomeController {@RequestMapping("/hutao")public String hutao() {return "hutaohutaohutaohutaohutao";}
}

我们通过@Controller注解来标识这个类是一个请求接入控制器类,在结合@RequestMapping注解来识别我们调用的是控制器的哪一个方法。在这个主流程中最核心的类是由DispatcherServlet来实现的。本文就开始来用我们的方式来实现DispatcherServlet吧。

2、SpringMVC原理图

先上一张SpringMVC的原理示意图。

如果我们现在要实现自己的MVC,参考上图,我们需要梳理如下流程。
1、编写DispatcherServlet实现Servlet拦截所有的请求接入;
2、DispatcherServlet根据请求地址中的RequestMapping映射参数,找到对象,对象在调用对应的方法;
3、DispatcherServlet把控制器类处理结果返回给调用方。

3、预备知识Servlet

Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
Servlet 的生命周期:加载—>初始化—>服务—>销毁。
加载:在web.xml中配置加载
init()
在Servlet的生命周期中,仅执行一次init()方法。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init()。
service()
它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能。
destroy()
仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。
加载,并且初始化,只会被执行一次,之后根据请求方式的不同,service决定调用doPost还是doGet。

 protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {String method = req.getMethod();if (method.equals(METHOD_GET)) {long lastModified = getLastModified(req);if (lastModified == -1) {// servlet doesn't support if-modified-since, no reason// to go through further expensive logicdoGet(req, resp);} else {long ifModifiedSince;try {ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);} catch (IllegalArgumentException iae) {// Invalid date header - proceed as if none was setifModifiedSince = -1;}if (ifModifiedSince < (lastModified / 1000 * 1000)) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) {long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) {doPost(req, resp);} else if (method.equals(METHOD_PUT)) {doPut(req, resp);} else if (method.equals(METHOD_DELETE)) {doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) {doOptions(req,resp);} else if (method.equals(METHOD_TRACE)) {doTrace(req,resp);} else {//// Note that this means NO servlet supports whatever// method was requested, anywhere on this server.//String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}}

4、实现SpringMVC三部曲

按照阶段我们可以把实现分为三部分:加载,初始化,运行

4.1加载期

web项目在启动的时候,回去读取web.xml,因此要在项目启动的时候去加载我们所编写的DispatcherServlet。

 <servlet><servlet-name>dispatcher</servlet-name><servlet-class>com.sunwise.alm.spring.container.DispatcherServlet</servlet-class><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/alm/*</url-pattern></servlet-mapping>

4.2初始化

加载DispatcherServlet的时候,重写srvlet的init()方法,这个方法需要去扫描指定路径下的class文件,找到含有Controller注解的类型,并且创建Controller类,放到SpringMVC容器中,这里我们使用Map来实现容器。

 /*** 存放被@Controller修饰的类所创建的Controller对象*/private ConcurrentHashMap<String, Object> springmvcBeans = new ConcurrentHashMap<String, Object>();

将调用的请求地址,和我们要具体调用的方法形成映射关系,即通过调用地址来确定我们要调用的是那一个方法,这里我们也是用Map来实现容器。

 /*** url请求地址,value为对应方法*/private ConcurrentHashMap<String, Object> urlBeans = new ConcurrentHashMap<String, Object>();

根据调用的请求地址,找到我们要调用的是我们所创建的哪一个对象,这里我们同样用Map来实现容器

/*** key为url请求地址,value为对应实例对象*/private ConcurrentHashMap<String, String> urlMethods = new ConcurrentHashMap<String, String>();

4.3、运行期

当别人通过http请求的时候,我们的XML下配置了拦截的地址,因此会拦截我们所配置的的所有请求,然后根据请求地址,找到我们所要调用的对象,调用的方法。调用完毕后,返回结果给调用方。

/*** 从请求地址中,找到需要调用的是哪一个对象*/Object object = urlBeans.get(controllerUrl);
/*** 从请求地址中,找到需要调用的是哪一个方法*/String methodName = urlMethods.get(controllerUrl);/*** 调用这个对象的这个方法*/String result = (String) methodInvoke(object, methodName,req,resp);

4.4、手写实现原理图

4.5、代码实现

4.5.1、代码结构

4.5.1、Controller注解

/*** @Description Controller注解* @author hutao* @mail hutao_2017@aliyun.com* @date 2020年6月27日*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {}

4.5.2、RequestMapping 注解

/*** @Description RequestMapping注解* @author hutao* @mail hutao_2017@aliyun.com* @date 2020年6月27日*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {String value() default "";
}

4.5.3、DispatcherServlet

/*** @Description DispatcherServlet实现MVC* @author hutao* @mail hutao_2017@aliyun.com* @date 2020年6月27日*/
public class DispatcherServlet extends HttpServlet {private static Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);/**@Description * @author hutao* @date 2019年10月22日*/private static final long serialVersionUID = 1L;/*** 存放被@Controller修饰的类所创建的Controller对象*/private ConcurrentHashMap<String, Object> springmvcBeans = new ConcurrentHashMap<String, Object>();/*** key为url请求地址,value为对应方法*/private ConcurrentHashMap<String, Object> urlBeans = new ConcurrentHashMap<String, Object>();/*** key为url请求地址,value为对应实例对象*/private ConcurrentHashMap<String, String> urlMethods = new ConcurrentHashMap<String, String>();/*** @Description DispatcherServlet加载初始化* @author hutao* @mail hutao_2017@aliyun.com* @date 2020年6月27日*/public void init() throws ServletException {// 1.获取当前包下的所有的类List<Class<?>> classes = ClassUtil.getClasses("com.sunwise.alm.spring");// 2.将扫包范围所有的类,注入到springmvc容器里面,存放在Map集合中 key为默认类名小写,value 对象try {findClassMVCAnnotation(classes);} catch (Exception e) {// TODO: handle exception}// 3.将url映射和方法进行关联handlerMapping();}protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {doPost(req, resp);}//http://localhost/polarion/aem/dispatcher/demo/testprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setCharacterEncoding("utf-8");       resp.setContentType("text/html; charset=utf-8");PrintWriter writer = resp.getWriter();String requestURI = req.getRequestURI();String controllerUrl = "";if(requestURI!=null) {controllerUrl = requestURI.replace("/polarion/sunwise/alm", "");}if (controllerUrl == null || "".equals(controllerUrl)) {return;}/*** 从请求地址中,找到需要调用的是哪一个对象*/Object object = urlBeans.get(controllerUrl);if (object == null) {resp.getWriter().println(" 404 ");return;}/*** 从请求地址中,找到需要调用的是哪一个方法*/String methodName = urlMethods.get(controllerUrl);if (methodName == null || "".equals(methodName)) {resp.getWriter().println(" not found method 404");}/*** 调用这个对象的这个方法*/String result = (String) methodInvoke(object, methodName,req,resp);writer.print(result);writer.close();}/*** @Description DispatcherServlet实现MVC* @author hutao* @mail hutao_2017@aliyun.com* @date 2020年6月27日*/private Object methodInvoke(Object object, String methodName,HttpServletRequest req, HttpServletResponse resp) {try {Class<? extends Object> classInfo = object.getClass();Method[] methods = classInfo.getMethods();Method method = null;for(Method me : methods) {if(me.getName().equals(methodName)) {method = me;break;}}Object result = method.invoke(object);//Object result = method.invoke(object,req, resp);return result;} catch (Exception e) {e.printStackTrace();return null;}}/*** @Description 找到含有Controller修饰的类,并且创建对象放到springmvcBeans容器中* @author hutao* @mail hutao_2017@aliyun.com* @date 2020年6月27日*/public void findClassMVCAnnotation(List<Class<?>> classes)throws ClassNotFoundException, InstantiationException, IllegalAccessException {for (Class<?> classInfo : classes) {Controller extController = classInfo.getDeclaredAnnotation(Controller.class);if (extController != null) {String beanId = ClassUtil.toLowerCaseFirstOne(classInfo.getSimpleName());Object object = ClassUtil.newInstance(classInfo);springmvcBeans.put(beanId, object);}}}/*** @Description 映射绑定,根据请求地址确定请求调用的对象和方法* @author hutao* @mail hutao_2017@aliyun.com* @date 2020年6月27日*/public void handlerMapping() {for (Map.Entry<String, Object> mvcBean : springmvcBeans.entrySet()) {Object object = mvcBean.getValue();Class<? extends Object> classInfo = object.getClass();RequestMapping declaredAnnotation = classInfo.getDeclaredAnnotation(RequestMapping.class);String baseUrl = "";if (declaredAnnotation != null) {baseUrl = declaredAnnotation.value();}Method[] declaredMethods = classInfo.getDeclaredMethods();for (Method method : declaredMethods) {RequestMapping methodExtRequestMapping = method.getDeclaredAnnotation(RequestMapping.class);if (methodExtRequestMapping != null) {String methodUrl = baseUrl + methodExtRequestMapping.value();urlBeans.put(methodUrl, object);urlMethods.put(methodUrl, method.getName());}}}}
}

4.5.4、ClassUtil 工具类

public class ClassUtil {/*** @Description 根据文件名获取class文件* @author hutao* @date 2019年10月25日*/public static List<Class<?>> getClasses(List<String> list) {// 第一个class类的集合List<Class<?>> classes = new ArrayList<Class<?>>();try {for(String controllerName : list) {Class<?> controllerClass = Class.forName(controllerName);classes.add(controllerClass);}} catch (Exception e) {e.printStackTrace();}return classes;}/*** @Description 首字母变小写* @author hutao* @date 2019年10月25日*/public static String toLowerCaseFirstOne(String s) {if (Character.isLowerCase(s.charAt(0)))return s;elsereturn (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();}/*** @Description 初始化对象* @author hutao* @date 2019年10月25日*/public static Object newInstance(Class<?> classInfo)throws ClassNotFoundException, InstantiationException, IllegalAccessException {return classInfo.newInstance();}/*** @Description 根据包名获取包路径下的class* @author hutao* @date 2019年10月27日*/public static List<Class<?>> getClasses(String packageName) {// 第一个class类的集合List<Class<?>> classes = new ArrayList<Class<?>>();// 是否循环迭代boolean recursive = true;// 获取包的名字 并进行替换String packageDirName = packageName.replace('.', '/');// 定义一个枚举的集合 并进行循环来处理这个目录下的thingsEnumeration<URL> dirs;try {//String property = System.getProperty("catalina.home");dirs = DispatcherServlet.class.getClassLoader().getResources(packageDirName);//dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);// 循环迭代下去while (dirs.hasMoreElements()) {URL url = dirs.nextElement();String protocol = url.getProtocol();if ("file".equals(protocol)) {// 获取包的物理路径String filePath = URLDecoder.decode(url.getFile(), "UTF-8");// 以文件的方式扫描整个包下的文件 并添加到集合中findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);}else if ("bundleresource".equals(protocol)) {//针对我的特定项目实现的一种方式,正常的项目采用file协议Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("");URL nextElement = resources.nextElement();String file = nextElement.getFile();String substring = file.substring(0,file.lastIndexOf(packageName)).replace("file:", "")+packageName+"/bin/"+packageName.replace(".", "/");// 以文件的方式扫描整个包下的文件 并添加到集合中findAndAddClassesInPackageByFile(packageName,substring, recursive, classes);}else if ("jar".equals(protocol)) {// 如果是jar包文件// 定义一个JarFileJarFile jar;try {// 获取jarjar = ((JarURLConnection) url.openConnection()).getJarFile();// 从此jar包 得到一个枚举类Enumeration<JarEntry> entries = jar.entries();// 同样的进行循环迭代while (entries.hasMoreElements()) {// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件JarEntry entry = entries.nextElement();String name = entry.getName();// 如果是以/开头的if (name.charAt(0) == '/') {// 获取后面的字符串name = name.substring(1);}// 如果前半部分和定义的包名相同if (name.startsWith(packageDirName)) {int idx = name.lastIndexOf('/');// 如果以"/"结尾 是一个包if (idx != -1) {// 获取包名 把"/"替换成"."packageName = name.substring(0, idx).replace('/', '.');}// 如果可以迭代下去 并且是一个包if ((idx != -1) || recursive) {// 如果是一个.class文件 而且不是目录if (name.endsWith(".class") && !entry.isDirectory()) {// 去掉后面的".class" 获取真正的类名String className = name.substring(packageName.length() + 1, name.length() - 6);try {// 添加到classesclasses.add(Class.forName(packageName + '.' + className));} catch (ClassNotFoundException e) {e.printStackTrace();}}}}}} catch (IOException e) {e.printStackTrace();}}}} catch (IOException e) {e.printStackTrace();}return classes;}/*** 以文件的形式来获取包下的所有Class* * @param packageName* @param packagePath* @param recursive* @param classes*/public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,List<Class<?>> classes) {// 获取此包的目录 建立一个FileFile dir = new File(packagePath);//file:/D:/workSpace/casco/com.sunwise.alm.spring////D:/workSpace/casco/com.sunwise.alm.spring/bin/com/sunwise/alm/spring/springIOC/service/impl// 如果不存在或者 也不是目录就直接返回if (!dir.exists() || !dir.isDirectory()) {return;}// 如果存在 就获取包下的所有文件 包括目录File[] dirfiles = dir.listFiles(new FileFilter() {// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)public boolean accept(File file) {return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));}});// 循环所有文件for (File file : dirfiles) {// 如果是目录 则继续扫描if (file.isDirectory()) {findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,classes);} else {// 如果是java类文件 去掉后面的.class 只留下类名String className = file.getName().substring(0, file.getName().length() - 6);try {// 添加到集合中去classes.add(Class.forName(packageName + '.' + className));} catch (ClassNotFoundException e) {e.printStackTrace();}}}}
}

4.5.5、实现效果

尝试手写一个框架(二)手写一个MVC的框架相关推荐

  1. 小白兔写话_二年级写话小白兔

    看图写话 小白兔韩奕哲夏天到了,小白兔拿着锄头到田地里种白菜.小白兔先刨土,一直干到了晚上,累得小白兔大汗淋漓.一天又一天过去了,白菜的种子发芽了,可是发芽的种子已经枯萎了,小白兔急忙拿来水壶给白菜浇 ...

  2. 直接写出php二维数组一个值,直接取PHP二维数组里面的值

    具体是这样的,如下一个二维数组,是从库中读取出来的. $user = array( 0 => array( 'id' => 1, 'name' => '张三', 'email' =& ...

  3. [Unity] 状态机事件流程框架 (二) 设计游戏状态的保存框架,存档功能 ScriptableObject、EasySave

    前文 : ​​​​​​​[Unity] 状态机事件流程框架 (一) 本期来设计一个游戏状态的怎么在游戏中表示和存储.保存游戏状态的目的一是方便根据玩家当前的游戏进度实行各种各样的逻辑分支,二是在存档时 ...

  4. python开源项目框架二次开发_Python中三大框架各自的应用场景(DJango,flask,Tornado)...

    django:主要是用来搞快速开发的,他的亮点就是快速开发,节约成本,正常的并发量不过10000,如果要实现高并发的话,就要对django进行二次开发,比如把整个笨重的框架给拆掉,自己写socket实 ...

  5. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(14)-主框架搭建

    ASP.NET MVC+EF框架+EasyUI实现权限管理系列(14)-主框架搭建 原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(14)-主框架搭建    ASP.NET MV ...

  6. 神经网络和深度学习(二)——一个简单的手写数字分类网络

    本文转自:https://blog.csdn.net/qq_31192383/article/details/77198870 一个简单的手写数字分类网络 接上一篇文章,我们定义了神经网络,现在我们开 ...

  7. Python(TensorFlow框架)实现手写数字识别系统

    手写数字识别算法的设计与实现 本文使用python基于TensorFlow设计手写数字识别算法,并编程实现GUI界面,构建手写数字识别系统.这是本人的本科毕业论文课题,当然,这个也是机器学习的基本问题 ...

  8. Python TensorFlow框架 实现手写数字识别系统

    手写数字识别算法的设计与实现 本文使用python基于TensorFlow设计手写数字识别算法,并编程实现GUI界面,构建手写数字识别系统.这是本人的本科毕业论文课题,当然,这个也是机器学习的基本问题 ...

  9. 手写篇:如何手写RPC框架?

    手写篇:如何手写RPC框架? 首先我们讲下什么是RPC? RPC(Remote Procedure Call)远程过程调用协议,他是一种通过网络从远程计算机程序请求服务.简单的来说,就是通过网络进行远 ...

  10. 【手写系列】纯手写实现一个高可用的RPC

    前言 在实际后台服务开发中,比如订单服务(开发者A负责)需要调用商品服务(开发者B负责),那么开发者B会和A约定调用API,以接口的形式提供给A.通常都是B把API上传到Maven私服,然后B开始写A ...

最新文章

  1. Cacti安装详细步骤
  2. python九十八类_Python领域最伟大工程师Kenneth Reitz,教你写代码
  3. 上海交通大学出版社python答案_线性代数答案上海交通大学出版社
  4. 开发转测试没人要_前端开发,测试,后端,该如何选择?
  5. PRD的编写竟然暗含这个思路
  6. leetcode 103. 二叉树的锯齿形层次遍历
  7. ReportViewer教程(3)-添加空报表Report1.rdlc
  8. 逼疯上海人的垃圾分类满月了:羊肉串变羊肉吕,奶茶里珍珠按颗算...
  9. PIC16F887 单片机 proteus 红外遥控灯光控制系统
  10. 神经网络模型如何使用的,神经网络模型是干嘛的
  11. c语言指针 汇编间接寻址,C语言指针和汇编语言间接寻址的关省略探讨从存储空间图的视角加以分析.pdf...
  12. 支付宝AR抢红包?前端轻松就破解~
  13. Log Parser Lizard 日志分析工具
  14. Hash表的初步认识
  15. EMW3162 AT固件的使用【2】
  16. 我在国企外包一年的经历和感受
  17. 计算机网络—数据链路层
  18. MySQL数据库,数据的约束
  19. 奖金575万!81岁拓扑数学家摘得数学界诺奖「阿贝尔奖」
  20. 无法加载静态的图片Failed to load resource: the server responded with a status of 404 (Not Found)

热门文章

  1. Python 测试题(覆盖了大多数的基础知识和进阶)
  2. 瑞萨 报错 Section “.monitor2“ overlaps section “.textf“
  3. BLE(9)—— 细说 Advertising
  4. matlab中squareg,matlab中y=square(t,DUTY)的用法
  5. petalinux常用命令(转载)
  6. linux-raid (二) faulty 和 multipath
  7. 空间命名的定义及使用:using namespace std 的用法详解
  8. 链家混三个月底薪_深圳链家正式入职,我想对应届毕业生说
  9. 给未来的电子工程师nbsp;---电子牛人给…
  10. gitter 卸载_最佳Gitter渠道:硬件,物联网和机器人技术