Github源码下载地址:https://github.com/chenxingxing6/springmvc
CSDN源码下载地址:https://download.csdn.net/download/m0_37499059/11783232


一、前言

SpringMVC是Spring框架的一个模块,是基于mvc的webframework模块。mvc是一种设计模式,即model-view-controller,mvc在b/s系统下的应用如下图所示。

SpringMvc原理图:


二、手写SpringMvc

代码下载Github:https://github.com/chenxingxing6/springmvc

我们所有的注解都自己定义,并对注解进行解析处理。通过写这个SpringMvc框架,我们可以大致掌握SpringMvc的实现思路,用户请求怎么进来的,怎么通过Mapping映射到具体需要执行的方法上,并如何对结果进行处理(Json数据格式,视图)。

需要依赖的包:

 <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><optional>true</optional><scope>provided</scope>  #应用服务器,比如tomcat都有这个jar包
</dependency>


GitHub里面对进行了更新,完善了更多的功能,具体看Github.

2.1项目结构


2.2登陆测试Demo

访问地址:http://localhost:8080/test/view?path=login



@RequestMapping("/login")public MyModeAndView login(@RequestParam("name") String name, @RequestParam("pwd") String pwd){MyModeAndView modeAndView = new MyModeAndView();modeAndView.setViewName("index");modeAndView.addObject("name", name);return modeAndView;}

<html>
<head><title>登陆</title>
</head>
<body>
<div><h1>欢迎登陆</h1><form action="/test/login" method="get"><div><label>用户名:</label><input name="name" type="text"/></div><div><label>用户名:</label><input name="pwd" type="password"/></div><div><label></label><input type="submit" value="提交"/></div></form>
</div>
</body>
</html>

<html>
<head><title>Title</title>
</head>
<body>
<div><h1>手写SpringMvc</h1><h3 style="color: blue;">欢迎 ${name} 你使用本系统....</h3>
</div>
</body>
</html>

2.3核心代码

