手写一个山寨版的springmvc框架
首先贴出来一张从网上copy下来的springmvc
工作原理图
分析:其实springmvc
只不过是把servlet
进行了封装和处理,下面就开始手写一个简易版的springmvc
框架。另外,客户端发送一个请求到前端控制器DispatcherServlet
,所谓的前端控制器只是封装后的一个servlet
,这个servlet
接收到请求后在初始化init()
方法中要做下面几件事情。
- 扫描所有的类
- 反射实例化controller、service类,放入ioc容器
- 注入autowired处理
- 请求路径映射处理
一,环境准备
- eclipse
- servlet-api.jar
- tomcat
二,项目结构搭建
在写之前要把准备工作都做好,项目结构如下图
在com.qianlong.zhujie
包中要把springmvc
框架中的常用注解都写进去,当然是我们的自定义注解了,山寨版的嘛!
DnAutowired
注解
@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.FIELD)//限定作用域,限制自定义的注解只能放在属性上面
public @interface DnAutowired {
String value() default "";
}
DnController
注解
@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.TYPE)//限定作用域,限制自定义的注解只能放在类上面
public @interface DnController {
String value() default "";
}
DnRequestMapping
注解
@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target({ElementType.TYPE,ElementType.METHOD})//限定作用域,放在类上面和方法上面
public @interface DnRequestMapping {
String value() default "";
}
DnRequestParam
注解
@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.PARAMETER)//限定作用域,限制自定义的注解只能放在参数上面
public @interface DnRequestParam {
String value() default "";
}
DnService
注解
@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target(ElementType.TYPE)//限定作用域,限制自定义的注解只能放在类上面
public @interface DnService {
String value() default "";
}
三,简易版的前端控制器 DnDispatcherServlet
在com.qianlong.servlet
包里新建servlet
文件名为DnDispatcherServlet
并继承HttpServlet
,也就是前端控制器。
然后在pom.xml
文件中加入servlet
配置,加入配置后,tomcat
一启动就进到了这个servlet
。
<servlet>
<servlet-name>DnDispathcherServlet</servlet-name>
<servlet-class>com.qianlong.servlet.DnDispathcherServlet</servlet-class>
<load-on-startup>1</load-on-startup><!--tomcat已启动首先进入这个servlet-->
</servlet>
<servlet-mapping>
<servlet-name>DnDispathcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
然后开始写DnDispatcherServlet
的内容。
在init()
方法中写准备工作,在doPost()
方法中处理客户端发来的请求。
package com.qianlong.servlet;
import com.qianlong.controller.TestController;
import com.qianlong.zhujie.*;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DnDispathcherServlet extends HttpServlet {
//扫描到的类的容器
private List<String> classNames=new ArrayList<String>();
//ioc容器,其实就是一个map集合
private Map<String,Object> beans=new HashMap<String, Object>();
//存放urlMapping的集合
private Map<String,Object> urlMapping=new HashMap<String, Object>();
@Override
public void init() throws ServletException {
//1,扫描类
doScanPackage("com.qianlong");
//2,反射实例化controller、service类,放入ioc容器
doInstance();
//3,注入autowired
doAutowired();
//4,请求路径映射
doUrlMapping();
}
/**
* 请求路径映射
*/
private void doUrlMapping() {
try{
//另一种遍历map的方式
for(Map.Entry<String,Object> entry:beans.entrySet()){
//从ioc容器beans中拿到实例化的controller类和service类的对象
Object instance = entry.getValue();
//实例化的对象得到类
Class<?> clazz = instance.getClass();
//如果该类被DnController注解
if(clazz.isAnnotationPresent(DnController.class)){
//得到DnRequestMapping注解在方法上的映射值
DnRequestMapping classAnnotation=clazz.getAnnotation(DnRequestMapping.class);
String classPath = classAnnotation.value();
//得到该类中所有的方法
Method[] methods = clazz.getDeclaredMethods();
//遍历所有方法
for(Method method:methods){
//得到所有方法上面DnRequestMapping注解的映射值
DnRequestMapping methodAnnotation=method.getAnnotation(DnRequestMapping.class);
String methodPath = methodAnnotation.value();
//把映射拼接到一起放到urlMapping集合里
urlMapping.put(classPath+methodPath,method);
}
}
}
}catch (Exception e){
}
}
/**
* 注入autowired
*/
private void doAutowired() {
try {
//另一种遍历map的方式
for(Map.Entry<String,Object> entry:beans.entrySet()){
//从ioc容器beans中拿到实例化的controller类和service类的对象
Object instance = entry.getValue();
//实例化的对象得到类
Class<?> clazz = instance.getClass();
//如果该类被DnController注解
if(clazz.isAnnotationPresent(DnController.class)){
//得到该类中所有的属性
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields){
//如果该属性被DnAutowired注解
if(field.isAnnotationPresent(DnAutowired.class)){
//拿到这个注解的值
DnAutowired annotation = field.getAnnotation(DnAutowired.class);
String key=annotation.value();
//私有属性强制控制
field.setAccessible(true);
//给属性赋值,set的两个参数(本属性属于哪个类,值)
field.set(instance,beans.get(key));
}
}
}
}
}catch (Exception e){
}
}
/**
* 反射实例化controller、service类,放入ioc容器
*/
private void doInstance(){
try{
for(String className:classNames){
//拿到类的全路径名,去掉后缀名
String cn = className.replace(".class", "");
//得到类
Class<?> clazz = Class.forName(cn);
//实例化controller类和service类
//如果该类被@DnController注解了
if(clazz.isAnnotationPresent(DnController.class)){
//实例化(创建此 Class 对象所表示的类的一个新实例)
Object controllerInstance = clazz.newInstance();
//拿到@DnRequestMapping注解的值
DnRequestMapping annotation=clazz.getAnnotation(DnRequestMapping.class);
String key = annotation.value();
//放到ioc容器中
beans.put(key,controllerInstance);
//如果该类被@DnService注解了
}else if(clazz.isAnnotationPresent(DnService.class)){
//实例化
Object serviceInstance = clazz.newInstance();
//拿到@DnService注解的值
DnService annotation=clazz.getAnnotation(DnService.class);
String key = annotation.value();
//放到ioc容器中
beans.put(key,serviceInstance);
}
}
}catch (Exception e){
}
}
/**
* 扫描类
* @param packages
*/
private void doScanPackage(String packages) {
//拿到包路径 ,path以'/'开头时,则是从项目的ClassPath根下获取资源,返回一个url
URL url = this.getClass().getClassLoader().getResource("/" + packages.replaceAll("\\.", "/"));
//获取此 URL 的文件名(这里是包名com.qianlong)
String files = url.getFile();
//创建流对象
File fileAll=new File(files);
//对该包下的文件判断
for(File file:fileAll.listFiles()){
if(file.isDirectory()){
//递归扫描
doScanPackage(packages+"."+file.getName());
}else{
//找到class类并且保存
classNames.add(packages+"."+file.getName());//包名+全类路径名 com.qianlong.controller
}
}
}
/**
* 接收前台请求,对请求进行处理
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try{
// 得到的路径是 /HandToSpringMvc/test/first
String uri=request.getRequestURI();
// /HandToSpringMvc
String context=request.getContextPath();
// /test/first
String path = uri.replaceAll(context, "");
//得到方法
Method method = (Method) urlMapping.get(path);
TestController instance=(TestController) beans.get("/"+path.split("/")[1]);// /test
//进行参数处理
Object[] args=hand(request,response,method);
/**
* invoke调用方法,参数(该方法属于哪个类,参数)
* jdkAPI解释:
* 如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。
* 如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。
*/
method.invoke(instance,args);
}catch (Exception e){
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
/**
* 获取参数的方法
*/
public static Object[] hand(HttpServletRequest request,HttpServletResponse response,Method method){
//拿到当前待执行的方法有哪些参数
Class<?>[] paramClazzes = method.getParameterTypes();
//根据参数的个数,new一个参数的数组,将方法里的全部参数赋值到args来
Object[] args=new Object[paramClazzes.length];
int index=0;
for(Class<?> paramClazz:paramClazzes){
//isAssignableFrom:判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,如果是则返回 true,否则返回 false
if(ServletRequest.class.isAssignableFrom(paramClazz)){
args[index]=request;
}
//同上
if(ServletResponse.class.isAssignableFrom(paramClazz)){
args[index]=response;
}
//判断有没有RequestParam注解,需要获取参数值
Annotation[] paramAns = method.getParameterAnnotations()[index];
if(paramAns.length>0){
for(Annotation annotation:paramAns){
if(DnRequestParam.class.isAssignableFrom(paramAns.getClass())){
DnRequestParam rp=(DnRequestParam)annotation;
args[index]=request.getParameter(rp.value());
}
}
}
index++;
}
return args;
}
}
然后山寨版的springmvc
框架就诞生了!
四,测试springmvc的性能
写一个TestService
接口
public interface TestService {
void say();
}
TestServiceImpl
实现类
@DnService("haha")
public class TestServiceImpl implements TestService{
@Override
public void say() {
System.out.println("手写springmvc框架实现");
}
}
TestController
类
@DnController
@DnRequestMapping("/test")
public class TestController {
@DnAutowired("haha")
TestService testService;
@DnRequestMapping("/first")
public String query(HttpServletRequest request,HttpServletResponse response) throws IOException{
Person person=new Person();
String s="我叫"+person.getName()+","+"住在"+person.getAddress();
response.setHeader("content-type","text/html;charset=utf-8");
response.getWriter().write(s);
testService.say();
return "";
}
}
然后把项目添加到tomcat
容器并运行
浏览器:
控制台:
五,springboot自定义日志注解
需求
加上自定义日志注解的方法执行时自动打印日志
步骤描述
1,随项目启动扫描所有被日志注解标注的方法
2,打印日志
注解类
@Retention(RetentionPolicy.RUNTIME)//选择运行环境
@Target({ElementType.TYPE, ElementType.METHOD})//限定作用域,放在类上面和方法上面
public @interface MyLog {
String value() default "";
}
随项目启动执行的方法
@Component
public class MyLogger implements ApplicationRunner {
//扫描到的类的容器
private List<String> classNames=new ArrayList<String>();
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("#####################################");
System.out.println(" 初始化自定义日志注解 ");
System.out.println("#####################################");
scanPackage("com.ftx.myannotation");
System.out.println(classNames);
for(String className:classNames){
//拿到类的全路径名,去掉后缀名
String cn = className.replace(".class", "");
Class<?> clazz = Class.forName(cn);
Method[] methods = clazz.getDeclaredMethods();
for(Method method:methods){
if(method.isAnnotationPresent(MyLog.class)){
System.out.println(clazz.getName()+"类的"+method.getName()+"方法已被@MyLog注解标注");
}
}
}
}
//扫描包
public void scanPackage(String packageName){
URL url = this.getClass().getClassLoader().getResource("" + packageName.replaceAll("\\.", "/"));
System.out.println(url);
// file:/D:/IdeaProjects/springboot-myannotation/target/classes/com/ftx/myannotation
String file = url.getFile();
File files=new File(file);
for(File file1:files.listFiles()){
if(file1.isDirectory()){
//递归扫描
scanPackage(packageName+"."+file1.getName());
}else{
classNames.add(packageName+"."+file1.getName());
//包名+类名 com.ftx.myannotation.controller.TestController
}
}
}
}
测试方法
@RestController
public class TestContrller {
@MyLog
@RequestMapping("/test")
public String test(){
return "test";
}
}
启动项目,后台打印
#####################################
初始化自定义日志注解
#####################################
com.ftx.myannotation.controller.TestContrller类的test方法已被@MyLog注解标注
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~