基于Servlet实现简化版SpringMVC
编写花费3天,本文是demo做完后整理的
其实还有一个文档,记录了博客和AI的一些回答
这里就不放哪些了,因为那些说实话思路还是比较混乱的,前期没有完全串起来,不知道具体啥实现。
但是也有很多相关资料,最好还是挑需要的部分看看
完整demo项目代码:ServletToSpringMVC: 使用servlet实现简化版的SpringMVC中的控制层请求处理,以及IOC容器(@Component及其相关注解,@Auwired)
后续如果基于这个写完购物系统再补:TODO
想法诞生
碎碎念,直接跳了就行,也可以简单看看最后
这里原本都没想过搞这个,还不是因为学校这里限选了javaweb,老师需要完成一个购物系统课设,但是老师教的是特别老的使用Servlet
、各种JDBC包但是没有再次封装过的、jsp前端部分的形式,但是这些大部分已经过时了。
不过我在很早之间就已经学过少少的一点SSM
基础,更想要的前后端分离的形式
于是就去问老师,能不能用框架?结果是不行,给出了具体的限制:
- 页面的可用啥都行
- 后端只能使用
Servlet
、maven
和原始的数据库工具类,不能使用其他web框架
这里就想着使用Servlet
完成前后端分离相关工作,前端部分从开源平台中随便找一个项目,用axios
进行网络请求,建起前后端连接的桥梁。
-
然后花了一个上午找到并运行了前后端分离的购物系统(vue2+nodejs)(没带数据库,只能自己设计下),项目看着还行有购物端和管理端
-
又花了一天整理了下需要完成的接口,并写到了apifox中
-
花了一个下午,找原始的数据库工具类,最后决定封装同时想着换成线程池
-
花了一个下午看这个博客入门了简单的
Servlet
项目(在idea环境中,使用maven搭建) -
最后看着用Servlet感觉还是太复杂了,感觉一个请求路径感觉就要编写一个类,看着有点不爽呀,并且向请求接受还有数据的响应太过重复了
例子:
@WebServlet("/admin/getType") public class GetTypeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } }
想着前后端分离都分离,接口写都写了,数据库封装都封装了,不如直接更近一步实现SpringMVC
一些基础的注解
目标
完成简化版本的SpringMVC
的功能,加深对Spring
框架的理解,学习运用注解、反射等java基础和中心代理的一些思想
- 请求控制层相关代码
- IOC容器
学习历程
这里几乎没有参照源码,只是实现了一样的经过简化后的功能,大部分都是自己的想法或者AI的想法不一定对
注解结构组成部分参考了源码
大部分是通过跟chatgpt不断地交互问答中完成的,外加一小部分看一些博客和工具文档完成的,但是AI感觉还是只能完成一个特别小的DEMO,很难整合到一块甚至部分代码是瞎编的。
这里其实我是先完成了控制层注解的书写,在完成IOC容器的书写的
学习见解:
就是现在这个时代写代码,在我看来有了AI后上手一个包会特别快,并且他还能给你提供一些你重来没有学习过的思路和框架,这点相当不错。感觉可以把当作查文档的,找思路的工具。
缺点:
- 但是很多情况下其实我们是不懂自己的需求是啥的也不懂需要的东西,于是需要不断调教AI,但是AI有时候又特别倔,很容易不断重复之前的思路
- 同时随着我们的功能越来越来,架构越来越大,前后模块之间相互依赖,这时候能够很明显地感觉AI记忆就不太够了,很难给你串起来,他很难把握整个全局
感觉现在最重要的就是一种的逻辑思维的能力,一种能把握全局的能力,你在不断使用AI中你需要对的项目越来越来清晰,不能被AI绕进去,形成一套自己思路,然后分成一个个模块,一个个慢慢完成
实现:
基于
servlet
、reflections
反射框架、fastjson
实现,运用注解和反射,以及切面编程AOP的思想实现SpringMVC
部分基础注解的简化版本功能然后为了偷懒用了点
Lombok
只讲讲原理代码具体看仓库中的
maven那边相关的依赖就先不说了
控制层部分
内容
包含注解(控制层相关注解的简化
-
@RestController,@RequestMapping,@请求方式Mapping
-
请求方法接受方法相关注解:@RequestBody,@RequestParam
实现思路
例子:
package com.wawu.common.httpHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
//TODO 待优化:请求路径处理冗余了
// 这里我规定/api,还会进行中心代理,返回json
@WebServlet("/api/*")
public class AopServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
/api/*
表示所有以/api/
开头的请求都将被AopServlet
拦截。service
方法:重写了HttpServlet
的service
方法,这个方法是处理所有 HTTP 请求的入口(包括 GET、POST 等)。
你会发先这个类能够统一出例当前路径下所有的请求,这里就可以用一个中心代理的思想(其实就是AOP面向切面编程),统一的处理的请求响应,将他分法到到对应的 请求控制层类 中
- 这里就引申出另一个问题,该如何分发呢?这里能够想到一个数据结构就是字典结构(
key
的为请求方式+路径;value
为其对应的调用方法),通过请求方式和路径进行对应的方法分发。 - 这里方法使用代理转发的方式,也能统一对请求的参数进行处理,作为参数传入到当前方法;获取当前方法的响应结构后,就可以统一用
json
格式放回
这里很类似前端的
vue
和nodejs
后端部分框架的路由表,尽管不知道具体实现方式
代码相关
本功能相关注解及其枚举类:
IOC容器:
简单介绍:
- 控制反转:传统上,对象由调用者自己创建并管理依赖关系,而 IOC 则将这种控制权“反转”给框架,使得框架负责对象的创建和管理。
- 依赖注入:IOC 容器通过构造器、setter 方法、或字段注入将所需依赖注入到对象中。
内容
@Component
及其相关注解(@RestController
、@Service
等)- 完成
@Autowired
规则:
- 需要注册为
@Component
的类(其实就是bean
对象),他的属性才能够被依赖注入 - 只扫描同包及其子包下的类(类似
SpringMVC
的启动类) - 这里默认是单例(多例还没有实现)
问题:
- 注解扫描注解时,大部分方法不会扫描注解上的注解
实现思路
-
启动类设置:使用的是
Servlet
的ServletContextListener
监听器(关键点:ServletContext
对象会在 Tomcat 启动时自动创建,且生命周期贯穿整个应用程序的运行期。),可以把这个作为启动类,以这个类所在的包进行扫描。在这里初始化IOC容器 -
IOC容器(直接叫做
ApplicationContext
):完成对Bean
对象扫描,同时进行依赖注入简化步骤:
-
获取所有自定义注解,并筛选出需要注册为
Bean
对象的注解(使用的自己完成了一个能够递归扫描注解的工具类,递归扫描是否被@Component
修饰) -
扫描并注册组件类:用
Reflections
扫描出这些相关注解,将这个Bean
对象实例化注册 -
执行依赖注入:扫描这些
Bean
对象,寻找字段被@Autowired
修饰,如果被修饰,则将Bean
对象注入这里可能出现接口,所有还需要特殊处理接口的实现类,当接口恰好有且只有一个
bean
对象作为实现类,则注入
ApplicationContext
这个是AI取得名字,感觉这里完成只是简化版本 -