package org.springframework.servlet;import com.alibaba.fastjson.JSON;
import org.springframework.annotation.*;
import org.springframework.core.*;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.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;/*** @Author: cxx* @Date: 2019/8/27 22:45*/
public class MyDispatcherServlet extends HttpServlet{// 存放配置信息Properties props = null;// 所有的类名private List<String> classNames = null;// 实例化对象Map<String, Object> ioc = null;// HandlerList<Handler> handlers;// 默认适配器IHandlerAdapter defaultHandlerAdapter = null;public MyDispatcherServlet(){props = new Properties();classNames = new ArrayList<>();ioc = new ConcurrentHashMap<>();handlers = new ArrayList<>();defaultHandlerAdapter = new DefaultHandlerAdapter();}// 初始化@Overridepublic void init(ServletConfig config) throws ServletException {System.out.println("------ My mvc is init start...... ------");// 1.加载配置文件doLoadConfig(config.getInitParameter("contextConfigLocation"));System.out.println("------ 1.加载配置文件成功-doLoadConfig() ------");// 2.根据配置文件扫描所有相关类doScanner(props.getProperty("scanPackage"));System.out.println("------ 2.扫描所有相关类-ddoScanner() ------");// 3.初始化所有相关类的实例,并将放入IOC容器中doInstance();System.out.println("------ 3.实例化成功-doInstance() ------");// 4.实现DIdoAutowried();System.out.println("------ 4.依赖注入成功-doAutowried() ------");// 5.初始化HandlerMappinginitHandlerMapping();System.out.println("------ 5.HandlerMapping初始化成功-initHandlerMapping() ------");System.out.println("------ My mvc is init end...... ------");}public void doLoadConfig(String location){String configName = location.split(":")[1];InputStream is = this.getClass().getClassLoader().getResourceAsStream(configName);try {props.load(is);}catch (Exception e){e.printStackTrace();}finally {if (is != null){try {is.close();}catch (Exception e){e.printStackTrace();}}}}public void doScanner(String packageName){// 进行递归扫描URL url = this.getClass().getClassLoader().getResource("/" + packageName.replace(".", "/"));File classDir = new File(url.getFile());for (File file : classDir.listFiles()) {if (file.isDirectory()){doScanner(packageName + "." + file.getName());}else {String className = packageName + "." + file.getName().replace(".class", "");classNames.add(className);}}}/*** IOC容器规则 key-value* 1.key默认用类名小写字段,否则优先使用用户自定义名字* 2.如果是接口,用接口的类型作为key*/public void doInstance(){if (classNames.isEmpty()){return;}// 利用反射,将扫描的className进行初始化try {for (String className : classNames) {Class clazz = Class.forName(className);// 进行Bean实例化,初始化IOCif (clazz.isAnnotationPresent(Controller.class)){String beanName = lowerFirstCase(clazz.getSimpleName());ioc.put(beanName, clazz.newInstance());}else if (clazz.isAnnotationPresent(Service.class)){Service service = (Service) clazz.getAnnotation(Service.class);String beanName = service.value();if ("".equals(beanName.trim())){beanName = lowerFirstCase(clazz.getSimpleName());}Object instance = clazz.newInstance();ioc.put(beanName, instance);// 接口也需要注入,接口类型作为keyClass<?>[] interfaces = clazz.getInterfaces();for (Class<?> i : interfaces) {ioc.put(i.getName(), instance);}}else {continue;}}}catch (Exception e){e.printStackTrace();}}public void doAutowried(){if (ioc.isEmpty()){return;}for (Map.Entry<String, Object> entry : ioc.entrySet()) {// 获取到所有字段,不管什么类型,都强制注入Field[] field = entry.getValue().getClass().getDeclaredFields();for (Field f : field) {if (f.isAnnotationPresent(Autowired.class)){Autowired autowired = f.getAnnotation(Autowired.class);String beanName = autowired.value().trim();if ("".equals(beanName)){// com.demo.service.ITestServicebeanName = f.getType().getName();}// 不管愿不愿意,都需要强吻f.setAccessible(true);try {// 例如:TestController -> TestServicef.set(entry.getValue(), ioc.get(beanName));}catch (Exception e) {e.printStackTrace();continue;}}}}}public void initHandlerMapping(){if (ioc.isEmpty()){return;}for (Map.Entry<String, Object> entry : ioc.entrySet()) {Class clazz = entry.getValue().getClass();if (!clazz.isAnnotationPresent(Controller.class)){continue;}String baseUrl = "";if (clazz.isAnnotationPresent(RequestMapping.class)){RequestMapping requestMapping = (RequestMapping)clazz.getAnnotation(RequestMapping.class);baseUrl = requestMapping.value();}Method[] methods = clazz.getMethods();for (Method method : methods) {if (!method.isAnnotationPresent(RequestMapping.class)){continue;}RequestMapping requestMapping = (RequestMapping)method.getAnnotation(RequestMapping.class);String regex = ("/" + baseUrl + requestMapping.value()).replaceAll("/+", "/");Pattern pattern = Pattern.compile(regex);handlers.add(new Handler(pattern, entry.getValue(), method));System.out.println("------   Mapping: " + regex + ", method:" + method);}}}// 6.运行阶段,等待请求@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {try {doDispatch(req, resp);String url = req.getRequestURI();String contextpath= req.getContextPath();url = url.replace(contextpath, "").replaceAll("/+", "/");System.out.println("进行请求....url:" + url);}catch (FileNotFoundException e){resp.getWriter().write("404 Not Found");return;}catch (Exception e){resp.getWriter().write("500 error \r\n\n" + Arrays.toString(e.getStackTrace()));return;}}private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{boolean jsonResult = false;// 获取适配器IHandlerAdapter handlerAdapter = defaultHandlerAdapter;Handler handler = handlerAdapter.getHandler(req, handlers);if (handler == null){throw new FileNotFoundException();}Object[] paramValues = handlerAdapter.hand(req, resp, handlers);Method method = handler.method;Object controller = handler.controller;String beanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());//如果controller或这个方法有UVResponseBody修饰,返回jsonif (controller.getClass().isAnnotationPresent(ResponseBody.class) || method.isAnnotationPresent(ResponseBody.class)){jsonResult = true;}Object object = method.invoke(ioc.get(beanName), paramValues);if (jsonResult && object !=null){resp.getWriter().write(JSON.toJSONString(object));}else {// 返回视图doResolveView(object, req, resp);}}public void doResolveView(Object object, HttpServletRequest req, HttpServletResponse resp) throws Exception{// 视图前缀String prefix = props.getProperty("view.prefix");// 视图后缀String suffix = props.getProperty("view.suffix");MyModeAndView modeAndView = null;if (object instanceof MyModeAndView){modeAndView = (MyModeAndView) object;}else {modeAndView = new MyModeAndView(object.toString());}DefaultViewResolver viewResolver = new DefaultViewResolver(prefix, suffix);viewResolver.resolve(modeAndView, req, resp);}/*** 首字母小写* @param old* @return*/private static String lowerFirstCase(String old){char [] chars = old.toCharArray();chars[0] += 32;return String.valueOf(chars);}
}

三、总结

服务启动会去读取web.xml配置文件,然后执行MyDispatcherServlet的init()方法,初始化一些配置,IOC,依赖注入,RequestMapping等,然后对发送进来的请求进行解析,通过反射方式调用到具体Controller的某个方法,然后对执行的结果进行处理。

 <servlet><servlet-name>DispatcherServlet</servlet-name><display-name>DispatcherServlet</display-name><servlet-class>org.springframework.servlet.MyDispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:application.properties</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>DispatcherServlet</servlet-name><url-pattern>/*</url-pattern></servlet-mapping>

添加过滤器对编码进行设置:

<!-- 过滤器 --><filter><filter-name>encoding</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>encoding</filter-name><url-pattern>/*</url-pattern></filter-mapping>

package org.springframework.web.filter;import javax.servlet.*;
import java.io.IOException;/*** @Author: cxx* @Date: 2019/9/15 23:56*/
public class CharacterEncodingFilter implements Filter{// 编码格式private String encoding;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("------ filter init .......");encoding = filterConfig.getInitParameter("encoding");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("------ 过滤器处理编码问题......" + encoding);servletRequest.setCharacterEncoding(encoding);servletResponse.setCharacterEncoding(encoding);servletResponse.setContentType("text/html");filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {}
}

