学习自《Spring 5核心原理与30个类手写实战》作者 Tom 老师

手写 Spring MVC

不多说,简历装 X 必备。不过练好还是需要求一定的思维能力。

一、整体思路

思路要熟练背下来

1)配置阶段

  • 配置 web.xml:

    • <sevlet>
    • XDispatchServlet
    • 设定 init-param: contextConfigLocation = applicationContext.properties
    • <servlet-mapping>
    • 设定 url-pattern: /*
  • 配置 Annotation: @XController @XService @XAutowired @XRequestMapping

2)初始化阶段

  • IOC:

    • 调用 init() 方法: 加载配置文件
    • IOC 容器初始化: Map<String, Object>
    • 扫描相关的类: scan-package=“com.xiaopengwei”
    • 创建实例化并保存到容器: 同过反射机制将类实例化放入 IOC 容器中
  • DI:
    • 进行 DI 操作: 扫描 IOC 容器中的实例,给没有赋值的属性自动赋值
  • MVC:
    • 初始化 HandlerMapping: 将一个 URL 和一个 Method 进行一对一的关联映射 Map<String, Method>

3)运行阶段

  • 调用 doGet() / doPost() 方法: Web 容器调用 doGet() / doPost() 方法,获得 request/response 对象
  • 匹配 HandleMapping: 从 request 对象中获得用户输入的 url,找到其对应的 Method
  • 反射调用 method.invoker(): 利用反射调用方法并返回结果
  • response.getWrite().write(): 将返回结果输出到浏览器

二、源代码

GitHub:https://github.com/xpwi/spring-custom

项目结构:

源代码:

(1)在 pom.xml 引入一个 jar 包

<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version>
</dependency>

(2)web.xml 文件:

<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><servlet><servlet-name>xmvc</servlet-name><servlet-class>com.xiaopengwei.xspring.servlet.XDispatchServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><!--you can't use classpath*: --><param-value>application.properties</param-value></init-param></servlet><servlet-mapping><servlet-name>xmvc</servlet-name><url-pattern>/*</url-pattern></servlet-mapping>
</web-app>

(3)application.properties 文件:

scan-package=com.xiaopengwei

(4)自定义注解 XAutowired:

package com.xiaopengwei.xspring.annotation;
import java.lang.annotation.*;
/*** <p>** @author XiaoPengwei* @since 2019-07-19*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XAutowired {String value() default "";
}

(5)自定义注解 XController:

package com.xiaopengwei.xspring.annotation;
import java.lang.annotation.*;
/*** <p>** @author XiaoPengwei* @since 2019-07-19*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XController {String value() default "";
}

(6)自定义注解 XRequestMapping:

package com.xiaopengwei.xspring.annotation;
import java.lang.annotation.*;
/*** <p>** @author XiaoPengwei* @since 2019-07-19*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XRequestMapping {String value() default "";
}

(7)自定义注解 XService:

package com.xiaopengwei.xspring.annotation;
import java.lang.annotation.*;
/*** <p>** @author XiaoPengwei* @since 2019-07-19*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XService {String value() default "";
}

(8)核心 XDispatchServlet:

package com.xiaopengwei.xspring.servlet;
import com.xiaopengwei.xspring.annotation.XAutowired;
import com.xiaopengwei.xspring.annotation.XController;
import com.xiaopengwei.xspring.annotation.XRequestMapping;
import com.xiaopengwei.xspring.annotation.XService;
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.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;/*** <p>* XSpring** @author XiaoPengwei* @since 2019-07-19*/
public class XDispatchServlet extends HttpServlet {/*** 属性配置文件*/private Properties contextConfig = new Properties();private List<String> classNameList = new ArrayList<>();/*** IOC 容器*/Map<String, Object> iocMap = new HashMap<String, Object>();Map<String, Method> handlerMapping = new HashMap<String, Method>();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//7、运行阶段try {doDispatch(req, resp);} catch (Exception e) {e.printStackTrace();resp.getWriter().write("500 Exception Detail:\n" + Arrays.toString(e.getStackTrace()));}}/*** 7、运行阶段,进行拦截,匹配** @param req  请求* @param resp 响应*/private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws InvocationTargetException, IllegalAccessException {String url = req.getRequestURI();String contextPath = req.getContextPath();url = url.replaceAll(contextPath, "").replaceAll("/+", "/");System.out.println("[INFO-7] request url-->" + url);if (!this.handlerMapping.containsKey(url)) {try {resp.getWriter().write("404 NOT FOUND!!");return;} catch (IOException e) {e.printStackTrace();}}Method method = this.handlerMapping.get(url);System.out.println("[INFO-7] method-->" + method);String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());System.out.println("[INFO-7] iocMap.get(beanName)->" + iocMap.get(beanName));// 第一个参数是获取方法,后面是参数,多个参数直接加,按顺序对应method.invoke(iocMap.get(beanName), req, resp);System.out.println("[INFO-7] method.invoke put {" + iocMap.get(beanName) + "}.");}@Overridepublic void init(ServletConfig servletConfig) throws ServletException {//1、加载配置文件doLoadConfig(servletConfig.getInitParameter("contextConfigLocation"));//2、扫描相关的类doScanner(contextConfig.getProperty("scan-package"));//3、初始化 IOC 容器,将所有相关的类实例保存到 IOC 容器中doInstance();//4、依赖注入doAutowired();//5、初始化 HandlerMappinginitHandlerMapping();System.out.println("XSpring FrameWork is init.");//6、打印数据doTestPrintData();}/*** 6、打印数据*/private void doTestPrintData() {System.out.println("[INFO-6]----data------------------------");System.out.println("contextConfig.propertyNames()-->" + contextConfig.propertyNames());System.out.println("[classNameList]-->");for (String str : classNameList) {System.out.println(str);}System.out.println("[iocMap]-->");for (Map.Entry<String, Object> entry : iocMap.entrySet()) {System.out.println(entry);}System.out.println("[handlerMapping]-->");for (Map.Entry<String, Method> entry : handlerMapping.entrySet()) {System.out.println(entry);}System.out.println("[INFO-6]----done-----------------------");System.out.println("====启动成功====");System.out.println("测试地址:http://localhost:8080/test/query?username=xiaopengwei");System.out.println("测试地址:http://localhost:8080/test/listClassName");}/*** 5、初始化 HandlerMapping*/private void initHandlerMapping() {if (iocMap.isEmpty()) {return;}for (Map.Entry<String, Object> entry : iocMap.entrySet()) {Class<?> clazz = entry.getValue().getClass();if (!clazz.isAnnotationPresent(XController.class)) {continue;}String baseUrl = "";if (clazz.isAnnotationPresent(XRequestMapping.class)) {XRequestMapping xRequestMapping = clazz.getAnnotation(XRequestMapping.class);baseUrl = xRequestMapping.value();}for (Method method : clazz.getMethods()) {if (!method.isAnnotationPresent(XRequestMapping.class)) {continue;}XRequestMapping xRequestMapping = method.getAnnotation(XRequestMapping.class);String url = ("/" + baseUrl + "/" + xRequestMapping.value()).replaceAll("/+", "/");handlerMapping.put(url, method);System.out.println("[INFO-5] handlerMapping put {" + url + "} - {" + method + "}.");}}}/*** 4、依赖注入*/private void doAutowired() {if (iocMap.isEmpty()) {return;}for (Map.Entry<String, Object> entry : iocMap.entrySet()) {Field[] fields = entry.getValue().getClass().getDeclaredFields();for (Field field : fields) {if (!field.isAnnotationPresent(XAutowired.class)) {continue;}System.out.println("[INFO-4] Existence XAutowired.");// 获取注解对应的类XAutowired xAutowired = field.getAnnotation(XAutowired.class);String beanName = xAutowired.value().trim();// 获取 XAutowired 注解的值if ("".equals(beanName)) {System.out.println("[INFO] xAutowired.value() is null");beanName = field.getType().getName();}// 只要加了注解,都要加载,不管是 private 还是 protectfield.setAccessible(true);try {field.set(entry.getValue(), iocMap.get(beanName));System.out.println("[INFO-4] field set {" + entry.getValue() + "} - {" + iocMap.get(beanName) + "}.");} catch (IllegalAccessException e) {e.printStackTrace();}}}}/*** 3、初始化 IOC 容器,将所有相关的类实例保存到 IOC 容器中*/private void doInstance() {if (classNameList.isEmpty()) {return;}try {for (String className : classNameList) {Class<?> clazz = Class.forName(className);if (clazz.isAnnotationPresent(XController.class)) {String beanName = toLowerFirstCase(clazz.getSimpleName());Object instance = clazz.newInstance();// 保存在 ioc 容器iocMap.put(beanName, instance);System.out.println("[INFO-3] {" + beanName + "} has been saved in iocMap.");} else if (clazz.isAnnotationPresent(XService.class)) {String beanName = toLowerFirstCase(clazz.getSimpleName());// 如果注解包含自定义名称XService xService = clazz.getAnnotation(XService.class);if (!"".equals(xService.value())) {beanName = xService.value();}Object instance = clazz.newInstance();iocMap.put(beanName, instance);System.out.println("[INFO-3] {" + beanName + "} has been saved in iocMap.");// 找类的接口for (Class<?> i : clazz.getInterfaces()) {if (iocMap.containsKey(i.getName())) {throw new Exception("The Bean Name Is Exist.");}iocMap.put(i.getName(), instance);System.out.println("[INFO-3] {" + i.getName() + "} has been saved in iocMap.");}}}} catch (Exception e) {e.printStackTrace();}}/*** 获取类的首字母小写的名称** @param className ClassName* @return java.lang.String*/private String toLowerFirstCase(String className) {char[] charArray = className.toCharArray();charArray[0] += 32;return String.valueOf(charArray);}/*** 2、扫描相关的类** @param scanPackage properties --> scan-package*/private void doScanner(String scanPackage) {// package's . ==> /URL resourcePath = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));if (resourcePath == null) {return;}File classPath = new File(resourcePath.getFile());for (File file : classPath.listFiles()) {if (file.isDirectory()) {System.out.println("[INFO-2] {" + file.getName() + "} is a directory.");// 子目录递归doScanner(scanPackage + "." + file.getName());} else {if (!file.getName().endsWith(".class")) {System.out.println("[INFO-2] {" + file.getName() + "} is not a class file.");continue;}String className = (scanPackage + "." + file.getName()).replace(".class", "");// 保存在内容classNameList.add(className);System.out.println("[INFO-2] {" + className + "} has been saved in classNameList.");}}}/*** 1、加载配置文件** @param contextConfigLocation web.xml --> servlet/init-param*/private void doLoadConfig(String contextConfigLocation) {InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);try {// 保存在内存contextConfig.load(inputStream);System.out.println("[INFO-1] property file has been saved in contextConfig.");} catch (IOException e) {e.printStackTrace();} finally {if (null != inputStream) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}}
}

