仿照源码,手写一个自定义 Spring MVC 框架
ccwgpt 2024-09-13 16:18 99 浏览 0 评论
前言
上节课我们学习了 Spring MVC 框架的使用,为了更好地理解这个框架,本节课我们来仿写一个 Spring MVC 框架,用到的技术比较简单,只需要 XML 解析+反射就可以完成,不需要 JDK 动态代理。
自己手写框架的前提使必须理解框架的底层原理和运行机制,所以我们还是先来回顾一下 Spring MVC 的实现原理。
Spring MVC 实现原理
核心组件
- DispatcherServlet:前端控制器,负责调度其他组件的执行,可降低不同组件之间的耦合性,是整个 Spring MVC 的核心模块。
- Handler:处理器,完成具体业务逻辑,相当于 Servlet 或 Action。
- HandlerMapping:DispatcherServlet 是通过 HandlerMapping 将请求映射到不同的 Handler。
- HandlerInterceptor:处理器拦截器,是一个接口,如果我们需要做一些拦截处理,可以来实现这个接口。
- HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外拦截处理,可以添加拦截器设置)。
- HandlerAdapter:处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作包括表单数据的验证,数据类型的转换,将表单数据封装到 POJO 等等,这一系列的操作,都是由 HandlerAdapter 来完成,DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler。
- ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
- ViewResolver:视图解析器,DispatcherServlet 通过它将逻辑视图解析成物理视图,最终将渲染结果响应给客户端。
以上就是 Spring MVC 的核心组件。那么这些组件之间是如何进行交互的呢?
工作流程
- 客户端请求被 DispatcherServlet(前端控制器)接收。
- 根据 Handler Mapping映射到 Handler。
- 生成 Handler 和 HandlerInterceptor(如果有则生成)。
- Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一并返回给 DispatcherServlet。
- DispatcherServlet 通过 HandlerAdapter 调用 Handler 的方法做业务逻辑处理。
- 返回一个 ModelAndView 对象给 DispatcherServlet。
- DispatcherServlet 将获取的 ModelAndView 对象传给 ViewResolver 视图解析器,将逻辑视图解析成物理视图 View。
- ViewResolver 返回一个 View 给 DispatcherServlet。
- DispatcherServlet 根据 View 进行视图渲染(将模型数据填充到视图中)。
- DispatcherServlet 将渲染后的视图响应给客户端。
通过以上的分析,大致可以将 Spring MVC 流程理解如下:
首先需要一个前置控制器 DispatcherServlet,作为整个流程的核心,由它去调用其他组件,共同完成业务。
主要组件有两个:
一是 Controller,调用其业务方法 Method,执行业务逻辑。
二是 ViewResolver 视图解析器,将业务方法的返回值解析为物理视图+模型数据,返回客户端。
我们自己写框架就按照这个思路来。
初始化工作
- 根据 Spring IoC 容器的特性,需要将参与业务的对象全部创建并保存到容器中,供流程调用。首先需要创建 Controller 对象,HTTP 请求是通过注解找到对应的 Controller 对象,所以我们需要将所有的 Controller 与其注解建立关联,很显然,使用 key-value 结构的 Map 集合来保存最合适不过了,这样就模拟了 IoC 容器。
- Controller 的 Method 也是通过注解与 HTTP 请求映射的,同样的,我们需要将所有的 Method 与其注解建立关联, HTTP 直接通过注解的值找到对应的 Method,这里也用 Map 集合保存。
- 实例化视图解析器。
初始化工作完成,接下来处理 HTTP 请求,业务流程如下:
- DispatcherServlet 接收请求,通过映射从 IoC 容器中获取对应的 Controller 对象。
- 根据映射获取 Controller 对象对应的 Method。
- 调用 Method,获取返回值。
- 将返回值传给视图解析器,返回物理视图。
- 完成页面跳转。
思路捋清楚了,接下来开始写代码,我们需要创建下面这四个类:
- MyDispatcherServlet:模拟 DispatcherServlet。
- MyController:模拟 Controller 注解。
- MyRequestMapping:模拟 RequestMapping 注解。
- MyViewResolver:模拟 ViewResolver 视图解析器。
首先创建 MyDispatcherServlet,init 方法完成初始化:
1、将 Controller 与注解进行关联,保存到 iocContainer 中,那些 Controller 是需要添加到 iocContainer 中的?
必须同时满足两点:
- springmvc.xml 中配置扫描的类。
- 类定义处添加了注解。
注意这两点必须同时满足。
代码思路:
- 解析 springmvc.xml。
- 获取 component-scan 标签配置的包下的所有类。
- 判断若这些类添加了 @MyController 注解,则创建实例对象,并且保存到 iocContainer。
- @MyRequestMapping 的值为键,Controller 对象为值。
2、将 Controller 中的 Method 与注解进行关联,保存到 handlerMapping 中。
代码思路:
- 遍历 iocContainer 中的 Controller 实例对象。
- 遍历每一个 Controlle r对象的 Method。
- 判断 Method 是否添加了 @MyRequestMapping 注解,若添加,则进行映射并保存。
- 保存到 handlerMapping 中,@MyRequestMapping 的值为键,Method 为值。
3、实例化 ViewResolver。
代码思路:
- 解析 springmvc.xml。
- 根据 bean 标签的 class 属性获取需要实例化的 MyViewResolver。
- 通过反射创建实例化对象,同时获取 prefix 和 suffix 属性,以及 setter 方法。
- 通过反射调用 setter 方法给属性赋值,完成 MyViewResolver 的实例化。
doPost 方法处理 HTTP 请求的流程:
1、解析 HTTP,分别得到 Controller 和 Method 对应的 uri。
2、通过 uri 分别在 iocContainer 和 handlerMapping 中获取对应的 Controller 以及 Method。
3、通过反射调用 Method,执行业务方法,获取结果。
4、将结果传给 MyViewResolver 进行解析,返回真正的物理视图(JSP 页面)。
5、完成 JSP 页面跳转。
代码实现:
1、创建 MyController 注解,作用目标为类。
/**
* 自定义Controller注解
* @author southwind
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
String value() default "";
}
2、创建 MyRequestMapping 注解,作用目标为类和方法。
/**
* 自定义RequestMapping注解
* @author southwind
*
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
String value() default "";
}
3、创建 MyDispatcherServlet,核心控制器,init 完成初始化工作,doPost 处理 HTTP 请求。
/**
* DispatcherServlet
* @author southwind
*
*/
public class MyDispatcherServlet extends HttpServlet{
//模拟IOC容器,保存Controller实例对象
private Map<String,Object> iocContainer = new HashMap<String,Object>();
//保存handler映射
private Map<String,Method> handlerMapping = new HashMap<String,Method>();
//自定视图解析器
private MyViewResolver myViewResolver;
@Override
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
//扫描Controller,创建实例对象,并存入iocContainer
scanController(config);
//初始化handler映射
initHandlerMapping();
//加载视图解析器
loadViewResolver(config);
}
/**
* 扫描Controller
* @param config
*/
public void scanController(ServletConfig config){
SAXReader reader = new SAXReader();
try {
//解析springmvc.xml
String path = config.getServletContext().getRealPath("")+"\\WEB-INF\\classes\\"+config.getInitParameter("contextConfigLocation");
Document document = reader.read(path);
Element root = document.getRootElement();
Iterator iter = root.elementIterator();
while(iter.hasNext()){
Element ele = (Element) iter.next();
if(ele.getName().equals("component-scan")){
String packageName = ele.attributeValue("base-package");
//获取base-package包下的所有类名
List<String> list = getClassNames(packageName);
for(String str:list){
Class clazz = Class.forName(str);
//判断是否有MyController注解
if(clazz.isAnnotationPresent(MyController.class)){
//获取Controller中MyRequestMapping注解的value
MyRequestMapping annotation = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class);
String value = annotation.value().substring(1);
//Controller实例对象存入iocContainer
iocContainer.put(value, clazz.newInstance());
}
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 获取包下的所有类名
* @param packageName
* @return
*/
public List<String> getClassNames(String packageName){
List<String> classNameList = new ArrayList<String>();
String packagePath = packageName.replace(".", "/");
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = loader.getResource(packagePath);
if(url != null){
File file = new File(url.getPath());
File[] childFiles = file.listFiles();
for(File childFile : childFiles){
String className = packageName+"."+childFile.getName().replace(".class", "");
classNameList.add(className);
}
}
return classNameList;
}
/**
* 初始化handler映射
*/
public void initHandlerMapping(){
for(String str:iocContainer.keySet()){
Class clazz = iocContainer.get(str).getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//判断方式是否添加MyRequestMapping注解
if(method.isAnnotationPresent(MyRequestMapping.class)){
//获取Method中MyRequestMapping注解的value
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String value = annotation.value().substring(1);
//method存入methodMapping
handlerMapping.put(value, method);
}
}
}
}
/**
* 加载自定义视图解析器
* @param config
*/
public void loadViewResolver(ServletConfig config){
SAXReader reader = new SAXReader();
try {
//解析springmvc.xml
String path = config.getServletContext().getRealPath("")+"\\WEB-INF\\classes\\"+config.getInitParameter("contextConfigLocation");
Document document = reader.read(path);
Element root = document.getRootElement();
Iterator iter = root.elementIterator();
while(iter.hasNext()){
Element ele = (Element) iter.next();
if(ele.getName().equals("bean")){
String className = ele.attributeValue("class");
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
//获取setter方法
Method prefixMethod = clazz.getMethod("setPrefix", String.class);
Method suffixMethod = clazz.getMethod("setSuffix", String.class);
Iterator beanIter = ele.elementIterator();
//获取property值
Map<String,String> propertyMap = new HashMap<String,String>();
while(beanIter.hasNext()){
Element beanEle = (Element) beanIter.next();
String name = beanEle.attributeValue("name");
String value = beanEle.attributeValue("value");
propertyMap.put(name, value);
}
for(String str:propertyMap.keySet()){
//反射机制调用setter方法,完成赋值。
if(str.equals("prefix")){
prefixMethod.invoke(obj, propertyMap.get(str));
}
if(str.equals("suffix")){
suffixMethod.invoke(obj, propertyMap.get(str));
}
}
myViewResolver = (MyViewResolver) obj;
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
//获取请求
String handlerUri = req.getRequestURI().split("/")[2];
//获取Controller实例
Object obj = iocContainer.get(handlerUri);
String methodUri = req.getRequestURI().split("/")[3];
//获取业务方法
Method method = handlerMapping.get(methodUri);
try {
//反射机制调用业务方法
String value = (String) method.invoke(obj);
//视图解析器将逻辑视图转换为物理视图
String result = myViewResolver.jspMapping(value);
//页面跳转
req.getRequestDispatcher(result).forward(req, resp);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4、创建视图解析器 MyViewResolver。
/**
* 自定义视图解析器
* @author southwind
*
*/
public class MyViewResolver {
private String prefix;
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String jspMapping(String value){
return this.prefix+value+this.suffix;
}
}
5、创建 TestController,处理业务请求。
@MyController
@MyRequestMapping(value = "/testController")
public class TestController {
@MyRequestMapping(value = "/test")
public String test(){
System.out.println("执行test相关业务");
return "index";
}
}
6、测试。
跳转 index.jsp,同时控制台打印业务日志,访问成功。
总结
本节课我们讲解了 Spring MVC 的底层原理,同时仿照 Spring MVC 手写了一个简单的框架,目的不是让大家自己去写框架,在实际开发中我们也不需要自己写框架,直接使用成熟的第三方框架即可。
手写框架的目的在于让大家更透彻地理解 Spring MVC 的底层流程,学习优秀框架的编程思想,理解了原理,才能更熟练地应用。
相关推荐
- 十分钟让你学会LNMP架构负载均衡(impala负载均衡)
-
业务架构、应用架构、数据架构和技术架构一、几个基本概念1、pv值pv值(pageviews):页面的浏览量概念:一个网站的所有页面,在一天内,被浏览的总次数。(大型网站通常是上千万的级别)2、u...
- AGV仓储机器人调度系统架构(agv物流机器人)
-
系统架构层次划分采用分层模块化设计,分为以下五层:1.1用户接口层功能:提供人机交互界面(Web/桌面端),支持任务下发、实时监控、数据可视化和报警管理。模块:任务管理面板:接收订单(如拣货、...
- 远程热部署在美团的落地实践(远程热点是什么意思)
-
Sonic是美团内部研发设计的一款用于热部署的IDEA插件,本文其实现原理及落地的一些技术细节。在阅读本文之前,建议大家先熟悉一下Spring源码、SpringMVC源码、SpringBoot...
- springboot搭建xxl-job(分布式任务调度系统)
-
一、部署xxl-job服务端下载xxl-job源码:https://gitee.com/xuxueli0323/xxl-job二、导入项目、创建xxl_job数据库、修改配置文件为自己的数据库三、启动...
- 大模型:使用vLLM和Ray分布式部署推理应用
-
一、vLLM:面向大模型的高效推理框架1.核心特点专为推理优化:专注于大模型(如GPT-3、LLaMA)的高吞吐量、低延迟推理。关键技术:PagedAttention:类似操作系统内存分页管理,将K...
- 国产开源之光【分布式工作流调度系统】:DolphinScheduler
-
DolphinScheduler是一个开源的分布式工作流调度系统,旨在帮助用户以可靠、高效和可扩展的方式管理和调度大规模的数据处理工作流。它支持以图形化方式定义和管理工作流,提供了丰富的调度功能和监控...
- 简单可靠高效的分布式任务队列系统
-
#记录我的2024#大家好,又见面了,我是GitHub精选君!背景介绍在系统访问量逐渐增大,高并发、分布式系统成为了企业技术架构升级的必由之路。在这样的背景下,异步任务队列扮演着至关重要的角色,...
- 虚拟服务器之间如何分布式运行?(虚拟服务器部署)
-
在云计算和虚拟化技术快速发展的今天,传统“单机单任务”的服务器架构早已难以满足现代业务对高并发、高可用、弹性伸缩和容错容灾的严苛要求。分布式系统应运而生,并成为支撑各类互联网平台、企业信息系统和A...
- 一文掌握 XXL-Job 的 6 大核心组件
-
XXL-Job是一个分布式任务调度平台,其核心组件主要包括以下部分,各组件相互协作实现高效的任务调度与管理:1.调度注册中心(RegistryCenter)作用:负责管理调度器(Schedule...
- 京东大佬问我,SpringBoot中如何做延迟队列?单机与分布式如何做?
-
京东大佬问我,SpringBoot中如何做延迟队列?单机如何做?分布式如何做呢?并给出案例与代码分析。嗯,用户问的是在SpringBoot中如何实现延迟队列,单机和分布式环境下分别怎么做。这个问题其实...
- 企业级项目组件选型(一)分布式任务调度平台
-
官网地址:https://www.xuxueli.com/xxl-job/能力介绍架构图安全性为提升系统安全性,调度中心和执行器进行安全性校验,双方AccessToken匹配才允许通讯;调度中心和执...
- python多进程的分布式任务调度应用场景及示例
-
多进程的分布式任务调度可以应用于以下场景:分布式爬虫:importmultiprocessingimportrequestsdefcrawl(url):response=re...
- SpringBoot整合ElasticJob实现分布式任务调度
-
介绍ElasticJob是面向互联网生态和海量任务的分布式调度解决方案,由两个相互独立的子项目ElasticJob-Lite和ElasticJob-Cloud组成。它通过弹性调度、资源管控、...
- 分布式可视化 DAG 任务调度系统 Taier 的整体流程分析
-
Taier作为袋鼠云的开源项目之一,是一个分布式可视化的DAG任务调度系统。旨在降低ETL开发成本,提高大数据平台稳定性,让大数据开发人员可以在Taier直接进行业务逻辑的开发,而不用关...
- SpringBoot任务调度:@Scheduled与TaskExecutor全面解析
-
一、任务调度基础概念1.1什么是任务调度任务调度是指按照预定的时间计划或特定条件自动执行任务的过程。在现代应用开发中,任务调度扮演着至关重要的角色,它使得开发者能够自动化处理周期性任务、定时任务和异...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)