手写 Spring MVC

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

一、整体思路

思路要熟练背下来

1)配置阶段

  • 配置 web.xml: XDispatchServlet
  • 设定 init-param: contextConfigLocation = classpath:application.xml
  • 设定 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(): 将返回结果输出到浏览器

二、源代码

项目结构:

源代码:

(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>(); @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
} @Override
protected 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) + "}.");
} @Override
public void init(ServletConfig servletConfig) throws ServletException { //1、加载配置文件
doLoadConfig(servletConfig.getInitParameter("contextConfigLocation")); //2、扫描相关的类
doScanner(contextConfig.getProperty("scan-package")); //3、初始化 IOC 容器,将所有相关的类实例保存到 IOC 容器中
doInstance(); //4、依赖注入
doAutowired(); //5、初始化 HandlerMapping
initHandlerMapping(); 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 还是 protect
field.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 { @XAutowired
ITestXService 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 { @Override
public String listClassName() { // 假装来自数据库
return "123456TestXServiceImpl";
}
}

(12)测试:

配置 Tomcat 后,访问:

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

http://localhost:8080/test/listClassName

手写 Spring的更多相关文章

  1. 一个老程序员是如何手写Spring MVC的

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

  2. 【Spring】手写Spring MVC

    Spring MVC原理 Spring的MVC框架主要由DispatcherServlet.处理器映射.处理器(控制器).视图解析器.视图组成. 完整的Spring MVC处理 流程如下: Sprin ...

  3. 我是这样手写 Spring 的(麻雀虽小五脏俱全)

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

  4. 《四 spring源码》利用TransactionManager手写spring的aop

    事务控制分类 编程式事务控制          自己手动控制事务,就叫做编程式事务控制. Jdbc代码: Conn.setAutoCommite(false);  // 设置手动控制事务 Hibern ...

  5. 手写Spring框架,加深对Spring工作机制的理解!

    在我们的日常工作中,经常会用到Spring.Spring Boot.Spring Cloud.Struts.Mybatis.Hibernate等开源框架,有了这些框架的诞生,平时的开发工作量也是变得越 ...

  6. 手写Spring+demo+思路

    我在学习Spring的时候,感觉Spring是很难的,通过学习后,发现Spring没有那么难,只有你去学习了,你才会发现,你才会进步 1.手写Spring思路: 分为配置.初始化.运行三个阶段如下图 ...

  7. 我是这样手写Spring的,麻雀虽小五脏俱全

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

  8. Spring事务原理分析--手写Spring事务

    一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...

  9. 手写Spring MVC

    闲及无聊 又打开了CSDN开始看一看有什么先进的可以学习的相关帖子,这时看到了一位大神写的简历装X必备,手写Spring MVC. 我想这个东西还是有一点意思的 就拜读了一下大佬的博客 通读了一遍相关 ...

随机推荐

  1. Linux运维工程师成长路线及应实现的目标

    作为一名运维工程师,需要学习的东西非常多,在学习的过程中也没有任何捷径可言,必须一步一个脚印地学习.积累才能把个人技能提升到相应的高度.根据目前流行的发行版及国际流行的Linux认证,红帽认证和LPI ...

  2. Dependency Injection 筆記 (4)

    续上集未完的相关设计模式... (本文摘自電子書:<.NET 依賴注入> Composite 模式 延续先前的电器比喻.现在,如果希望 UPS 不只接计算机,还要接电风扇.除湿机,可是 U ...

  3. ring3层一种占用文件的方法(DuplicateHandle以后,把占用文件的句柄丢给系统进程,导致被占用)

    前段时间,一个测试工程师问我关于怎样长时间的占用一个文件,而使别的程序无法再访问这个文件,想起以前很多病毒木马经常劫持hosts文件不放,除非你找到占用文件的程序,并强行结束掉,否则怎么也访问不了ho ...

  4. scrapy爬虫框架研究!

    最近由于项目需要,开始研究scrapy爬虫,走过不少弯路,准备写个记录,记下踩过的各种坑.

  5. better later than never

    工作一年半,写程序也有几年了,终于开了个博客,记录一下遇到的问题和解决方法,和其他需要记录的东西.希望自己不会懒……已经开通两周了才开始写,唉……

  6. IT职场初体验一

    自己学习计算机专业也算有两个年头了吧,对于这个刚刚IT入门的菜鸟,对IT职场充满了好奇和憧憬,本人大学也像很多大学生一样,进入计算机专业也不是自己最初想进入的专业,进入这个原本离自己有点遥远的行业,一 ...

  7. abp(net core)+easyui+efcore仓储系统——展现层实现增删改查之控制器(六)

    abp(net core)+easyui+efcore仓储系统目录 abp(net core)+easyui+efcore仓储系统——ABP总体介绍(一) abp(net core)+easyui+e ...

  8. ZooKeeper学习第三期---Zookeeper命令操作(转)

    转载来源:https://www.cnblogs.com/sunddenly/p/4031881.html 一.Zookeeper的四字命令 Zookeeper支持某些特定的四字命令字母与其的交互.他 ...

  9. spring 5.x 系列第2篇 —— springmvc基础 (代码配置方式)

    文章目录 一.搭建hello spring工程 1.1 项目搭建 1.2 相关注解说明 二.配置自定义拦截器 三.全局异常处理 四.参数绑定 4.1 参数绑定 4.2 关于日期格式转换的三种方法 五. ...

  10. CentOS7系统安装

    CenOS7安装系统 镜像下载地址: http://isoredirect.centos.org/centos/7/isos/x86_64/ https://mirrors.aliyun.com/ce ...