手写一个山寨版的springmvc框架

今天药忘吃喽~ 2023-05-31 06:49 77阅读 0赞

文章目录

    • 一,环境准备
    • 二,项目结构搭建
    • 三,简易版的前端控制器 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注解

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

DnController注解

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

DnRequestMapping注解

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

DnRequestParam注解

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

DnService注解

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

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

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

  1. <servlet>
  2. <servlet-name>DnDispathcherServlet</servlet-name>
  3. <servlet-class>com.qianlong.servlet.DnDispathcherServlet</servlet-class>
  4. <load-on-startup>1</load-on-startup><!--tomcat已启动首先进入这个servlet-->
  5. </servlet>
  6. <servlet-mapping>
  7. <servlet-name>DnDispathcherServlet</servlet-name>
  8. <url-pattern>/</url-pattern>
  9. </servlet-mapping>

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

  1. package com.qianlong.servlet;
  2. import com.qianlong.controller.TestController;
  3. import com.qianlong.zhujie.*;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.ServletRequest;
  6. import javax.servlet.ServletResponse;
  7. import javax.servlet.http.HttpServlet;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletResponse;
  10. import java.io.File;
  11. import java.io.IOException;
  12. import java.lang.annotation.Annotation;
  13. import java.lang.reflect.Field;
  14. import java.lang.reflect.Method;
  15. import java.net.URL;
  16. import java.util.ArrayList;
  17. import java.util.HashMap;
  18. import java.util.List;
  19. import java.util.Map;
  20. public class DnDispathcherServlet extends HttpServlet {
  21. //扫描到的类的容器
  22. private List<String> classNames=new ArrayList<String>();
  23. //ioc容器,其实就是一个map集合
  24. private Map<String,Object> beans=new HashMap<String, Object>();
  25. //存放urlMapping的集合
  26. private Map<String,Object> urlMapping=new HashMap<String, Object>();
  27. @Override
  28. public void init() throws ServletException {
  29. //1,扫描类
  30. doScanPackage("com.qianlong");
  31. //2,反射实例化controller、service类,放入ioc容器
  32. doInstance();
  33. //3,注入autowired
  34. doAutowired();
  35. //4,请求路径映射
  36. doUrlMapping();
  37. }
  38. /** * 请求路径映射 */
  39. private void doUrlMapping() {
  40. try{
  41. //另一种遍历map的方式
  42. for(Map.Entry<String,Object> entry:beans.entrySet()){
  43. //从ioc容器beans中拿到实例化的controller类和service类的对象
  44. Object instance = entry.getValue();
  45. //实例化的对象得到类
  46. Class<?> clazz = instance.getClass();
  47. //如果该类被DnController注解
  48. if(clazz.isAnnotationPresent(DnController.class)){
  49. //得到DnRequestMapping注解在方法上的映射值
  50. DnRequestMapping classAnnotation=clazz.getAnnotation(DnRequestMapping.class);
  51. String classPath = classAnnotation.value();
  52. //得到该类中所有的方法
  53. Method[] methods = clazz.getDeclaredMethods();
  54. //遍历所有方法
  55. for(Method method:methods){
  56. //得到所有方法上面DnRequestMapping注解的映射值
  57. DnRequestMapping methodAnnotation=method.getAnnotation(DnRequestMapping.class);
  58. String methodPath = methodAnnotation.value();
  59. //把映射拼接到一起放到urlMapping集合里
  60. urlMapping.put(classPath+methodPath,method);
  61. }
  62. }
  63. }
  64. }catch (Exception e){
  65. }
  66. }
  67. /** * 注入autowired */
  68. private void doAutowired() {
  69. try {
  70. //另一种遍历map的方式
  71. for(Map.Entry<String,Object> entry:beans.entrySet()){
  72. //从ioc容器beans中拿到实例化的controller类和service类的对象
  73. Object instance = entry.getValue();
  74. //实例化的对象得到类
  75. Class<?> clazz = instance.getClass();
  76. //如果该类被DnController注解
  77. if(clazz.isAnnotationPresent(DnController.class)){
  78. //得到该类中所有的属性
  79. Field[] fields = clazz.getDeclaredFields();
  80. for(Field field:fields){
  81. //如果该属性被DnAutowired注解
  82. if(field.isAnnotationPresent(DnAutowired.class)){
  83. //拿到这个注解的值
  84. DnAutowired annotation = field.getAnnotation(DnAutowired.class);
  85. String key=annotation.value();
  86. //私有属性强制控制
  87. field.setAccessible(true);
  88. //给属性赋值,set的两个参数(本属性属于哪个类,值)
  89. field.set(instance,beans.get(key));
  90. }
  91. }
  92. }
  93. }
  94. }catch (Exception e){
  95. }
  96. }
  97. /** * 反射实例化controller、service类,放入ioc容器 */
  98. private void doInstance(){
  99. try{
  100. for(String className:classNames){
  101. //拿到类的全路径名,去掉后缀名
  102. String cn = className.replace(".class", "");
  103. //得到类
  104. Class<?> clazz = Class.forName(cn);
  105. //实例化controller类和service类
  106. //如果该类被@DnController注解了
  107. if(clazz.isAnnotationPresent(DnController.class)){
  108. //实例化(创建此 Class 对象所表示的类的一个新实例)
  109. Object controllerInstance = clazz.newInstance();
  110. //拿到@DnRequestMapping注解的值
  111. DnRequestMapping annotation=clazz.getAnnotation(DnRequestMapping.class);
  112. String key = annotation.value();
  113. //放到ioc容器中
  114. beans.put(key,controllerInstance);
  115. //如果该类被@DnService注解了
  116. }else if(clazz.isAnnotationPresent(DnService.class)){
  117. //实例化
  118. Object serviceInstance = clazz.newInstance();
  119. //拿到@DnService注解的值
  120. DnService annotation=clazz.getAnnotation(DnService.class);
  121. String key = annotation.value();
  122. //放到ioc容器中
  123. beans.put(key,serviceInstance);
  124. }
  125. }
  126. }catch (Exception e){
  127. }
  128. }
  129. /** * 扫描类 * @param packages */
  130. private void doScanPackage(String packages) {
  131. //拿到包路径 ,path以'/'开头时,则是从项目的ClassPath根下获取资源,返回一个url
  132. URL url = this.getClass().getClassLoader().getResource("/" + packages.replaceAll("\\.", "/"));
  133. //获取此 URL 的文件名(这里是包名com.qianlong)
  134. String files = url.getFile();
  135. //创建流对象
  136. File fileAll=new File(files);
  137. //对该包下的文件判断
  138. for(File file:fileAll.listFiles()){
  139. if(file.isDirectory()){
  140. //递归扫描
  141. doScanPackage(packages+"."+file.getName());
  142. }else{
  143. //找到class类并且保存
  144. classNames.add(packages+"."+file.getName());//包名+全类路径名 com.qianlong.controller
  145. }
  146. }
  147. }
  148. /** * 接收前台请求,对请求进行处理 */
  149. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  150. try{
  151. // 得到的路径是 /HandToSpringMvc/test/first
  152. String uri=request.getRequestURI();
  153. // /HandToSpringMvc
  154. String context=request.getContextPath();
  155. // /test/first
  156. String path = uri.replaceAll(context, "");
  157. //得到方法
  158. Method method = (Method) urlMapping.get(path);
  159. TestController instance=(TestController) beans.get("/"+path.split("/")[1]);// /test
  160. //进行参数处理
  161. Object[] args=hand(request,response,method);
  162. /** * invoke调用方法,参数(该方法属于哪个类,参数) * jdkAPI解释: * 如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。 * 如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。 */
  163. method.invoke(instance,args);
  164. }catch (Exception e){
  165. }
  166. }
  167. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  168. doPost(request,response);
  169. }
  170. /** * 获取参数的方法 */
  171. public static Object[] hand(HttpServletRequest request,HttpServletResponse response,Method method){
  172. //拿到当前待执行的方法有哪些参数
  173. Class<?>[] paramClazzes = method.getParameterTypes();
  174. //根据参数的个数,new一个参数的数组,将方法里的全部参数赋值到args来
  175. Object[] args=new Object[paramClazzes.length];
  176. int index=0;
  177. for(Class<?> paramClazz:paramClazzes){
  178. //isAssignableFrom:判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,如果是则返回 true,否则返回 false
  179. if(ServletRequest.class.isAssignableFrom(paramClazz)){
  180. args[index]=request;
  181. }
  182. //同上
  183. if(ServletResponse.class.isAssignableFrom(paramClazz)){
  184. args[index]=response;
  185. }
  186. //判断有没有RequestParam注解,需要获取参数值
  187. Annotation[] paramAns = method.getParameterAnnotations()[index];
  188. if(paramAns.length>0){
  189. for(Annotation annotation:paramAns){
  190. if(DnRequestParam.class.isAssignableFrom(paramAns.getClass())){
  191. DnRequestParam rp=(DnRequestParam)annotation;
  192. args[index]=request.getParameter(rp.value());
  193. }
  194. }
  195. }
  196. index++;
  197. }
  198. return args;
  199. }
  200. }

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

