文章目录

  • 一,环境准备
  • 二,项目结构搭建
  • 三,简易版的前端控制器 DnDispatcherServlet
  • 四,测试springmvc的性能
  • 五,结束语

首先贴出来一张从网上copy下来的 springmvc工作原理图

分析:其实 springmvc只不过是把 servlet进行了封装和处理,下面就开始手写一个简易版的 springmvc框架。另外,客户端发送一个请求到前端控制器 DispatcherServlet,所谓的前端控制器只是封装后的一个 servlet,这个 servlet接收到请求后在初始化 init()方法中要做下面几件事情。

  1. 扫描所有的类
  2. 反射实例化controller、service类,放入ioc容器
  3. 注入autowired处理
  4. 请求路径映射处理

一,环境准备

  1. eclipse
  2. servlet-api.jar
  3. tomcat

二,项目结构搭建

在写之前要把准备工作都做好,项目结构如下图

com.qianlong.zhujie包中要把springmvc框架中的常用注解都写进去,当然是我们的自定义注解了,山寨版的嘛!
DnAutowired注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.FIELD)//限定作用域,限制自定义的注解只能放在属性上面
public @interface DnAutowired {String value() default "";
}

DnController注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.TYPE)//限定作用域,限制自定义的注解只能放在类上面
public @interface DnController {String value() default "";
}

DnRequestMapping注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target({ElementType.TYPE,ElementType.METHOD})//限定作用域,放在类上面和方法上面
public @interface DnRequestMapping {String value() default "";
}

DnRequestParam注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.PARAMETER)//限定作用域,限制自定义的注解只能放在参数上面
public @interface DnRequestParam {String value() default "";
}

DnService注解

@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.TYPE)//限定作用域,限制自定义的注解只能放在类上面
public @interface DnService {String value() default "";
}

三,简易版的前端控制器 DnDispatcherServlet

com.qianlong.servlet包里新建servlet文件名为DnDispatcherServlet并继承HttpServlet,也就是前端控制器。
然后在pom.xml文件中加入servlet配置,加入配置后,tomcat一启动就进到了这个servlet

<servlet><servlet-name>DnDispathcherServlet</servlet-name><servlet-class>com.qianlong.servlet.DnDispathcherServlet</servlet-class><load-on-startup>1</load-on-startup><!--tomcat已启动首先进入这个servlet--></servlet><servlet-mapping><servlet-name>DnDispathcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping>

然后开始写DnDispatcherServlet的内容。
init()方法中写准备工作,在doPost()方法中处理客户端发来的请求。

