用 300 行代码手写提炼 Spring 核心原理 [3]
- 用 300 行代码手写提炼 Spring 核心原理 [1]
- 用 300 行代码手写提炼 Spring 核心原理 [2]
- 用 300 行代码手写提炼 Spring 核心原理 [3]
上文 中我们实现了 mini-spring 的 2.0 版本,基本功能已经实现,但依然还有很多工作要做,例如 HandlerMapping 还不能像 SpringMVC 一样支持正则,url 参数还不支持类型转换,在 3.0 版本中我们将继续优化。
初始化阶段
HandlerMapping
首先改造 HandlerMapping,在真实的 Spring 源码中,HandlerMapping 其实是一个 List 而非 Map,List 中的元素是自定义类型的。现在我们来仿真写一段代码,先定义一个内部类 Handler:
/**
* 内部类,记录Controller中RequestMapping和Method的对应关系
*/
private class Handler {
protected Object controller; // 保存方法对应的实例
protected Method method; // 保存映射的方法
protected Pattern pattern; // url pattern
protected Map<String, Integer> paramIndexMapping; // 保存参数名-参数index的映射
/**
* 构造方法
* @param pattern
* @param controller
* @param method
*/
protected Handler(Pattern pattern, Object controller, Method method) {
this.pattern = pattern;
this.controller = controller;
this.method = method;
paramIndexMapping = new HashMap<>();
putParamIndexMapping(method);
}
private void putParamIndexMapping(Method method) {
// 提取方法中加了注解的参数
Annotation[][] pa = method.getParameterAnnotations();
for (int i = 0; i < pa.length; i++) {
for (Annotation a: pa[i]) {
if (a instanceof MyRequestParam) {
String paramName = ((MyRequestParam) a).value().trim();
if (!"".equals(paramName)) {
paramIndexMapping.put(paramName, i);
}
}
}
}
// 提取方法中的request和response参数
Class<?>[] paramsTypes = method.getParameterTypes();
for (int i = 0; i < paramsTypes.length; i++) {
Class<?> type = paramsTypes[i];
if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
paramIndexMapping.put(type.getName(), i);
}
}
}
}
然后优化 HandlerMapping 的结构:
// 保存url -> method的映射关系
private List<Handler> handlerMapping = new ArrayList<>();
修改 initHandlerMapping() 方法:
/**
* 初始化HandlerMapping
*
* 本例中handlerMapping中的内容是:
* /demo/query = org.example.minispring.action.DemoAction.query(HttpServletRequest, HttpServletResponse, String)
*/
private void initHandlerMapping() {
if (IoC.isEmpty())
return;
for (Entry<String, Object> entry : IoC.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(MyController.class))
continue;
String baseUrl = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
baseUrl = requestMapping.value();
}
// 解析@MyController中方法上的@MyRequestMapping注解
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(MyRequestMapping.class)) {
continue;
}
MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
// 组合方法签名上的完整url,正则替换是为防止路径中出现多个连续多个"/"的不规范写法
String regex = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
Pattern pattern = Pattern.compile(regex);
// 保存url -> method的对应关系
handlerMapping.add(new Handler(pattern, entry.getValue(), method));
System.out.println("Mapped " + regex + " -> " + method);
}
}
}
修改 doDispatch() 方法,为了支持 url 的正则匹配和 url 参数的类型转换,我们新增了两个方法 getHandler() 和 convert():
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
Handler handler = getHandler(req);
if (handler == null) {
resp.getWriter().write("404 Not Found!");
return;
}
// 获得方法的形参列表
Class<?>[] paramTypes = handler.method.getParameterTypes();
Object[] paramValues = new Object[paramTypes.length];
// 从req中接收到的参数是Map类型,需要按照handler的method中的参数顺序传递
// handler.paramIndexMapping就发挥作用了:按照参数名称映射到正确的index位置
// 保证了paramValues中参数按顺序正确传递给method
Map<String, String[]> params = req.getParameterMap();
for (Map.Entry<String, String[]> entry : params.entrySet()) {
String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
if (!handler.paramIndexMapping.containsKey(entry.getKey()))
continue;
int index = handler.paramIndexMapping.get(entry.getKey());
paramValues[index] = convert(paramTypes[index], value);
}
if (handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
paramValues[reqIndex] = req;
}
if (handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
paramValues[respIndex] = resp;
}
Object returnValue = handler.method.invoke(handler.controller, paramValues);
if (returnValue == null || returnValue instanceof Void)
return;
resp.getWriter().write(returnValue.toString());
}
/**
* 根据request的url找到匹配的handler
*/
private Handler getHandler(HttpServletRequest req) {
if (handlerMapping.isEmpty())
return null;
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath, "").replaceAll("/+", "/");
for (Handler handler : handlerMapping) {
Matcher matcher = handler.pattern.matcher(url);
// 没有匹配上,继续匹配下一个
if (!matcher.matches())
continue;
return handler;
}
return null;
}
/*
* url传过来的参数都是String类型的,只需要把String转换为任意类型
*/
private Object convert(Class<?> type, String value) {
if (Integer.class == type) {
return Integer.valueOf(value);
}
// 如果还有double或者其他类型的参数,继续添加if
return value;
}
运行阶段
至此,mini-spring 的 3.0 版本就已经完成了。运行效果演示:
参考
[1] 《Spring 5 核心原理与 30 个类手写实战》,谭勇德著。
用 300 行代码手写提炼 Spring 核心原理 [3]的更多相关文章
- 【面试题】手写async await核心原理,再也不怕面试官问我async await原理
前言 async await 语法是 ES7出现的,是基于ES6的 promise和generator实现的 generator函数 在之前我专门讲个generator的使用与原理实现,大家没了解过的 ...
- 30个类手写Spring核心原理之环境准备(1)
本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...
- 自定义控件?试试300行代码实现QQ侧滑菜单
Android自定义控件并没有什么捷径可走,需要不断得模仿练习才能出师.这其中进行模仿练习的demo的选择是至关重要的,最优选择莫过于官方的控件了,但是官方控件动辄就是几千行代码往往可能容易让人望而却 ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍
概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...
- 通过 Mesos、Docker 和 Go,使用 300 行代码创建一个分布式系统
[摘要]虽然 Docker 和 Mesos 已成为不折不扣的 Buzzwords ,但是对于大部分人来说它们仍然是陌生的,下面我们就一起领略 Mesos .Docker 和 Go 配合带来的强大破坏力 ...
- 【Xamarin挖墙脚系列:代码手写UI,xib和StoryBoard间的博弈,以及Interface Builder的一些小技巧(转)】
正愁如何选择构建项目中的视图呢,现在官方推荐画板 Storybord...但是好像 xib貌似更胜一筹.以前的老棒子总喜欢装吊,用代码写....用代码堆一个HTML页面不知道你们尝试过没有.等页面做出 ...
- Python:游戏:300行代码实现俄罗斯方块
本文代码基于 python3.6 和 pygame1.9.4. 俄罗斯方块是儿时最经典的游戏之一,刚开始接触 pygame 的时候就想写一个俄罗斯方块.但是想到旋转,停靠,消除等操作,感觉好像很难啊, ...
- 代码手写UI,xib和StoryBoard间的博弈,以及Interface Builder的一些小技巧
近期接触了几个刚入门的iOS学习者,他们之中存在一个普遍和困惑和疑问.就是应该怎样制作UI界面.iOS应用是非常重视用户体验的,能够说绝大多数的应用成功与否与交互设计以及UI是否美丽易用有着非常大的关 ...
- 通过Mesos、Docker和Go,使用300行代码创建一个分布式系统
[摘要]虽然 Docker 和 Mesos 已成为不折不扣的 Buzzwords ,但是对于大部分人来说它们仍然是陌生的,下面我们就一起领略 Mesos .Docker 和 Go 配合带来的强大破坏力 ...
- 关于代码手写UI,xib和StoryBoard
代码手写UI 这种方法经常被学院派的极客或者依赖多人合作的大型项目大规模使用.Geek们喜欢用代码构建UI,是因为代码是键盘敲出来的,这样可以做到不开IB,手不离开键盘就完成工作,可以专注于编码环境, ...
随机推荐
- psutil 检测exe是否已经运行
安装依赖 pip install psutil 代码 import psutil def check_if_process_running(process_name): ''' Check if th ...
- CSS – :has parent selector, @container container query, transform replacement, subgrid (2022 期待新功能)
前言 CSS 一直有一些老问题没有被解决. 2022 视乎看见了曙光. 参考 4 Exciting New CSS Features in 2022 :has() 参考: YouTube – How ...
- CSS – Counters
介绍 counter 有点像 JS 的 for loop index. 最常用到的地方就是做 ol > li. 参考: W3Schools – CSS Counters 默认 ol > l ...
- CSS – border-radius (Rounded Corners)
前言 之前的文章 CSS – W3Schools 学习笔记 (3), 这篇独立出来写, 作为整理. 参考: Youtube – Advanced CSS Border-Radius Tutorial ...
- C++ STL queue容器——队列
queue容器 基本概念 queue是一种**先进先出的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素. queue容器没有迭代器,所有元素进出都必须符合"先进先 ...
- 多线程ExecutorService 的理解与使用
原文链接:https://www.cnblogs.com/gxz-sw/p/6754476.html 接口 Java.util.concurrent.ExecutorService 表述了异步执行的机 ...
- Flutter TextField 的高度问题
示例 先来看一个例子:假设我们要做一个表单,左边是提示文字,右边是输入框 给出代码: Row( crossAxisAlignment: CrossAxisAlignment.center, child ...
- pytorch中y.data.norm()的含义
import torch x = torch.randn(3, requires_grad=True) y = x*2 print(y.data.norm()) print(torch.sqrt(to ...
- day07-数据类型及标识符
数据类型 强类型语言 要求变量的使用严格要严格符合规定,所有变量都必须先定义后才能使用 弱类型语言 java的数据类型分为两大类 基本类型(primitive type) 数值类型 整数类型 byte ...
- 2024 年 04 月编程语言排行榜,PHP 排名创新低?
编程语言的流行度总是变化莫测,每个月的排行榜都揭示着新的趋势.2024年4月的编程语言排行榜揭示了一个引人关注的现象:PHP的排名再次下滑,创下了历史新低.这种变化对于PHP开发者和整个技术社区来 ...