手写简易SpringMVC


手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器

必备知识:

Servlet相关理解和使用,Maven,Java 反射,Java自定义注解

配置Web类型结构

结构如图所示:



注意 要设置 webapp为web moudle -> IDEA 有蓝色小圈圈为准,resource 配置为资源文件

配置Web.xml,配置Artifacts,配置文件



<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"> <servlet>
<servlet-name>KerwinCodes</servlet-name>
<servlet-class>com.mycode.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>KerwinCodes</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
// 配置包扫描的路径
scanPackage=com.mycode

编码阶段

  1. 第一步毋庸置疑,我们需要创建必要的注解,如MyController,MyRequestMapping等等
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController { String value() default "";
} @Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping { String value() default "";
} @Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam { String value();
}
  1. 需要思考我们如何才能实现SpringMVC
A.参考真正的SpringMVC, 它是基于Spring的基础上,因此我们需要自行实现IOC容器
B.想要实现IOC容易,管理Bean,我们就需要根据包扫描的路径进行全项目扫描
C.全项目扫描后,利用反射,同时根据注解判断是否是Bean,然后注入到Map容器中即可 D.遍历容器,获取存储的Bean中的方法,配合RequestMapping注解,得到 url - method映射,同时得到 url - object映射,存储到新的Map集合总,便于后续反射调用 E.页面请求时候,判断request.url 映射的到底是哪一个bean,哪一个方法 同时获取方法的参数,解析request的参数,即可匹配路径调用方法 F.万事俱备,到底如何运行?
Servlet -> init方法,doGet方法,doPost方法 实质就是Servlet生命周期中初始化和真正执行策略的方法,我们只需要重写方法,然后让doGet,doPost 都调用我们的方法即可
  1. 核心代码如下:
package com.mycode.servlet;

