当前位置: 首页 > news >正文

01-spring-手写spring-demo实现基础的功能

1 目标

自定义BonnieController BonnieRequestMapping、BonnieService、BonnieAutowired等注解实现一个简单的springmvc的功能。从浏览器访问url,然后请求到对应的接口,以及service等。

2 流程说明

3、代码获取途径

以下是下载地址以及分支: 如果需要自己开发,可以基于20250808-base分支,这个分支仅搭建好了基础的框架

地址分支
 https://gitee.com/huyanqiu6666/customize-spring.git20250808-springV1.0

4 重点代码说明

4.1 入口web.xml

在web.xml中配置了servlet接收所有请求,调用它的doGet或者doPost方法;服务启动的时候调用DispatcherServlet.init方法,在这里可以做初始化阶段的所有操作。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"version="2.4"><display-name>Bonnie Web Application</display-name><servlet><servlet-name>bonnieMvc</servlet-name><servlet-class>com.bonnie.servlet.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>bonnieMvc</servlet-name><url-pattern>/*</url-pattern></servlet-mapping>
</web-app>

4.2 获取servlet配置init-param信息

// 1、加载配置文件
String contextConfigLocation = config.getInitParameter("contextConfigLocation");
   /*** 加载配置文件 - application.properties* @param contextConfigLocation*/private void doLoadConfig(String contextConfigLocation) {// 加载配置文件流InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);try {contextPropertiesConfig.load(is);} catch (IOException e) {e.printStackTrace();}}

4.3 扫描相关类

/*** 所有类的全路径集合*/
private List<String> classNameList = new ArrayList<>();
String scanPackage = contextPropertiesConfig.get("componentScan").toString();
private void doComponentScan(String scanPackage) {try {/*** scanPackage = com.bonnie, 转化为com/bonnie*/String scanPackagePath = scanPackage.replaceAll("\\.", "/");URL url = this.getClass().getClassLoader().getResource("/" + scanPackagePath);File[] fileArray = new File(url.getFile()).listFiles();for (File file : fileArray) {// 如果是目录,则递归调用doComponentScanif (file.isDirectory()) {String newPackage = scanPackage + "." +  file.getName();doComponentScan(newPackage);} else {if (!file.getName().endsWith(".class")) {continue;}// 类的全路径String classFullName = scanPackage + "." + file.getName().replace(".class", "");System.out.println("扫描的classFullName:" + classFullName);classNameList.add(classFullName);}}} catch (Exception e) {e.printStackTrace();}}

4.4 将类放入到IOC容器中

/*** 简化IOC容器*/
private Map<String,Object> iocMap = new ConcurrentHashMap<>();
 /*** 初始化扫描到的所有类,放到到IOC容器中*/private void doInstance() {if (CollectionUtils.isEmpty(classNameList)) {return;}try {for (String className : classNameList) {Class<?> clazz = Class.forName(className);/*** 只有被自定义注解的类才可以被加载到IOC容器中*/if (clazz.isAnnotationPresent(BonnieController.class)) {IocLoadUtils.loadController(clazz, iocMap);System.out.println("ioc 扫描到: " + clazz.getName());} else if (clazz.isAnnotationPresent(BonnieService.class)) {IocLoadUtils.loadService(clazz, iocMap);System.out.println("ioc 扫描到: " + clazz.getName());} else {continue;}}} catch (Exception e) {e.printStackTrace();}}
package com.bonnie.utils;import com.bonnie.annotations.BonnieService;
import org.apache.commons.lang3.StringUtils;import java.util.Map;public class IocLoadUtils {public static void loadController(Class<?> clazz, Map<String, Object> iocMap) throws InstantiationException, IllegalAccessException {Object object = clazz.newInstance();String beanName = toLowerFirstCase(clazz.getSimpleName());iocMap.put(beanName, object);}public static void loadService(Class<?> clazz, Map<String, Object> iocMap) throws InstantiationException, IllegalAccessException {BonnieService bonnieService = clazz.getAnnotation(BonnieService.class);String beanName = bonnieService.value().trim();if (StringUtils.isEmpty(beanName)) {beanName = toLowerFirstCase(clazz.getSimpleName());}Object object = clazz.newInstance();iocMap.put(beanName, object);// 根据类型自动赋值 接口for (Class<?> classInterface : clazz.getInterfaces()) {if (iocMap.containsKey(classInterface.getName())) {throw new RuntimeException(classInterface.getName() + " 已经在IOCMap中");}iocMap.put(classInterface.getName(), object);}}private static String toLowerFirstCase(String simpleName) {char [] chars = simpleName.toCharArray();//之所以加,是因为大小写字母的ASCII码相差32,// 而且大写字母的ASCII码要小于小写字母的ASCII码//在Java中,对char做算学运算,实际上就是对ASCII码做算学运算chars[0] += 32;return String.valueOf(chars);}}

4.5 依赖注入

/*** 依赖注入*/private void doBonnieAutowired() {if (iocMap.isEmpty()) {return;}Set<Map.Entry<String, Object>> entries = iocMap.entrySet();for (Map.Entry<String, Object> entry : entries) {Object beanInstance = entry.getValue();// 获取类中的所有字段(成员变量)Field[] declaredFields = beanInstance.getClass().getDeclaredFields();for (Field field : declaredFields) {if (!field.isAnnotationPresent(BonnieAutowired.class)) {continue;}// 如果指定bean名称,则使用指定的,否则使用类的名称String autowiredBeanName = field.getAnnotation(BonnieAutowired.class).value().trim();if (StringUtils.isEmpty(autowiredBeanName)) {autowiredBeanName = field.getType().getName();}if (!iocMap.containsKey(autowiredBeanName)) {throw new RuntimeException("Ioc容器中不存在类,无法依赖注入:" + autowiredBeanName);}// 开启暴力访问field.setAccessible(true);try {// 用反射机制,动态给字段赋值field.set(beanInstance, iocMap.get(autowiredBeanName));} catch (IllegalAccessException e) {e.printStackTrace();}}}}

4.6 初始化handlerMapping

/*** requestHandler集合*/
private List<Handler> requestHandlerMapping = new ArrayList<Handler>();
/*** 初始化HandlerMapping*/private void initHandlerMapping() {if (iocMap.isEmpty()) {return;}Set<Map.Entry<String, Object>> entries = iocMap.entrySet();for (Map.Entry<String, Object> entry : entries) {Class<?> beanClazz = entry.getValue().getClass();// 只扫描被注解修饰的类BonnieControllerif (!beanClazz.isAnnotationPresent(BonnieController.class)) {continue;}String baseUrl = StringUtils.EMPTY;if (beanClazz.isAnnotationPresent(BonnieRequestMapping.class)) {BonnieRequestMapping requestMapping = beanClazz.getAnnotation(BonnieRequestMapping.class);baseUrl = requestMapping.value();}// 获取所有的public方法Method[] methods = beanClazz.getMethods();for (Method method : methods) {BonnieRequestMapping methodRequestMapping = method.getAnnotation(BonnieRequestMapping.class);if (Objects.isNull(methodRequestMapping)) {// 先不抛出异常continue;}String regex = ("/" + baseUrl + "/" + methodRequestMapping.value()).replaceAll("/+","/");Pattern pattern = Pattern.compile(regex);System.out.println("扫描到的请求url: " + pattern);Handler handler = new Handler();handler.setPattern(pattern);handler.setController(entry.getValue());handler.setMethod(method);handler.setParamTypes(method.getParameterTypes());handler.putParamIndexMapping(method);requestHandlerMapping.add(handler);}}}

4.7 运行阶段

doGet方法使用doPost方法

  @Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}
@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {try {// 真实处理业务请求doDispatch(req, resp);} catch (Exception e) {e.printStackTrace();}}
 /*** 处理请求:*  1、解析请求的url获取对应的handler*  2、解析请求的入参,参数列表,参数类型 以及request response*  3、反射调用获取结果*  4、返回对应的结果到前端* @param req* @param resp* @throws Exception*/private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {// 获取对应的handlerHandler handler = RequestHandlerMappingUtils.getHandler(req, requestHandlerMapping);if (Objects.isNull(handler)) {resp.getWriter().write("404 Not Found!!!");return;}// 方法的形参列表Class<?>[] paramTypes = handler.getParamTypes();// 定义参数列表Object [] paramValues = new Object[paramTypes.length];Map<String,String[]> parameterMap = req.getParameterMap();for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","").replaceAll("\\s",",");Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();if (!paramIndexMapping.containsKey(param.getKey())) {continue;}int index = handler.getParamIndexMapping().get(param.getKey());paramValues[index] = convert(paramTypes[index],value);}if(handler.getParamIndexMapping().containsKey(HttpServletRequest.class.getName())) {int reqIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getName());paramValues[reqIndex] = req;}if(handler.getParamIndexMapping().containsKey(HttpServletResponse.class.getName())) {int respIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getName());paramValues[respIndex] = resp;}// 反射调用,获取结果Object returnValue = handler.getMethod().invoke(handler.getController(),paramValues);if (returnValue == null || returnValue instanceof Void){return;}resp.getWriter().write(returnValue.toString());}private Object convert(Class<?> paramType, String value) {//如果是intif(Integer.class == paramType){return Integer.valueOf(value);}else if(Double.class == paramType){return Double.valueOf(value);}else if (Long.class == paramType) {return Long.parseLong(value);}//如果还有double或者其他类型,继续加if// 可以使用策略模式了return value;}
package com.bonnie.utils;import com.bonnie.entity.Handler;import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.regex.Matcher;public class RequestHandlerMappingUtils {/*** 获取请求对应的handler** @param req* @param requestHandlerMapping* @return*/public static Handler getHandler(HttpServletRequest req, List<Handler> requestHandlerMapping) {// 绝对路径String requestURI = req.getRequestURI();String contextPath = req.getContextPath();// 相对路径String url = requestURI.replaceAll(contextPath, "").replaceAll("/+", "/");for (Handler handler : requestHandlerMapping) {Matcher matcher = handler.getPattern().matcher(url);if (!matcher.matches()) {continue;}return handler;}return null;}}

5 运行结果

5.1 测试类

package com.bonnie.controller;import com.bonnie.annotations.BonnieAutowired;
import com.bonnie.annotations.BonnieController;
import com.bonnie.annotations.BonnieRequestMapping;
import com.bonnie.annotations.BonnieRequestParam;
import com.bonnie.service.UserService;@BonnieController
@BonnieRequestMapping("/user")
public class UserController {@BonnieAutowiredprivate UserService userService;@BonnieRequestMapping("/user")public String findById(@BonnieRequestParam("id") Long id) {return userService.findById(id);}}
package com.bonnie.service;import com.bonnie.annotations.BonnieService;import java.text.SimpleDateFormat;
import java.util.Date;@BonnieService
public class UserServiceImpl implements UserService{@Overridepublic String findById(Long id) {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String now = simpleDateFormat.format(new Date());return "userId:" + id + " Date: "+ now;}}

5.2 启动

启动部分日志

扫描的classFullName:com.bonnie.annotations.BonnieAutowired
扫描的classFullName:com.bonnie.annotations.BonnieController
扫描的classFullName:com.bonnie.annotations.BonnieRequestMapping
扫描的classFullName:com.bonnie.annotations.BonnieRequestParam
扫描的classFullName:com.bonnie.annotations.BonnieService
扫描的classFullName:com.bonnie.controller.UserController
扫描的classFullName:com.bonnie.entity.Handler
扫描的classFullName:com.bonnie.service.UserService
扫描的classFullName:com.bonnie.service.UserServiceImpl
扫描的classFullName:com.bonnie.servlet.DispatcherServlet
扫描的classFullName:com.bonnie.servlet.MyServlet
扫描的classFullName:com.bonnie.utils.IocLoadUtils
扫描的classFullName:com.bonnie.utils.RequestHandlerMappingUtils
ioc 扫描到: com.bonnie.controller.UserController
ioc 扫描到: com.bonnie.service.UserServiceImpl
扫描到的请求url: /user/user
Bonnie Spring framework is init.
11-Aug-2025 15:01:38.596 信息 [main] org.apache.catalina.startup.HostConfig.deployDescriptor 部署描述符[C:\Users\L14\.SmartTomcat\customize-spring\customize-spring\conf\Catalina\localhost\customize-spring.xml]的部署已在[2,704]ms内完成
11-Aug-2025 15:01:38.618 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["http-nio-8080"]
11-Aug-2025 15:01:38.873 信息 [main] org.apache.catalina.startup.Catalina.start [3255]毫秒后服务器启动
http://localhost:8080/customize-spring

5.3 运行结果

http://www.dtcms.com/a/326446.html

相关文章:

  • SAM2的应用
  • 机器学习中数据集的划分难点及实现
  • 比例份额调度
  • CV 医学影像分类、分割、目标检测,之【血红细胞分类】项目拆解
  • n8n中调用playwright-mcp 项目
  • LeetCode151~188题解
  • C++ 流式处理字符串
  • C语言变量的声明和定义有什么区别?
  • UE 手柄点击UI 事件
  • 长难句lesson1
  • PPIO上线智谱GLM-4.5V
  • 【stm32】EXTI外部中断
  • QT聊天项目DAY18
  • Prompt Engineering 最佳实践:让AI输出更精准的核心技巧
  • HIS系统:医院信息化建设的核心,采用Angular+Java技术栈,集成MySQL、Redis等技术,实现医院全业务流程管理。
  • LS1043A+AQR115C万兆网口调试
  • 机器学习第九课之DBSCAN算法
  • 下一代防火墙组网全解析
  • Linux下安装jdk
  • 从零构建企业级K8S:高可用集群部署指南
  • 简单了解MongoDB数据存储
  • 计算机网络---交换机
  • Excel导入mysql,带小数点如何解决?
  • 物联网通讯协议-MQTT、Modbus、OPC
  • 支持向量机SM
  • 人工智能-python-机器学习-线性回归与梯度下降:理论与实践
  • 大屏幕自适应
  • 基于FPGA的热电偶测温数据采集系统,替代NI的产品(三)测试
  • C++ STL | STL迭代器(lterator)
  • 阿里千问系列:Qwen3 强化学习新算法GSPO!