一 写在前面

这是自己实现一个简单的具有SpringMVC功能的小Demo,主要实现效果是;

自己定义的实现效果是通过浏览器地址传一个name参数,打印“my name is”+name参数。不使用SpringMVC,自己定义部分注解,实现DispatcherServlet核心功能,通过这个demo可以加深自己对源码的理解。

先看一下实现效果:

(传入了参数时)

(没有传入参数时)

二  DispatcherServlet流程

  1. 加载配置文件
  2. 扫描所有相关类
  3. 初始化所有相关的类
  4. 自动注入
  5. 初始化HandlerMapping
  6. 等待请求

三 代码回顾

1.首先来看一下Pom文件的依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>javax.servlet</groupId>
  4. <artifactId>servlet-api</artifactId>
  5. <version>2.5</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.apache.commons</groupId>
  9. <artifactId>commons-lang3</artifactId>
  10. <version>3.10</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.projectlombok</groupId>
  14. <artifactId>lombok</artifactId>
  15. <version>1.18.12</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>ch.qos.logback</groupId>
  19. <artifactId>logback-core</artifactId>
  20. <version>1.2.3</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>ch.qos.logback</groupId>
  24. <artifactId>logback-classic</artifactId>
  25. <version>1.2.3</version>
  26. </dependency>
  27. </dependencies>

依赖比较少,没有spring的依赖,主要就是一个servlet的。

2. 配置文件:

2.1. application.properties文件:

  1. scanPackage=com.qunar.framework.demo

这是说明要扫描的位置。