(9)示例:TestController:

package com.xiaopengwei.demo.xcontroller;
import com.xiaopengwei.demo.xservice.ITestXService;
import com.xiaopengwei.xspring.annotation.XAutowired;
import com.xiaopengwei.xspring.annotation.XController;
import com.xiaopengwei.xspring.annotation.XRequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;/*** <p>* 前置控制器** @author XiaoPengwei* @since 2019-07-19*/
@XController
@XRequestMapping("/test")
public class TestController {@XAutowiredITestXService testXService;/*** 测试方法 /test/query** @param req  请求体* @param resp 响应体*/@XRequestMapping("/query")public void query(HttpServletRequest req, HttpServletResponse resp) {if (req.getParameter("username") == null) {try {resp.getWriter().write("param username is null");} catch (IOException e) {e.printStackTrace();}} else {String paramName = req.getParameter("username");try {resp.getWriter().write("param username is " + paramName);} catch (IOException e) {e.printStackTrace();}System.out.println("[INFO-req] New request param username-->" + paramName);}}/*** 测试方法 /test/listClassName** @param req  请求体* @param resp 响应体*/@XRequestMapping("/listClassName")public void listClassName(HttpServletRequest req, HttpServletResponse resp) {String str = testXService.listClassName();System.out.println("testXService----------=-=-=>" + str);try {resp.getWriter().write(str);} catch (IOException e) {e.printStackTrace();}}
}