import com.mycode.annotation.MyController;
import com.mycode.annotation.MyRequestMapping; 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.*;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*; /**
* ******************************
* author: 柯贤铭
* createTime: 2019/9/5 11:53
* description: MyDispatcherServlet
* version: V1.0
* ******************************
*/
public class MyDispatcherServlet extends HttpServlet{ /** 配置信息 **/
private Properties properties = new Properties(); /** 所有类的Class地址 **/
private List<String> classNames = new ArrayList<>(); /** Bean容器 **/
private Map<String, Object> iocFactory = new HashMap<>(); /** HandlerMapping - 方法**/
private Map<String, Method> handleMapping = new HashMap<>(); /** HandlerMapping - 对象**/
private Map<String, Object> controllers = new HashMap<>(); @Override
public void init(ServletConfig config) throws ServletException { // 1.加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation")); // 2.初始化所有相关联的类,扫描用户设定的包下面所有的类
doScanner(properties.getProperty("scanPackage")); // 3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v beanName-bean)
doInstance(); // 4.初始化HandlerMapping(将url和method对应上)
initHandlerMapping();
} @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 {
try {
doDispatch(req, resp);
} catch (Exception e) {
e.printStackTrace();
}
} // 加载配置文件索取包扫描路径
private void doLoadConfig (String fileUrl) {
try {
properties.load(this.getClass().getClassLoader().getResourceAsStream(fileUrl));
} catch (IOException e) {
e.printStackTrace();
}
} // 扫描目录下所有的类
private void doScanner (String rootPath) throws ServletException {
URL url = this.getClass().getClassLoader().getResource( "/" + rootPath.replaceAll("\\.", "/"));
File file = new File(Objects.requireNonNull(url).getFile());
if (!file.isDirectory()) {
throw new ServletException("Base Package is wrong.");
} for (File current : Objects.requireNonNull(file.listFiles())) {
if (current.isDirectory()) {
doScanner(rootPath + "." + current.getName());
} else {
String className = rootPath + "." + current.getName().replace(".class", "");
classNames.add(className);
}
}
} // 拿到所有的classNames 通过反射创建其对象 - 放入ioc容器中
private void doInstance () {
if (classNames.isEmpty()) {
return;
} try {
for (String className : classNames) {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyController.class)) {
iocFactory.put(clazz.getSimpleName(), clazz.newInstance());
}
}
} catch (Exception e) {
e.printStackTrace();
}
} // 初始化HandlerMapping(将url和method对应上)
private void initHandlerMapping () {
if (iocFactory.isEmpty()) {
return;
} for (String key : iocFactory.keySet()) {
Class<? extends Object> clazz = iocFactory.get(key).getClass();
if (!clazz.isAnnotationPresent(MyController.class)) {
continue;
} // 类 url
MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
String baseUrl = annotation.value(); Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MyRequestMapping.class)) {
String mappingUrl = method.getAnnotation(MyRequestMapping.class).value(); // 获取匹配方法及对象 方便之后通过反射调用
handleMapping.put(baseUrl + mappingUrl, method);
controllers.put(baseUrl + mappingUrl, iocFactory.get(key));
}
}
}
} // 中央处理器
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (iocFactory.isEmpty() || handleMapping.isEmpty() || controllers.isEmpty()) {
return;
} String url = request.getRequestURI(); // 如果不存在url
if (!handleMapping.containsKey(url)) {
response.getWriter().write("Do Not Get Url : 404 ERROR");
return;
} // HandleMapping 的方法
Method method = handleMapping.get(url); // 获取方法的参数列表
Class<?>[] parameterTypes = method.getParameterTypes(); //获取请求的参数
Map<String, String[]> parameterMap = request.getParameterMap(); //保存参数值
Object [] paramValues= new Object[parameterTypes.length]; // 方法的参数列表
for (int i = 0; i< parameterTypes.length; i++){
//根据参数名称,做某些处理
String requestParam = parameterTypes[i].getSimpleName(); if (requestParam.equals("HttpServletRequest")){
//参数类型已明确,这边强转类型
paramValues[i] = request;
continue;
}
if (requestParam.equals("HttpServletResponse")){
paramValues[i] = response;
continue;
}
if(requestParam.equals("String")){
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
paramValues[i] = value;
}
}
} method.invoke(controllers.get(url), paramValues);
}
}
  1. 测试代码:
@MyController
@MyRequestMapping("/test")
public class TestController { @MyRequestMapping("/doTest")
public void test1 ( HttpServletRequest request, HttpServletResponse response,
@MyRequestParam("param") String param){
System.out.println(param);
try {
response.getWriter().write( "doTest method success! param:"+param);
} catch (IOException e) {
e.printStackTrace();
}
} @MyRequestMapping("/doTest2")
public void test2(HttpServletRequest request, HttpServletResponse response){
try {
response.getWriter().println("doTest2 method success!");
} catch (IOException e) {
e.printStackTrace();
}
}
}

测试结果:

http://localhost:8080/ -> Do Not Get Url : 404 ERROR

http://localhost:8080/test/doTest2 -> doTest2 method success!

http://localhost:8080/test/doTest?param=asdasdad -> doTest method success! param:asdasdad

源码地址:https://github.com/kkzhilu/KerwinCodes code_springmvc分支