package com.qianlong.servlet;
import com.qianlong.controller.TestController;
import com.qianlong.zhujie.*;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class DnDispathcherServlet extends HttpServlet {//扫描到的类的容器private List<String> classNames=new ArrayList<String>();//ioc容器,其实就是一个map集合private Map<String,Object> beans=new HashMap<String, Object>();//存放urlMapping的集合private Map<String,Object> urlMapping=new HashMap<String, Object>();@Overridepublic void init() throws ServletException {//1,扫描类doScanPackage("com.qianlong");//2,反射实例化controller、service类,放入ioc容器doInstance();//3,注入autowireddoAutowired();//4,请求路径映射doUrlMapping();}/*** 请求路径映射*/private void doUrlMapping() {try{//另一种遍历map的方式for(Map.Entry<String,Object> entry:beans.entrySet()){//从ioc容器beans中拿到实例化的controller类和service类的对象Object instance = entry.getValue();//实例化的对象得到类Class<?> clazz = instance.getClass();//如果该类被DnController注解if(clazz.isAnnotationPresent(DnController.class)){//得到DnRequestMapping注解在方法上的映射值DnRequestMapping classAnnotation=clazz.getAnnotation(DnRequestMapping.class);String classPath = classAnnotation.value();//得到该类中所有的方法Method[] methods = clazz.getDeclaredMethods();//遍历所有方法for(Method method:methods){//得到所有方法上面DnRequestMapping注解的映射值DnRequestMapping methodAnnotation=method.getAnnotation(DnRequestMapping.class);String methodPath = methodAnnotation.value();//把映射拼接到一起放到urlMapping集合里urlMapping.put(classPath+methodPath,method);}}}}catch (Exception e){}}/*** 注入autowired*/private void doAutowired() {try {//另一种遍历map的方式for(Map.Entry<String,Object> entry:beans.entrySet()){//从ioc容器beans中拿到实例化的controller类和service类的对象Object instance = entry.getValue();//实例化的对象得到类Class<?> clazz = instance.getClass();//如果该类被DnController注解if(clazz.isAnnotationPresent(DnController.class)){//得到该类中所有的属性Field[] fields = clazz.getDeclaredFields();for(Field field:fields){//如果该属性被DnAutowired注解if(field.isAnnotationPresent(DnAutowired.class)){//拿到这个注解的值DnAutowired annotation = field.getAnnotation(DnAutowired.class);String key=annotation.value();//私有属性强制控制field.setAccessible(true);//给属性赋值,set的两个参数(本属性属于哪个类,值)field.set(instance,beans.get(key));}}}}}catch (Exception e){}}/*** 反射实例化controller、service类,放入ioc容器*/private void doInstance(){try{for(String className:classNames){//拿到类的全路径名,去掉后缀名String cn = className.replace(".class", "");//得到类Class<?> clazz = Class.forName(cn);//实例化controller类和service类//如果该类被@DnController注解了if(clazz.isAnnotationPresent(DnController.class)){//实例化(创建此 Class 对象所表示的类的一个新实例)Object controllerInstance = clazz.newInstance();//拿到@DnRequestMapping注解的值DnRequestMapping annotation=clazz.getAnnotation(DnRequestMapping.class);String key = annotation.value();//放到ioc容器中beans.put(key,controllerInstance);//如果该类被@DnService注解了}else if(clazz.isAnnotationPresent(DnService.class)){//实例化Object serviceInstance = clazz.newInstance();//拿到@DnService注解的值DnService annotation=clazz.getAnnotation(DnService.class);String key = annotation.value();//放到ioc容器中beans.put(key,serviceInstance);}}}catch (Exception e){}}/*** 扫描类* @param packages*/private void doScanPackage(String packages) {//拿到包路径 ,path以'/'开头时,则是从项目的ClassPath根下获取资源,返回一个urlURL url = this.getClass().getClassLoader().getResource("/" + packages.replaceAll("\\.", "/"));//获取此 URL 的文件名(这里是包名com.qianlong)String files = url.getFile();//创建流对象File fileAll=new File(files);//对该包下的文件判断for(File file:fileAll.listFiles()){if(file.isDirectory()){//递归扫描doScanPackage(packages+"."+file.getName());}else{//找到class类并且保存classNames.add(packages+"."+file.getName());//包名+全类路径名 com.qianlong.controller}}}/*** 接收前台请求,对请求进行处理*/protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {try{// 得到的路径是 /HandToSpringMvc/test/firstString uri=request.getRequestURI();//  /HandToSpringMvcString context=request.getContextPath();//  /test/firstString path = uri.replaceAll(context, "");//得到方法Method method = (Method) urlMapping.get(path);TestController instance=(TestController) beans.get("/"+path.split("/")[1]);//   /test//进行参数处理Object[] args=hand(request,response,method);/*** invoke调用方法,参数(该方法属于哪个类,参数)* jdkAPI解释:* 如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。 * 如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。 */method.invoke(instance,args);}catch (Exception e){}}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doPost(request,response);}/*** 获取参数的方法*/public static Object[] hand(HttpServletRequest request,HttpServletResponse response,Method method){//拿到当前待执行的方法有哪些参数Class<?>[] paramClazzes = method.getParameterTypes();//根据参数的个数,new一个参数的数组,将方法里的全部参数赋值到args来Object[] args=new Object[paramClazzes.length];int index=0;for(Class<?> paramClazz:paramClazzes){//isAssignableFrom:判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,如果是则返回 true,否则返回 falseif(ServletRequest.class.isAssignableFrom(paramClazz)){args[index]=request;}//同上if(ServletResponse.class.isAssignableFrom(paramClazz)){args[index]=response;}//判断有没有RequestParam注解,需要获取参数值Annotation[] paramAns = method.getParameterAnnotations()[index];if(paramAns.length>0){for(Annotation annotation:paramAns){if(DnRequestParam.class.isAssignableFrom(paramAns.getClass())){DnRequestParam rp=(DnRequestParam)annotation;args[index]=request.getParameter(rp.value());}}}index++;}return args;}
}

然后山寨版的springmvc框架就诞生了!

四,测试springmvc的性能

写一个TestService接口

public interface TestService {void say();
}

TestServiceImpl实现类

@DnService("haha")
public class TestServiceImpl implements TestService{@Overridepublic void say() {System.out.println("手写springmvc框架实现");}
}

TestController

@DnController
@DnRequestMapping("/test")
public class TestController {@DnAutowired("haha")TestService testService;@DnRequestMapping("/first")public String query(HttpServletRequest request,HttpServletResponse response) throws IOException{Person person=new Person();String s="我叫"+person.getName()+","+"住在"+person.getAddress();response.setHeader("content-type","text/html;charset=utf-8");response.getWriter().write(s);testService.say();return "";}
}

