手写一个简单版的SpringMVC
一 写在前面
这是自己实现一个简单的具有SpringMVC功能的小Demo,主要实现效果是;
自己定义的实现效果是通过浏览器地址传一个name参数,打印“my name is”+name参数。不使用SpringMVC,自己定义部分注解,实现DispatcherServlet核心功能,通过这个demo可以加深自己对源码的理解。
先看一下实现效果:
(传入了参数时)
(没有传入参数时)
二 DispatcherServlet流程
- 加载配置文件
- 扫描所有相关类
- 初始化所有相关的类
- 自动注入
- 初始化HandlerMapping
- 等待请求
三 代码回顾
1.首先来看一下Pom文件的依赖:


- <dependencies>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>servlet-api</artifactId>
- <version>2.5</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.10</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.12</version>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-core</artifactId>
- <version>1.2.3</version>
- </dependency>
- <dependency>
- <groupId>ch.qos.logback</groupId>
- <artifactId>logback-classic</artifactId>
- <version>1.2.3</version>
- </dependency>
- </dependencies>
依赖比较少,没有spring的依赖,主要就是一个servlet的。
2. 配置文件:
2.1. application.properties文件:


- scanPackage=com.qunar.framework.demo
这是说明要扫描的位置。
2.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>MySpringMVC</display-name>
- <servlet>
- <servlet-name>mvc</servlet-name>
- <servlet-class>com.qunar.framework.webmvc.DispatcherServlet</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>mvc</servlet-name>
- <url-pattern>/*</url-pattern>
- </servlet-mapping>
- </web-app>
3. 下面是整个工程的目录结构:
4. 自定义注解:
@Controller:


- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface Controller {
- String value() default "";
- }
@Service:


- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface Service {
- String value() default "";
- }
@AutoWired:


- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface Autowired {
- String value() default "";
- }
@RequestMapping:


- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface Autowired {
- String value() default "";
- }
@RequestParam:


- @Target(ElementType.PARAMETER)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface RequestParam {
- String value() default "";
- }
5.自己封装的Handler:


- public class Handler {
- protected Object controller;
- protected Method method;
- protected Pattern pattern;
- protected Map<String,Integer> paramIndexMap;
- public Handler(Object controller, Method method, Pattern pattern) {
- this.controller = controller;
- this.method = method;
- this.pattern = pattern;
- this.paramIndexMap = new HashMap<>();
- putParamIndexMapping(method);
- }
- private void putParamIndexMapping(Method method) {
- //获取方法中加了注解的参数
- Annotation[][] annotations = method.getParameterAnnotations();
- for (int i =0; i < annotations.length;i++){
- for (Annotation annotation : annotations[i]){
- if (annotation instanceof RequestParam){
- String paramName = ((RequestParam) annotation).value();
- if (!StringUtils.isBlank(paramName)){
- paramIndexMap.put(paramName,i);
- }
- }
- }
- }
- //获取方法中的我request和response的参数
- Class<?>[] paramTypes = method.getParameterTypes();
- for (int i = 0; i < paramTypes.length; i++){
- Class<?> paramType = paramTypes[i];
- if (paramType == HttpServletRequest.class || paramType == HttpServletResponse.class){
- paramIndexMap.put(paramType.getName(),i);
- }
- }
- }
- }
6. 自己封装的DispatcherServlet:


- @Slf4j
- public class DispatcherServlet extends HttpServlet {
- private static final long serialVersionUID = 1L;
- private Properties contextConfig = new Properties();
- private List<String> classNames = new ArrayList<>();
- private Map<String, Object> iocMap = new HashMap<>();
- private List<Handler> handlerMapping = new ArrayList<>();
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
- this.doPost(req, resp);
- }
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
- //等待请求
- try {
- doDispatch(req, resp);
- } catch (Exception exception) {
- resp.getWriter().write("500 Exception");
- log.error("500 Exception. Cause: {}", exception.getMessage());
- exception.printStackTrace();
- }
- }
- private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
- Handler handler = getHandler(req);
- if (handler == null) {
- //没有匹配上,404
- log.info("404 Not Found");
- resp.getWriter().write("404 Not Found");
- return;
- }
- //获取参数列表
- Class<?>[] parameterTypes = handler.method.getParameterTypes();
- //保存所有需要自动赋值的参数值
- Object[] parameterValues = new Object[parameterTypes.length];
- Map<String, String[]> parameterMap = req.getParameterMap();
- for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
- String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll("/+", "/");
- log.info(value);
- //如果找到了匹配的值,就填充
- if (!handler.paramIndexMap.containsKey(entry.getKey())) {
- continue;
- }
- Integer index = handler.paramIndexMap.get(entry.getKey());
- parameterValues[index] = convert(parameterTypes[index], value);
- }
- //设置方法中的request对象和response对象
- Integer reqIndex = handler.paramIndexMap.get(HttpServletRequest.class.getName());
- Integer respIndex = handler.paramIndexMap.get(HttpServletResponse.class.getName());
- parameterValues[reqIndex] = req;
- parameterValues[respIndex] = resp;
- handler.method.invoke(handler.controller, parameterValues);
- }
- private Object convert(Class<?> parameterType, String value) {
- if (parameterType == Integer.class) {
- return Integer.valueOf(value);
- }
- return value;
- }
- private Handler getHandler(HttpServletRequest req) {
- if (handlerMapping.isEmpty()) {
- return null;
- }
- String requestURI = req.getRequestURI();
- String contextPath = req.getContextPath();
- requestURI = requestURI.replace(contextPath, "").replaceAll("/+", "/");
- for (Handler handler : handlerMapping) {
- Matcher matcher = handler.pattern.matcher(requestURI);
- if (!matcher.matches()) {
- continue;
- }
- return handler;
- }
- return null;
- }
- @Override
- public void init(ServletConfig config) {
- //从这里开始启动:
- //加载配置文件
- loadConfig(config.getInitParameter("contextConfigLocation"));
- //扫描相关类
- doScanner(contextConfig.getProperty("scanPackage"));
- //初始化相关类
- try {
- doInstance();
- } catch (Exception exception) {
- log.error("Execute doInstance method fail.");
- exception.printStackTrace();
- }
- //自动注入
- doAutowired();
- //初始化HandlerMapping
- initHandlerMapping();
- }
- private void initHandlerMapping() {
- if (iocMap.isEmpty()) {
- return;
- }
- for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
- Class<?> clazz = entry.getValue().getClass();
- if (!clazz.isAnnotationPresent(Controller.class)) {
- continue;
- }
- String baseUrl = "";
- if (clazz.isAnnotationPresent(RequestMapping.class)) {
- RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
- baseUrl = requestMapping.value();
- }
- //扫描所有的公共方法
- for (Method method : clazz.getMethods()) {
- if (!method.isAnnotationPresent(RequestMapping.class)) {
- continue;
- }
- RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
- String regex = ("/" + baseUrl + requestMapping.value()).replaceAll("/+", "/");
- Pattern pattern = Pattern.compile(regex);
- handlerMapping.add(new Handler(entry.getValue(), method, pattern));
- log.info("Mapping: {}.{}", regex, method);
- }
- }
- }
- 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(Autowired.class)) {
- continue;
- }
- Autowired autowired = field.getAnnotation(Autowired.class);
- String beanName = autowired.value();
- if (beanName != null) {
- beanName = beanName.trim();
- }
- if (StringUtils.isBlank(beanName)) {
- beanName = field.getType().getName();
- }
- field.setAccessible(true);
- try {
- field.set(entry.getValue(), iocMap.get(beanName));
- } catch (IllegalAccessException e) {
- log.error("AutoWired fail,beanName: {}", beanName);
- e.printStackTrace();
- continue;
- }
- }
- }
- }
- private void doInstance() throws Exception {
- if (classNames.isEmpty()) {
- return;
- }
- for (String className : classNames) {
- Class<?> clazz = Class.forName(className);
- //如果自定义了名字,就优先使用自己的名字,否则默认是小写(这里就不默认首字母为小写了
- if (clazz.isAnnotationPresent(Controller.class)) {
- Controller controller = clazz.getAnnotation(Controller.class);
- String beanName = controller.value();
- if (StringUtils.isBlank(beanName)) {
- beanName = clazz.getName().toLowerCase();
- }
- Object instance = clazz.newInstance();
- iocMap.put(beanName, instance);
- } else if (clazz.isAnnotationPresent(Service.class)) {
- Service service = clazz.getAnnotation(Service.class);
- String beanName = service.value();
- if (StringUtils.isBlank(beanName)) {
- beanName = clazz.getName().toLowerCase();
- }
- Object instance = clazz.newInstance();
- iocMap.put(beanName, instance);
- //根据接口类型来赋值
- for (Class<?> clazzInterface : clazz.getInterfaces()) {
- iocMap.put(clazzInterface.getName(), instance);
- }
- } else {
- continue;
- }
- }
- }
- private void doScanner(String scanPackage) {
- URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
- File classDir = new File(url.getFile());
- for (File file : classDir.listFiles()) {
- if (file.isDirectory()) {
- doScanner(scanPackage + "." + file.getName());
- } else {
- String className = scanPackage + "." + file.getName().replace(".class", "");
- classNames.add(className);
- }
- }
- }
- private void loadConfig(String location) {
- InputStream inputStream = this.getClass().getResourceAsStream(location);
- try {
- contextConfig.load(inputStream);
- } catch (IOException e) {
- log.error("Load fail, location: {}", location);
- e.printStackTrace();
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- log.error("Close fail, inputStream: {}", inputStream);
- e.printStackTrace();
- }
- }
- }
- }
- }
这个类就是最核心的类,它做了SpringMVC的事情。
7.下面是验证自己SpringMVC是否可用的时候了,自己写了service和controller:
7.1 service:


- public class DemoServiceImpl implements IDemoService {
- @Override
- public String get(String name) {
- return "my name is " + name;
- }
- }
7.2 controller:


- @Controller
- @RequestMapping("/demo")
- @Slf4j
- public class DemoController {
- @Autowired
- IDemoService service;
- @RequestMapping("/get")
- public void get(HttpServletRequest req, HttpServletResponse resp, @RequestParam("name") String name) {
- String res = service.get(name);
- try {
- resp.setContentType("text/html;charset=UTF-8");
- resp.getWriter().println(res);
- } catch (IOException e) {
- log.info(e.getMessage());
- e.printStackTrace();
- }
- }
- }
再结合开头贴出来的图片,验证了自己的这个SpringMVC是可以使用的。
四 最后
这里只要实现了SpringMVC最简单的功能而已。这只是一个加深自己对SpringMVC的mapping映射流程的理解而已,真正的SpringMVC当然远不止如此简单。
Demo的github地址:https://github.com/Happy-Ape/Spring
手写一个简单版的SpringMVC的更多相关文章
- 动手写一个简单版的谷歌TPU-矩阵乘法和卷积
谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...
- 动手写一个简单版的谷歌TPU-指令集
系列目录 谷歌TPU概述和简化 基本单元-矩阵乘法阵列 基本单元-归一化和池化(待发布) TPU中的指令集 SimpleTPU实例: (计划中) 拓展 TPU的边界(规划中) 重新审视深度神经网络中的 ...
- 利用SpringBoot+Logback手写一个简单的链路追踪
目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简 ...
- 手写一个简单的ElasticSearch SQL转换器(一)
一.前言 之前有个需求,是使ElasticSearch支持使用SQL进行简单查询,较新版本的ES已经支持该特性(不过貌似还是实验性质的?) ,而且git上也有elasticsearch-sql 插件, ...
- 手写一个简版 asp.net core
手写一个简版 asp.net core Intro 之前看到过蒋金楠老师的一篇 200 行代码带你了解 asp.net core 框架,最近参考蒋老师和 Edison 的文章和代码,结合自己对 asp ...
- 手写一个简单的starter组件
spring-boot中有很多第三方包,都封装成starter组件,在maven中引用后,启动springBoot项目时会自动装配到spring ioc容器中. 思考: 为什么我们springBoot ...
- 手写一个简单到SpirngMVC框架
spring对于java程序员来说,无疑就是吃饭到筷子.在每次编程工作到时候,我们几乎都离不开它,相信无论过去,还是现在或是未来到一段时间,它仍会扮演着重要到角色.自己对spring有一定的自我见解, ...
- 手写一个简单的HashMap
HashMap简介 HashMap是Java中一中非常常用的数据结构,也基本是面试中的"必考题".它实现了基于"K-V"形式的键值对的高效存取.JDK1.7之前 ...
- 手写实现简单版IOC
概述 IOC (Inversion of Control) 控制反转,大家应该都比较熟悉了.应该也都有用过,这里就不具体介绍了.自己平时也有用到过IOC,但是对它的具体实现原理只有一个模糊的概念,所以 ...
随机推荐
- Django学习路37_request属性
打印元信息,基本上都会打印出来 类字典结构的 key 键 允许重复 get 请求可以传参,但是长度有限制 最大不能超过 2K post 文件上传使用 get 参数 默认放在网址中 post 在 ...
- 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 ...
- PHP user_error() 函数
定义和用法 user_error() 函数创建用户自定义的错误消息. user_error() 函数用于在用户指定的条件下触发一个错误消息.它可以与内建的错误处理程序一起使用,或者与由 set_err ...
- 4.9 省选模拟赛 生成树求和 变元矩阵树定理 生成函数 iDFT 插值法
有同学在loj上找到了加强版 所以这道题是可以交的.LINK:生成树求和 加强版 对于30分 爆搜 可实际上我爆搜只过了25分 有同学使用按秩合并并茶几的及时剪枝通过了30分. const int M ...
- vue-cli脚手架的搭建
1.安装node.js 2.安装cnpm npm install -g cnpm --registry=https://registry.npm.taobao.org 3.安装vue-cli npm ...
- 基于视频压缩的实时监控系统-sprint3采集端传输子系统设计
由于jpg本来就是编码压缩后的格式,所有无需重复编码 传输子系统步骤:(1)初始化:a.socket(初始化tcp连接):b.将事件添加到epoll中 (2)事件处理:接收到网络包.发送完网络包 st ...
- 基于视频压缩的实时监控系统-sprint2采集端图像采集子系统设计
(1).初始化:a.初始化摄像头:b.注册事件到epoll (2).开始采集--->触发事件处理系统 (3).保存图像(方便测试) a.初始化摄像头 //初始化摄像头 1.获取驱动信息 2.设置 ...
- Qt实现的多菜单选择界面
文章目录 1.效果展示 2.实现代码 2.1 菜单实现代码 2.1.1 头文件 2.1.2 源文件 2.2 应用代码 1.效果展示 这种菜单样式比较常用,实现的方法也有很多种,比如可以直接使用QTab ...
- Java 集合框架综述,这篇让你吃透!
一.集合框架图 简化图: 说明:对于以上的框架图有如下几点说明 1.所有集合类都位于java.util包下.Java的集合类主要由两个接口派生而出:Collection和Map,Collection和 ...
- OpenCV之高斯平滑(Python实现)
假设一个列数为W,行数为H的高斯卷计算子gaussKernel,其中W,H均为奇数,描点位置在((H-1)/2 ,(W-1)/2),构建高斯卷积核的步骤如下 1.计算高斯矩阵 \[gaussMatri ...