(10)示例接口:ITestXService:

package com.xiaopengwei.demo.xservice;
/*** <p>* 接口** @author XiaoPengwei* @since 2019-07-19*/
public interface ITestXService {String listClassName();
}

(11)示例实现类 TestXServiceImpl:

package com.xiaopengwei.demo.xservice.impl;
import com.xiaopengwei.demo.xservice.ITestXService;
import com.xiaopengwei.xspring.annotation.XService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;/*** <p>* 业务实现类** @author XiaoPengwei* @since 2019-07-19*/
@XService
public class TestXServiceImpl implements ITestXService {@Overridepublic String listClassName() {// 假装来自数据库return "123456TestXServiceImpl";}
}

(12)测试:

配置 Tomcat 后,访问:

http://localhost:8080/test/query?username=xiaopengwei

http://localhost:8080/test/listClassName

https://github.com/xpwi/spring-custom

手写 Spring MVC相关推荐

  1. JAVA项目代码手写吗_一个老程序员是如何手写Spring MVC的

    见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十多 ...

  2. 记录一次阿里架构师全程手写Spring MVC

    人见人爱的Spring已然不仅仅只是一个框架了.如今,Spring已然成为了一个生态.但深入了解Spring的却寥寥无几.这里,我带大家一起来看看,我是如何手写Spring的.我将结合对Spring十 ...