然后把项目添加到tomcat容器并运行
浏览器:

控制台:

五,结束语

springmvc框架,为什么叫框架,而不是叫插件,鬼都知道一个框架包含的内容简直不要太多,像视图解析器等等五大组件。而上面写的简易版springmvc框架只是还原了它的基础用法而已,所以,对于我们这些对底层原理感兴趣的兄弟盟还是要加油,多看看源码总没错!

手写一个山寨版的springmvc框架相关推荐

  1. 仿照源码,手写一个自定义 Spring MVC 框架

    毫无疑问,Spring 框架目前已经成为 Java 开发的行业标准,Spring MVC 作为其 Web 解决方案,是所有 Java 开发者都必须掌握的基本技能,理解其底层原理,才能更好地应用它进行实 ...

  2. 如何手写一个简单的RPC框架

    http://www.czhenblog.cn/article?articleId=29

  3. zookeeper springboot_摊牌了!我要手写一个“Spring Boot”

    ❝ 目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https:// ...

  4. 摊牌了!我要手写一个“Spring Boot”

    目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https://gi ...

  5. 从 0 开始手写一个 Spring MVC 框架,向高手进阶

    转载自   从 0 开始手写一个 Spring MVC 框架,向高手进阶 Spring框架对于Java后端程序员来说再熟悉不过了,以前只知道它用的反射实现的,但了解之后才知道有很多巧妙的设计在里面.如 ...

  6. 自己手写一个Spring MVC框架

    想要了解Spring MVC框架的原理,探究框架是如何设计的,不错的学习方式是阅读源码,然后自己手写一个框架.本文带领大家简化的手写一个Spring MVC框架. Spring框架对于Java后端程序 ...

  7. 【RPC框架、RPC框架必会的基本知识、手写一个RPC框架案例、优秀的RPC框架Dubbo、Dubbo和SpringCloud框架比较】

    一.RPC框架必会的基本知识 1.1 什么是RPC? RPC(Remote Procedure Call --远程过程调用),它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络的技术. ...

  8. 肝一波 ~ 手写一个简易版的Mybatis,带你深入领略它的魅力!

    零.准备工作 <dependencies><dependency><groupId>mysql</groupId><artifactId>m ...

  9. dueros模拟测试没有请求后台_实战 | 用手写一个骚气的请求合并,演绎底层的真实...

    来源:公众号[ java进阶架构师] 好文推荐: 字节跳动Java岗4面面经分享:索弓|+rabbitmq+spring+Redis 拼多多面经Java开发3面面经:准备好久没想到面试题超级简单 网易 ...

最新文章

  1. nginx+FastCGI到底是谁影响超时时间
  2. 网站内链为什么要做上下文链接?对网站优化有什么帮助?
  3. Rxjava使用四部曲 + 相应衍生
  4. 15 个变量和方法命名的最佳实践
  5. Android上玩玩Hook?
  6. AI加持的竖屏沉浸播放新体验
  7. 西瓜书学习记录-决策树(第四章)
  8. JavaFX逆运动学库2.0
  9. 2017-02-26,周日整理
  10. Hyper-V里安装Linux虚机
  11. LINUX下设置定时运行PERL脚本
  12. 6款良心本地视频播放器,功能强大还完全免费
  13. 使用UMI仿今日头条首页
  14. NOIP2018普及赛后总结
  15. 删除windows下一些无法删除的文件夹
  16. 支付宝钱包接口开发包2.0标准版接入与使用规则
  17. 【笔记】LaTeX数学公式
  18. 在 Ubuntu 20.04 LTS 桌面版上安装 MS 字体
  19. 使用一个线性回归模型来拟合身高-体重的数据
  20. 小程序与小程序之间相互跳转、传值、接收参数、navigator、navigateToMiniProgram

热门文章

  1. 沁园春计算机,借助电脑软件教学生写“沁园春”
  2. 中国IT运维O2O市场发展研究及十四五前景规划分析报告2022-2027年
  3. phpmailer 私密抄送_PHPMailer使用说明
  4. 跨平台工程移植:opencv_pnp + artoolkitplus :ubuntu qtcreator-- windows qtcreator/ VS
  5. 使用宝塔面板搭建jumpserver开源堡垒机
  6. 全球首个5G全覆盖国家诞生,华为成幕后最大功臣!
  7. 极域2015,学生连不上教机,广播黑屏
  8. 怎么把cad转成jpg高清图片?
  9. HR8833 替换DRV8833 H桥电机驱动IC
  10. cesium 实战记录(六)地图通用工具方法的封装