第一篇 - 手写SpringMvc框架相关推荐

  1. 转载:手写SpringMVC框架

    带你手写一个SpringMVC框架(有助于理解springMVC) 链接:https://my.oschina.net/liughDevelop 作者:我叫刘半仙 Spring框架对于Java后端程序 ...

  2. 第四篇 - 手写RPC框架

    Github源码下载地址:https://github.com/chenxingxing6/myrpc 一.前言 RPC(Remote Procedure Call)-远程过程调用,它是一种通过网络从 ...

  3. 第02篇:手写JavaRPC框架之设计思路

    作者: 西魏陶渊明 博客: https://blog.springlearn.cn/ 天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄! 一.前言 隔壁老李又在喷我了: "完犊子了,小编这 ...

  4. js如何在当前页面加载springmvc返回的页面_手写SpringMVC学习

    前面我们学习了spring框架源码,做了一些自己手写的学习,最近,我们开始学习springMVC框架的学习 ,springMVC框架,相信大家不陌生了,所以这里不做过多的介绍了. SpringMVC以 ...

  5. 秒懂Spring源码,轻松手写SpringMVC框架

    1,3分钟读懂Spring核心源码: 2,SpringMVC与Spring框架关系: MVC--Spring的作用是整合,但不仅仅限于整合,Spring 框架可以被看做是一个企业解决方案级别的框架.客 ...

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

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

  7. Marco's Java【Dubbo 之手写Dubbo框架实现远程调用】

    前言 关于Dubbo入门的网上教程也特别多,因此我没有专门出关于Dubbo的系列博文(主要呢- 也是在忙些工作上的事儿),用Dubbo特别简单,但是想要把Dubbo学好,学精还得花费不少时间的,特别是 ...

  8. 手写springmvc

    手写springmvc 既然已经手写了spring的IOC,那springmvc肯定也要尝试写写了.手写spring博客:https://www.cnblogs.com/xiaojiesir/p/11 ...

  9. onclick 源码_精读:手写React框架 解析Hooks源码

    写在开头: 去年发表过一篇手写React,带diff算法,异步setState队列的文章,有一位阿里的朋友在下面评论,让我可以用hooks实现一次,也很简单,我当时觉得,这人有病,现在回过头来看,还是 ...

最新文章

  1. nagios监控windows USBKEY
  2. jmeter --- 基于InfluxDBGrafana的JMeter实时性能测试数据的监控和展示
  3. 一文带你理解云原生|云原生全景指南
  4. python卡方分布计算
  5. 《盘点那些秀你一脸的秒天秒地算法》(4)
  6. 没数据时y轴不显示_Matplotlib数据可视化
  7. C/C++根据特定字符分割字符串、读取文件去掉逗号等特定字符、strtok()函数详解
  8. 如何将所有音乐从 iPhone传输到Mac?
  9. powerquery分组_Power Query 基本功能使用
  10. Atitit.得到网络邻居列表java php c#.net python
  11. 按timestamp查询_04. 复杂查询
  12. Java学习路线图(完整详细2021版)
  13. jeb安装教程_JEB2插件教程之一
  14. 保姆级win7下配置虚拟机-安装Ubuntu20.04
  15. cortex-M3/cortex-M4处理器简介
  16. 成为钢铁侠!只需一块RTX3090,微软开源贾维斯(J.A.R.V.I.S.)人工智能AI助理系统
  17. github创建tag
  18. a类计算机机房建设标准,A类机房建设标准和B类机房建设标准有什么差别
  19. python蓝桥杯准备
  20. 游戏充值数据分析报告

热门文章

  1. chrome浏览器自定义安装方法
  2. Spring注解驱动开发第7讲——如何按照条件向Spring容器中注册bean?这次我懂了!!
  3. php 读取图片bgr,OpenCV读取图像为BGR
  4. 高完整性系统工程(十一):Fault Tolerant Design
  5. 好消息,​继中山、佛山、东莞后,广东又有一城市放松​限购,终于可以购买第二套房了...
  6. SAP ABAP修改字段描述(翻译及传输)
  7. 安卓9.0发布,献上Android 9.0新特性适配处理
  8. LeetCode-791. 自定义字符串排序【哈希表,字符串,排序】
  9. 基于JAVA网上书店进销存管理系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署
  10. mysqldump Version Mismatch