四,测试springmvc的性能

写一个TestService接口

  1. public interface TestService {
  2. void say();
  3. }

TestServiceImpl实现类

  1. @DnService("haha")
  2. public class TestServiceImpl implements TestService{
  3. @Override
  4. public void say() {
  5. System.out.println("手写springmvc框架实现");
  6. }
  7. }

TestController

  1. @DnController
  2. @DnRequestMapping("/test")
  3. public class TestController {
  4. @DnAutowired("haha")
  5. TestService testService;
  6. @DnRequestMapping("/first")
  7. public String query(HttpServletRequest request,HttpServletResponse response) throws IOException{
  8. Person person=new Person();
  9. String s="我叫"+person.getName()+","+"住在"+person.getAddress();
  10. response.setHeader("content-type","text/html;charset=utf-8");
  11. response.getWriter().write(s);
  12. testService.say();
  13. return "";
  14. }
  15. }

然后把项目添加到tomcat容器并运行
浏览器:
在这里插入图片描述
控制台:
在这里插入图片描述

五,结束语

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

发表评论

表情:
评论列表 (有 0 条评论,77人围观)

还没有评论,来说两句吧...

相关阅读

    相关 迷你SpringMVC框架

    前言 本文总结自慕课网同名课程。 学习如何使用Spring,SpringMVC是很快的,但是在往后使用的过程中难免会想探究一下框架背后的原理是什么,本文将通过讲解如何手

    相关 SpringMVC框架

    引言 在分析springMVC框架之前,我们根据我们对整个框架的流程分析,先来手写一个简易版的springMVC框架, 这样我们在看源码的时候会更清晰,毕竟框架源码还是非