  3. 手写 Spring 事务、IOC、DI 和 MVC

    Spring AOP 原理 什么是 AOP? AOP 即面向切面编程,利用 AOP 可以对业务进行解耦,提高重用性,提高开发效率 应用场景:日志记录,性能统计,安全控制,事务处理,异常处理 AOP 底 ...

  4. 通过手写Spring MVC来理解其原理

    Spring MVC简单说,是Spring生态圈里的一个优秀的MVC框架,也可以认为是一个增强型的servlet(核心是DispatcherServlet,配置在web.xml中). Spring M ...

  5. 20分钟教你手写Sping MVC

    注意:前方高能 彻底理解Spring MVC看这一篇就够了 三层架构 我们的开发架构一般都是基于两种形式,一种是C/S架构,也就是客户端/服务器,另一种是B/S架构,也就 是浏览器服务器.在 Java ...

  6. 十年java架构师分享:我是这样手写Spring的

    人见人爱的 Spring 已然不仅仅只是一个框架了.如今,Spring 已然成为了一个生态.但深入了解 Spring 的却寥寥无几.这里,我带大家一起来看看,我是如何手写 Spring 的.我将结合对 ...

  7. 30个类仿真手写spring框架V2.0版本

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  8. 一步一步手绘Spring MVC运行时序图(Spring MVC原理)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  9. 05. 手写Spring核心框架

    目录 05 手写Spring核心框架 Pt1 手写IoC/DI Pt1.1 流程设计 Pt1.2 基础配置 application.properties pom.xml web.xml Pt1.3 注 ...

最新文章

  1. jupyter写python的时候换行总是换到下一行的中间
  2. DIRECTDRAW 1:创建一个简单的DIRECTDRAW程序
  3. Python 错误和异常小结
  4. 5 java中的集合类_java基础(5)-集合类1
  5. poj 2418 Hardwood Species
  6. java final修饰的类_java final修饰符使用总结,final修饰类详解
  7. 第二弹:超全Python学习资源整理(进阶系列)
  8. centos7 yum安装maven_Centos7.3安装Maven私服nexus-3.x
  9. arm linux 进程页表,linux-kernel – ARM Linux页表项格式 – 未使用的位?
  10. IDEA 底部工具栏没有 Version Control 解决办法
  11. 【渝粤教育】电大中专跨境电子商务理论与实务 (28)作业 题库
  12. es6 遍历数组对象获取所有的id_ES6对象遍历Object.keys()方法
  13. 一步一步教你Pycharm的配置Python环境
  14. Ubuntu 20.04更新源报错W: 校验数字签名时出错。此仓库未被更新,所以仍然使用此前的索引文件。
  15. es配置中文和拼音分词器
  16. mysql如何使用多核cpu_利用多核 CPU 实现并行计算
  17. 微信公众号三方平台开发【代微信公众号接收消息事件并响应】
  18. 复变函数 —— 0. 连接复数与三角函数的欧拉公式
  19. Mysql之using用法
  20. 《JavaScript高级编程》HTML中的JavaScript

热门文章

  1. 【图像去噪】基于matlab快速跨尺度小波降噪泊松损坏图像去噪【含Matlab源码 1893期】
  2. Flink-时间语义以及WaterMark
  3. 拯救者y7000笔记本VMware虚拟机打开蓝屏问题
  4. ByteBridge数据采集服务:图片采集
  5. Java 获取近七天、近六个月(包含今天,本月)
  6. Speech两种使用方法
  7. manjaro双系统 windows_win10环境下安装manjaro kde(双系统)
  8. Radware研究发现,数据泄露将成为最大的网络攻击问题
  9. Word插入的分节符(下一页)自动变为分节符(连续)的解决办法
  10. 太卷了,详情页做这样,你让我怎么找工作?