2.2. web.xml文件:

  1. <!DOCTYPE web-app PUBLIC
  2. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  3. "http://java.sun.com/dtd/web-app_2_3.dtd" >
  4.  
  5. <web-app>
  6. <display-name>MySpringMVC</display-name>
  7. <servlet>
  8. <servlet-name>mvc</servlet-name>
  9. <servlet-class>com.qunar.framework.webmvc.DispatcherServlet</servlet-class>
  10. <init-param>
  11. <param-name>contextConfigLocation</param-name>
  12. <param-value>/application.properties</param-value>
  13. </init-param>
  14. <load-on-startup>1</load-on-startup>
  15. </servlet>
  16. <servlet-mapping>
  17. <servlet-name>mvc</servlet-name>
  18. <url-pattern>/*</url-pattern>
  19. </servlet-mapping>
  20. </web-app>

3. 下面是整个工程的目录结构:

4. 自定义注解:

@Controller:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Controller {
  5. String value() default "";
  6. }

@Service:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Service {
  5. String value() default "";
  6. }

@AutoWired:

  1. @Target(ElementType.FIELD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Autowired {
  5. String value() default "";
  6. }

@RequestMapping:

  1. @Target(ElementType.FIELD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Autowired {
  5. String value() default "";
  6. }

@RequestParam:

  1. @Target(ElementType.PARAMETER)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface RequestParam {
  5. String value() default "";
  6. }

5.自己封装的Handler:

  1. public class Handler {
  2. protected Object controller;
  3. protected Method method;
  4. protected Pattern pattern;
  5. protected Map<String,Integer> paramIndexMap;
  6.  
  7. public Handler(Object controller, Method method, Pattern pattern) {
  8. this.controller = controller;
  9. this.method = method;
  10. this.pattern = pattern;
  11. this.paramIndexMap = new HashMap<>();
  12. putParamIndexMapping(method);
  13. }
  14.  
  15. private void putParamIndexMapping(Method method) {
  16. //获取方法中加了注解的参数
  17. Annotation[][] annotations = method.getParameterAnnotations();
  18. for (int i =0; i < annotations.length;i++){
  19. for (Annotation annotation : annotations[i]){
  20. if (annotation instanceof RequestParam){
  21. String paramName = ((RequestParam) annotation).value();
  22. if (!StringUtils.isBlank(paramName)){
  23. paramIndexMap.put(paramName,i);
  24. }
  25. }
  26. }
  27. }
  28. //获取方法中的我request和response的参数
  29. Class<?>[] paramTypes = method.getParameterTypes();
  30. for (int i = 0; i < paramTypes.length; i++){
  31. Class<?> paramType = paramTypes[i];
  32. if (paramType == HttpServletRequest.class || paramType == HttpServletResponse.class){
  33. paramIndexMap.put(paramType.getName(),i);
  34. }
  35. }
  36. }
  37. }

6. 自己封装的DispatcherServlet:

  1. @Slf4j
  2. public class DispatcherServlet extends HttpServlet {
  3. private static final long serialVersionUID = 1L;
  4. private Properties contextConfig = new Properties();
  5. private List<String> classNames = new ArrayList<>();
  6. private Map<String, Object> iocMap = new HashMap<>();
  7. private List<Handler> handlerMapping = new ArrayList<>();
  8.  
  9. @Override
  10. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  11. this.doPost(req, resp);
  12. }
  13.  
  14. @Override
  15. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  16. //等待请求
  17. try {
  18. doDispatch(req, resp);
  19. } catch (Exception exception) {
  20. resp.getWriter().write("500 Exception");
  21. log.error("500 Exception. Cause: {}", exception.getMessage());
  22. exception.printStackTrace();
  23. }
  24. }
  25.  
  26. private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
  27. Handler handler = getHandler(req);
  28. if (handler == null) {
  29. //没有匹配上,404
  30. log.info("404 Not Found");
  31. resp.getWriter().write("404 Not Found");
  32. return;
  33. }
  34. //获取参数列表
  35. Class<?>[] parameterTypes = handler.method.getParameterTypes();
  36. //保存所有需要自动赋值的参数值
  37. Object[] parameterValues = new Object[parameterTypes.length];
  38.  
  39. Map<String, String[]> parameterMap = req.getParameterMap();
  40. for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
  41. String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll("/+", "/");
  42. log.info(value);
  43. //如果找到了匹配的值,就填充
  44. if (!handler.paramIndexMap.containsKey(entry.getKey())) {
  45. continue;
  46. }
  47. Integer index = handler.paramIndexMap.get(entry.getKey());
  48. parameterValues[index] = convert(parameterTypes[index], value);
  49. }
  50. //设置方法中的request对象和response对象
  51. Integer reqIndex = handler.paramIndexMap.get(HttpServletRequest.class.getName());
  52. Integer respIndex = handler.paramIndexMap.get(HttpServletResponse.class.getName());
  53. parameterValues[reqIndex] = req;
  54. parameterValues[respIndex] = resp;
  55. handler.method.invoke(handler.controller, parameterValues);
  56. }
  57.  
  58. private Object convert(Class<?> parameterType, String value) {
  59. if (parameterType == Integer.class) {
  60. return Integer.valueOf(value);
  61. }
  62. return value;
  63. }
  64.  
  65. private Handler getHandler(HttpServletRequest req) {
  66. if (handlerMapping.isEmpty()) {
  67. return null;
  68. }
  69. String requestURI = req.getRequestURI();
  70. String contextPath = req.getContextPath();
  71. requestURI = requestURI.replace(contextPath, "").replaceAll("/+", "/");
  72. for (Handler handler : handlerMapping) {
  73. Matcher matcher = handler.pattern.matcher(requestURI);
  74. if (!matcher.matches()) {
  75. continue;
  76. }
  77. return handler;
  78. }
  79. return null;
  80. }
  81.  
  82. @Override
  83. public void init(ServletConfig config) {
  84. //从这里开始启动:
  85. //加载配置文件
  86. loadConfig(config.getInitParameter("contextConfigLocation"));
  87. //扫描相关类
  88. doScanner(contextConfig.getProperty("scanPackage"));
  89. //初始化相关类
  90. try {
  91. doInstance();
  92. } catch (Exception exception) {
  93. log.error("Execute doInstance method fail.");
  94. exception.printStackTrace();
  95. }
  96. //自动注入
  97. doAutowired();
  98. //初始化HandlerMapping
  99. initHandlerMapping();
  100. }
  101.  
  102. private void initHandlerMapping() {
  103. if (iocMap.isEmpty()) {
  104. return;
  105. }
  106. for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
  107. Class<?> clazz = entry.getValue().getClass();
  108. if (!clazz.isAnnotationPresent(Controller.class)) {
  109. continue;
  110. }
  111. String baseUrl = "";
  112. if (clazz.isAnnotationPresent(RequestMapping.class)) {
  113. RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
  114. baseUrl = requestMapping.value();
  115. }
  116. //扫描所有的公共方法
  117. for (Method method : clazz.getMethods()) {
  118. if (!method.isAnnotationPresent(RequestMapping.class)) {
  119. continue;
  120. }
  121. RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
  122. String regex = ("/" + baseUrl + requestMapping.value()).replaceAll("/+", "/");
  123. Pattern pattern = Pattern.compile(regex);
  124. handlerMapping.add(new Handler(entry.getValue(), method, pattern));
  125. log.info("Mapping: {}.{}", regex, method);
  126. }
  127. }
  128. }
  129.  
  130. private void doAutowired() {
  131. if (iocMap.isEmpty()) {
  132. return;
  133. }
  134. //循环所有的类,对需要自动赋值的属性进行赋值
  135. for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
  136. Field[] fields = entry.getValue().getClass().getDeclaredFields();
  137. for (Field field : fields) {
  138. if (!field.isAnnotationPresent(Autowired.class)) {
  139. continue;
  140. }
  141. Autowired autowired = field.getAnnotation(Autowired.class);
  142. String beanName = autowired.value();
  143. if (beanName != null) {
  144. beanName = beanName.trim();
  145. }
  146. if (StringUtils.isBlank(beanName)) {
  147. beanName = field.getType().getName();
  148. }
  149. field.setAccessible(true);
  150. try {
  151. field.set(entry.getValue(), iocMap.get(beanName));
  152. } catch (IllegalAccessException e) {
  153. log.error("AutoWired fail,beanName: {}", beanName);
  154. e.printStackTrace();
  155. continue;
  156. }
  157. }
  158. }
  159. }
  160.  
  161. private void doInstance() throws Exception {
  162. if (classNames.isEmpty()) {
  163. return;
  164. }
  165. for (String className : classNames) {
  166. Class<?> clazz = Class.forName(className);
  167. //如果自定义了名字,就优先使用自己的名字,否则默认是小写(这里就不默认首字母为小写了
  168. if (clazz.isAnnotationPresent(Controller.class)) {
  169. Controller controller = clazz.getAnnotation(Controller.class);
  170. String beanName = controller.value();
  171. if (StringUtils.isBlank(beanName)) {
  172. beanName = clazz.getName().toLowerCase();
  173. }
  174. Object instance = clazz.newInstance();
  175. iocMap.put(beanName, instance);
  176. } else if (clazz.isAnnotationPresent(Service.class)) {
  177. Service service = clazz.getAnnotation(Service.class);
  178. String beanName = service.value();
  179. if (StringUtils.isBlank(beanName)) {
  180. beanName = clazz.getName().toLowerCase();
  181. }
  182. Object instance = clazz.newInstance();
  183. iocMap.put(beanName, instance);
  184. //根据接口类型来赋值
  185. for (Class<?> clazzInterface : clazz.getInterfaces()) {
  186. iocMap.put(clazzInterface.getName(), instance);
  187. }
  188. } else {
  189. continue;
  190. }
  191. }
  192. }
  193.  
  194. private void doScanner(String scanPackage) {
  195. URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
  196. File classDir = new File(url.getFile());
  197. for (File file : classDir.listFiles()) {
  198. if (file.isDirectory()) {
  199. doScanner(scanPackage + "." + file.getName());
  200. } else {
  201. String className = scanPackage + "." + file.getName().replace(".class", "");
  202. classNames.add(className);
  203. }
  204. }
  205. }
  206.  
  207. private void loadConfig(String location) {
  208. InputStream inputStream = this.getClass().getResourceAsStream(location);
  209. try {
  210. contextConfig.load(inputStream);
  211. } catch (IOException e) {
  212. log.error("Load fail, location: {}", location);
  213. e.printStackTrace();
  214. } finally {
  215. if (inputStream != null) {
  216. try {
  217. inputStream.close();
  218. } catch (IOException e) {
  219. log.error("Close fail, inputStream: {}", inputStream);
  220. e.printStackTrace();
  221. }
  222. }
  223. }
  224. }
  225. }

这个类就是最核心的类,它做了SpringMVC的事情。

7.下面是验证自己SpringMVC是否可用的时候了,自己写了service和controller:

7.1 service:

  1. public class DemoServiceImpl implements IDemoService {
  2. @Override
  3. public String get(String name) {
  4. return "my name is " + name;
  5. }
  6. }

7.2 controller:

  1. @Controller
  2. @RequestMapping("/demo")
  3. @Slf4j
  4. public class DemoController {
  5. @Autowired
  6. IDemoService service;
  7.  
  8. @RequestMapping("/get")
  9. public void get(HttpServletRequest req, HttpServletResponse resp, @RequestParam("name") String name) {
  10. String res = service.get(name);
  11. try {
  12. resp.setContentType("text/html;charset=UTF-8");
  13. resp.getWriter().println(res);
  14. } catch (IOException e) {
  15. log.info(e.getMessage());
  16. e.printStackTrace();
  17. }
  18. }
  19. }

再结合开头贴出来的图片,验证了自己的这个SpringMVC是可以使用的。

四 最后

这里只要实现了SpringMVC最简单的功能而已。这只是一个加深自己对SpringMVC的mapping映射流程的理解而已,真正的SpringMVC当然远不止如此简单。

Demo的github地址:https://github.com/Happy-Ape/Spring

手写一个简单版的SpringMVC的更多相关文章

  1. 动手写一个简单版的谷歌TPU-矩阵乘法和卷积

    谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...

  2. 动手写一个简单版的谷歌TPU-指令集

    系列目录 谷歌TPU概述和简化 基本单元-矩阵乘法阵列 基本单元-归一化和池化(待发布) TPU中的指令集 SimpleTPU实例: (计划中) 拓展 TPU的边界(规划中) 重新审视深度神经网络中的 ...

  3. 利用SpringBoot+Logback手写一个简单的链路追踪

    目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简 ...

  4. 手写一个简单的ElasticSearch SQL转换器(一)

    一.前言 之前有个需求,是使ElasticSearch支持使用SQL进行简单查询,较新版本的ES已经支持该特性(不过貌似还是实验性质的?) ,而且git上也有elasticsearch-sql 插件, ...

  5. 手写一个简版 asp.net core

    手写一个简版 asp.net core Intro 之前看到过蒋金楠老师的一篇 200 行代码带你了解 asp.net core 框架,最近参考蒋老师和 Edison 的文章和代码,结合自己对 asp ...

  6. 手写一个简单的starter组件

    spring-boot中有很多第三方包,都封装成starter组件,在maven中引用后,启动springBoot项目时会自动装配到spring ioc容器中. 思考: 为什么我们springBoot ...

  7. 手写一个简单到SpirngMVC框架

    spring对于java程序员来说,无疑就是吃饭到筷子.在每次编程工作到时候,我们几乎都离不开它,相信无论过去,还是现在或是未来到一段时间,它仍会扮演着重要到角色.自己对spring有一定的自我见解, ...

  8. 手写一个简单的HashMap

    HashMap简介 HashMap是Java中一中非常常用的数据结构,也基本是面试中的"必考题".它实现了基于"K-V"形式的键值对的高效存取.JDK1.7之前 ...

  9. 手写实现简单版IOC

    概述 IOC (Inversion of Control) 控制反转,大家应该都比较熟悉了.应该也都有用过,这里就不具体介绍了.自己平时也有用到过IOC,但是对它的具体实现原理只有一个模糊的概念,所以 ...

随机推荐

  1. Django学习路37_request属性

      打印元信息,基本上都会打印出来 类字典结构的 key 键 允许重复   get 请求可以传参,但是长度有限制 最大不能超过 2K post 文件上传使用 get 参数 默认放在网址中 post 在 ...

  2. Numpy访问数组元素

    import numpy as np n = np.array(([1,2,3],[4,5,6],[7,8,9])) ''' array([[1, 2, 3], [4, 5, 6], [7, 8, 9 ...

  3. PHP user_error() 函数

    定义和用法 user_error() 函数创建用户自定义的错误消息. user_error() 函数用于在用户指定的条件下触发一个错误消息.它可以与内建的错误处理程序一起使用,或者与由 set_err ...

  4. 4.9 省选模拟赛 生成树求和 变元矩阵树定理 生成函数 iDFT 插值法

    有同学在loj上找到了加强版 所以这道题是可以交的.LINK:生成树求和 加强版 对于30分 爆搜 可实际上我爆搜只过了25分 有同学使用按秩合并并茶几的及时剪枝通过了30分. const int M ...

  5. vue-cli脚手架的搭建

    1.安装node.js 2.安装cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org 3.安装vue-cli npm ...

  6. 基于视频压缩的实时监控系统-sprint3采集端传输子系统设计

    由于jpg本来就是编码压缩后的格式,所有无需重复编码 传输子系统步骤:(1)初始化:a.socket(初始化tcp连接):b.将事件添加到epoll中 (2)事件处理:接收到网络包.发送完网络包 st ...

  7. 基于视频压缩的实时监控系统-sprint2采集端图像采集子系统设计

    (1).初始化:a.初始化摄像头:b.注册事件到epoll (2).开始采集--->触发事件处理系统 (3).保存图像(方便测试) a.初始化摄像头 //初始化摄像头 1.获取驱动信息 2.设置 ...

  8. Qt实现的多菜单选择界面

    文章目录 1.效果展示 2.实现代码 2.1 菜单实现代码 2.1.1 头文件 2.1.2 源文件 2.2 应用代码 1.效果展示 这种菜单样式比较常用,实现的方法也有很多种,比如可以直接使用QTab ...

  9. Java 集合框架综述,这篇让你吃透!

    一.集合框架图 简化图: 说明:对于以上的框架图有如下几点说明 1.所有集合类都位于java.util包下.Java的集合类主要由两个接口派生而出:Collection和Map,Collection和 ...

  10. OpenCV之高斯平滑(Python实现)

    假设一个列数为W,行数为H的高斯卷计算子gaussKernel,其中W,H均为奇数,描点位置在((H-1)/2 ,(W-1)/2),构建高斯卷积核的步骤如下 1.计算高斯矩阵 \[gaussMatri ...