手写简易SpringMVC的更多相关文章

  1. JDK动态代理深入理解分析并手写简易JDK动态代理(下)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...

  2. JDK动态代理深入理解分析并手写简易JDK动态代理(上)

    原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...

  3. 【教程】手写简易web服务器

    package com.littlepage.testjdbc; import java.io.BufferedReader; import java.io.FileReader; import ja ...

  4. 看年薪50W的架构师如何手写一个SpringMVC框架

    前言 做 Java Web 开发的你,一定听说过SpringMVC的大名,作为现在运用最广泛的Java框架,它到目前为止依然保持着强大的活力和广泛的用户群. 本文介绍如何用eclipse一步一步搭建S ...

  5. 手写简易的Mybatis

    手写简易的Mybatis 此篇文章用来记录今天花个五个小时写出来的简易版mybatis,主要实现了基于注解方式的增删查改,目前支持List,Object类型的查找,参数都是基于Map集合的,可以先看一 ...

  6. Spring系列之手写一个SpringMVC

    目录 Spring系列之IOC的原理及手动实现 Spring系列之DI的原理及手动实现 Spring系列之AOP的原理及手动实现 Spring系列之手写注解与配置文件的解析 引言 在前面的几个章节中我 ...

  7. Java多线程之Executor框架和手写简易的线程池

    目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...

  8. 自己手写一个SpringMVC 框架

    一.了解SpringMVC运行流程及九大组件 1.SpringMVC 的运行流程   · 用户发送请求至前端控制器DispatcherServlet · DispatcherServlet收到请求调用 ...

  9. 手写简易WEB服务器

    今天我们来写一个类似于Tomcat的简易服务器.可供大家深入理解一下tomcat的工作原理,本文仅供新手参考,请各位大神指正!首先我们要准备的知识是: Socket编程 HTML HTTP协议 服务器 ...

随机推荐

  1. cb46a_c++_STL_算法_逆转和旋转reverse_rotate函数advance

    cb46a_c++_STL_算法_逆转和旋转reverse_rotateSTL算法--变序性算法reverse() 逆转reverse_copy()一边复制一般逆转rotate()旋转,某个位置开始前 ...

  2. cb33a_c++_STL_算法_查找算法_(6)binary_search_includes

    cb33a_c++_STL_算法_查找算法_(6)binary_search_includes//针对已序区间的查找算法,如set,multiset关联容器-自动排序binary_search(b,e ...

  3. 微信小程序入门-刘志敏-专题视频课程

    微信小程序入门-269人已学习 课程介绍        微信小程序入门基础,给入门级程序员好的教程.教程中对小程序的介绍到小程序的基本使用都做了详细的介绍,教程以实用的实现作为案例,如列表下拉刷新.抽 ...

  4. yum 安装JDK

    系统:CentOS 7 查看当前系统是否已安装JDK yum list installed |grep java 如果没有就选择yum库中的包进行安装,查看yum库中JDK列表 yum -y list ...

  5. 深拷贝和浅拷贝以及void里的return用法

    Object o1=new Object(); Object o2; int i1=3,i2; 浅拷贝 o2=o1;i2=i1; 深拷贝 o2=new Object();o2=o1.clone(); ...

  6. Java的前生今世

    Java作为一门编程语言,自诞生以来已经流行了20多年,在学习它之前,我们有必要先了解一下它的历史,了解它是如何一步步发展到今天这个样子. 孕育 上世纪90年代,硬件领域出现了单片式计算机系统,比如电 ...

  7. Python实用笔记——错误处理

    让我们用一个例子来看看try的机制: try: print('try...') r = 10 / 0 print('result:', r) except ZeroDivisionError as e ...

  8. Windows Defender might be impacting your build performance

    由于换了SSD, 昨天安装了最新的 Idea 2019.2+ , 然后发现每次导入项目都有如下提示: 处理方法就是在Windows安全中心排除目录 处理方式参考: 官方 Known issues An ...

  9. 洛谷 P3243 【[HNOI2015]菜肴制作】

    先吐槽一下这个难度吧,评的有点高了,但是希望别降,毕竟这是我能做出来的不多的紫题了(狗头). 大家上来的第一反应应该都是啊,模板题,然后兴高采烈的打了拓补排序的板子,然后搞个小根堆,按照字典序输出就可 ...

  10. 洛谷 P3063 【[USACO12DEC]Milk Routing S】

    这道题可以暴力哒~ 我们枚举每一个出现过的容量,然后跑一次最短路,求延迟,在跑最短路的时候,如果遇到的某一个点,比我们当前枚举的那个点小,那么就直接不走这一个点,然后枚举完后,就能得到最大值了. 代码 ...