news 2026/4/16 10:14:18

(21)手写Spring框架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
(21)手写Spring框架

Spring IoC容器的实现原理:工厂模式 + 解析XML + 反射机制。
我们给自己的框架起名为:myspring(我的春天)

第一步:创建模块myspring

采用Maven方式新建Module:myspring

打包方式采用jar,并且引入dom4j和jaxen的依赖,因为要使用它解析XML文件,还有junit依赖。

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.myspringframework</groupId><artifactId>myspring</artifactId><version>1.0.0</version><packaging>jar</packaging><dependencies><dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</version></dependency><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.2.0</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency></dependencies><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties></project>

第二步:准备好我们要管理的Bean

准备好我们要管理的Bean(这些Bean在将来开发完框架之后是要删除的
注意包名,不要用org.myspringframework包,因为这些Bean不是框架内置的。是将来使用我们框架的程序员提供的。

packagecom.powernode.myspring.bean;/** * @author 动力节点 * @version 1.0 * @className Address * @since 1.0 **/publicclassAddress{privateStringcity;privateStringstreet;privateStringzipcode;publicAddress(){}publicStringgetCity(){returncity;}publicvoidsetCity(Stringcity){this.city=city;}publicStringgetStreet(){returnstreet;}publicvoidsetStreet(Stringstreet){this.street=street;}publicStringgetZipcode(){returnzipcode;}publicvoidsetZipcode(Stringzipcode){this.zipcode=zipcode;}@OverridepublicStringtoString(){return"Address{"+"city='"+city+'\''+", street='"+street+'\''+", zipcode='"+zipcode+'\''+'}';}}
packagecom.powernode.myspring.bean;/** * @author 动力节点 * @version 1.0 * @className User * @since 1.0 **/publicclassUser{privateStringname;privateintage;privateAddressaddr;publicUser(){}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicintgetAge(){returnage;}publicvoidsetAge(intage){this.age=age;}publicAddressgetAddr(){returnaddr;}publicvoidsetAddr(Addressaddr){this.addr=addr;}@OverridepublicStringtoString(){return"User{"+"name='"+name+'\''+", age="+age+", addr="+addr+'}';}}

第三步:准备myspring.xml配置文件

将来在框架开发完毕之后,这个文件也是要删除的。因为这个配置文件的提供者应该是使用这个框架的程序员。
文件名随意,我们这里叫做:myspring.xml
文件放在类路径当中即可,我们这里把文件放到类的根路径下。

<?xml version="1.0" encoding="UTF-8"?><beans><beanid="userBean"class="com.powernode.myspring.bean.User"><propertyname="name"value="张三"/><propertyname="age"value="20"/><propertyname="addr"ref="addrBean"/></bean><beanid="addrBean"class="com.powernode.myspring.bean.Address"><propertyname="city"value="北京"/><propertyname="street"value="大兴区"/><propertyname="zipcode"value="1000001"/></bean></beans>

使用value给简单属性赋值。使用ref给非简单属性赋值。

第四步:编写ApplicationContext接口

ApplicationContext接口中提供一个getBean()方法,通过该方法可以获取Bean对象。
注意包名:这个接口就是myspring框架中的一员了。

packageorg.myspringframework.core;/** * @author 动力节点 * @version 1.0 * @className ApplicationContext * @since 1.0 **/publicinterfaceApplicationContext{/** * 根据bean的id获取bean实例。 * @param beanId bean的id * @return bean实例 */ObjectgetBean(StringbeanId);}

第五步:编写ClassPathXmlApplicationContext

ClassPathXmlApplicationContext是ApplicationContext接口的实现类。该类从类路径当中加载myspring.xml配置文件。

packageorg.myspringframework.core;/** * @author 动力节点 * @version 1.0 * @className ClassPathXmlApplicationContext * @since 1.0 **/publicclassClassPathXmlApplicationContextimplementsApplicationContext{@OverridepublicObjectgetBean(StringbeanId){returnnull;}}

第六步:确定采用Map集合存储Bean

确定采用Map集合存储Bean实例。Map集合的key存储beanId,value存储Bean实例。Map<String,Object>
在ClassPathXmlApplicationContext类中添加Map<String,Object>属性。
并且在ClassPathXmlApplicationContext类中添加构造方法,该构造方法的参数接收myspring.xml文件。
同时实现getBean方法。

packageorg.myspringframework.core;importjava.util.HashMap;importjava.util.Map;/** * @author 动力节点 * @version 1.0 * @className ClassPathXmlApplicationContext * @since 1.0 **/publicclassClassPathXmlApplicationContextimplementsApplicationContext{/** * 存储bean的Map集合 */privateMap<String,Object>beanMap=newHashMap<>();/** * 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。 * @param resource 配置文件路径(要求在类路径当中) */publicClassPathXmlApplicationContext(Stringresource){}@OverridepublicObjectgetBean(StringbeanId){returnbeanMap.get(beanId);}}

第七步:解析配置文件实例化所有Bean

在ClassPathXmlApplicationContext的构造方法中解析配置文件,获取所有bean的类名,通过反射机制调用无参数构造方法创建Bean。并且将Bean对象存放到Map集合中。

/** * 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。 * @param resource 配置文件路径(要求在类路径当中) */publicClassPathXmlApplicationContext(Stringresource){try{SAXReaderreader=newSAXReader();Documentdocument=reader.read(ClassLoader.getSystemClassLoader().getResourceAsStream(resource));// 获取所有的bean标签List<Node>beanNodes=document.selectNodes("//bean");// 遍历集合beanNodes.forEach(beanNode->{ElementbeanElt=(Element)beanNode;// 获取idStringid=beanElt.attributeValue("id");// 获取classNameStringclassName=beanElt.attributeValue("class");try{// 通过反射机制创建对象Class<?>clazz=Class.forName(className);Constructor<?>defaultConstructor=clazz.getDeclaredConstructor();Objectbean=defaultConstructor.newInstance();// 存储到Map集合beanMap.put(id,bean);}catch(Exceptione){e.printStackTrace();}});}catch(Exceptione){e.printStackTrace();}}

第八步:测试能否获取到Bean

编写测试程序。

packagecom.powernode.myspring.test;importorg.junit.Test;importorg.myspringframework.core.ApplicationContext;importorg.myspringframework.core.ClassPathXmlApplicationContext;/** * @author 动力节点 * @version 1.0 * @className MySpringTest * @since 1.0 **/publicclassMySpringTest{@TestpublicvoidtestMySpring(){ApplicationContextapplicationContext=newClassPathXmlApplicationContext("myspring.xml");ObjectuserBean=applicationContext.getBean("userBean");ObjectaddrBean=applicationContext.getBean("addrBean");System.out.println(userBean);System.out.println(addrBean);}}

通过测试Bean已经实例化成功了,属性的值是null,这是我们能够想到的,毕竟我们调用的是无参数构造方法,所以属性都是默认值。
下一步就是我们应该如何给Bean的属性赋值呢?

第九步:给Bean的属性赋值

通过反射机制调用set方法,给Bean的属性赋值。
继续在ClassPathXmlApplicationContext构造方法中编写代码。

packageorg.myspringframework.core;importorg.dom4j.Document;importorg.dom4j.Element;importorg.dom4j.Node;importorg.dom4j.io.SAXReader;importjava.lang.reflect.Constructor;importjava.lang.reflect.Method;importjava.util.HashMap;importjava.util.List;importjava.util.Map;/** * @author 动力节点 * @version 1.0 * @className ClassPathXmlApplicationContext * @since 1.0 **/publicclassClassPathXmlApplicationContextimplementsApplicationContext{/** * 存储bean的Map集合 */privateMap<String,Object>beanMap=newHashMap<>();/** * 在该构造方法中,解析myspring.xml文件,创建所有的Bean实例,并将Bean实例存放到Map集合中。 * @param resource 配置文件路径(要求在类路径当中) */publicClassPathXmlApplicationContext(Stringresource){try{SAXReaderreader=newSAXReader();Documentdocument=reader.read(ClassLoader.getSystemClassLoader().getResourceAsStream(resource));// 获取所有的bean标签List<Node>beanNodes=document.selectNodes("//bean");// 遍历集合(这里的遍历只实例化Bean,不给属性赋值。为什么要这样做?)beanNodes.forEach(beanNode->{ElementbeanElt=(Element)beanNode;// 获取idStringid=beanElt.attributeValue("id");// 获取classNameStringclassName=beanElt.attributeValue("class");try{// 通过反射机制创建对象Class<?>clazz=Class.forName(className);Constructor<?>defaultConstructor=clazz.getDeclaredConstructor();Objectbean=defaultConstructor.newInstance();// 存储到Map集合beanMap.put(id,bean);}catch(Exceptione){e.printStackTrace();}});// 再重新遍历集合,这次遍历是为了给Bean的所有属性赋值。// 思考:为什么不在上面的循环中给Bean的属性赋值,而在这里再重新遍历一次呢?// 通过这里你是否能够想到Spring是如何解决循环依赖的:实例化和属性赋值分开。beanNodes.forEach(beanNode->{ElementbeanElt=(Element)beanNode;// 获取bean的idStringbeanId=beanElt.attributeValue("id");// 获取所有property标签List<Element>propertyElts=beanElt.elements("property");// 遍历所有属性propertyElts.forEach(propertyElt->{try{// 获取属性名StringpropertyName=propertyElt.attributeValue("name");// 获取属性类型Class<?>propertyType=beanMap.get(beanId).getClass().getDeclaredField(propertyName).getType();// 获取set方法名StringsetMethodName="set"+propertyName.toUpperCase().charAt(0)+propertyName.substring(1);// 获取set方法MethodsetMethod=beanMap.get(beanId).getClass().getDeclaredMethod(setMethodName,propertyType);// 获取属性的值,值可能是value,也可能是ref。// 获取valueStringpropertyValue=propertyElt.attributeValue("value");// 获取refStringpropertyRef=propertyElt.attributeValue("ref");ObjectpropertyVal=null;if(propertyValue!=null){// 该属性是简单属性StringpropertyTypeSimpleName=propertyType.getSimpleName();switch(propertyTypeSimpleName){case"byte":case"Byte":propertyVal=Byte.valueOf(propertyValue);break;case"short":case"Short":propertyVal=Short.valueOf(propertyValue);break;case"int":case"Integer":propertyVal=Integer.valueOf(propertyValue);break;case"long":case"Long":propertyVal=Long.valueOf(propertyValue);break;case"float":case"Float":propertyVal=Float.valueOf(propertyValue);break;case"double":case"Double":propertyVal=Double.valueOf(propertyValue);break;case"boolean":case"Boolean":propertyVal=Boolean.valueOf(propertyValue);break;case"char":case"Character":propertyVal=propertyValue.charAt(0);break;case"String":propertyVal=propertyValue;break;}setMethod.invoke(beanMap.get(beanId),propertyVal);}if(propertyRef!=null){// 该属性不是简单属性setMethod.invoke(beanMap.get(beanId),beanMap.get(propertyRef));}}catch(Exceptione){e.printStackTrace();}});});}catch(Exceptione){e.printStackTrace();}}@OverridepublicObjectgetBean(StringbeanId){returnbeanMap.get(beanId);}}

重点处理:当property标签中是value怎么办?是ref怎么办?

第十步:打包发布

将多余的类以及配置文件删除,使用maven打包发布。

第十一步:站在程序员角度使用myspring框架

新建模块:myspring-test

引入myspring框架的依赖:

<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.powernode</groupId><artifactId>myspring-test</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><dependencies><dependency><groupId>org.myspringframework</groupId><artifactId>myspring</artifactId><version>1.0.0</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency></dependencies><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties></project>

编写Bean

packagecom.powernode.myspring.bean;/** * @author 动力节点 * @version 1.0 * @className UserDao * @since 1.0 **/publicclassUserDao{publicvoidinsert(){System.out.println("UserDao正在插入数据");}}
packagecom.powernode.myspring.bean;/** * @author 动力节点 * @version 1.0 * @className UserService * @since 1.0 **/publicclassUserService{privateUserDaouserDao;publicvoidsetUserDao(UserDaouserDao){this.userDao=userDao;}publicvoidsave(){System.out.println("UserService开始执行save操作");userDao.insert();System.out.println("UserService执行save操作结束");}}

编写myspring.xml文件

<?xml version="1.0" encoding="UTF-8"?><beans><beanid="userServiceBean"class="com.powernode.myspring.bean.UserService"><propertyname="userDao"ref="userDaoBean"/></bean><beanid="userDaoBean"class="com.powernode.myspring.bean.UserDao"/></beans>

编写测试程序

packagecom.powernode.myspring.test;importcom.powernode.myspring.bean.UserService;importorg.junit.Test;importorg.myspringframework.core.ApplicationContext;importorg.myspringframework.core.ClassPathXmlApplicationContext;/** * @author 动力节点 * @version 1.0 * @className MySpringTest * @since 1.0 **/publicclassMySpringTest{@TestpublicvoidtestMySpring(){ApplicationContextapplicationContext=newClassPathXmlApplicationContext("myspring.xml");UserServiceuserServiceBean=(UserService)applicationContext.getBean("userServiceBean");userServiceBean.save();}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/12 22:35:05

MATLAB中两种常用的纹理特征提取方法:灰度共生矩阵和灰度差分统计

1. 灰度共生矩阵 灰度共生矩阵是迄今为止最经典、最常用的纹理分析方法。它通过计算图像中特定方向和距离的像素对出现的频率来描述纹理。 原理简介 GLCM是一个方阵&#xff0c;其大小由图像的最大灰度级决定。矩阵中的元素 P(i, j | d, θ) 表示在给定空间距离 d 和方向 θ 时…

作者头像 李华
网站建设 2026/3/31 4:27:06

2025年12月9日,OpenAI发布ChatGPT-5.2:未来已经到来,AI改变生活

2025年12月9日&#xff0c;OpenAI迎来了一个重磅发布——ChatGPT-5.2。作为继ChatGPT-5.0之后的又一重要版本更新&#xff0c;5.2不仅带来了更强的技术功能&#xff0c;还让人工智能在各个领域的应用变得更加深入人心。通过强大的多模态能力、超高的情感理解、无缝的跨行业适配…

作者头像 李华
网站建设 2026/4/16 7:03:57

.NET 10 网络堆栈深度架构解析:HTTP/3、性能优化与后量子加密的融合演进

T 10 的发布&#xff0c;微软不仅是在更新一个开发框架&#xff0c;更是在重新定义云原生时代的网络通信标准。本次更新的核心理念紧扣“更现代、更高效、更开发者友好”的三大支柱&#xff0c;标志着.NET 网络堆栈从传统的 TCP/IP 依赖向以 UDP 为基础的 QUIC 协议、后量子加密…

作者头像 李华
网站建设 2026/4/15 17:11:29

有哪些永久免费进销存出入库管理系统?推荐象过河软件

对于中小微企业和个体商户而言&#xff0c;进销存出入库管理是经营的核心环节&#xff0c;可传统手工记录模式易出现数据错漏、库存积压或缺货的问题&#xff0c;而付费进销存系统又会增加经营成本&#xff0c;因此不少商家都在寻找永久免费的进销存出入库管理系统。2025 年&am…

作者头像 李华
网站建设 2026/4/10 21:01:41

Python+Vue的基于推荐算法的在线课程推荐系统 Pycharm django flask

收藏关注不迷路&#xff01;&#xff01;需要的小伙伴可以发链接或者截图给我 项目介绍 本系统共有管理员,用户2个角色&#xff0c;具体功能如下&#xff1a; 1.管理员角色的功能主要包括管理员登录&#xff0c;用户管理&#xff0c;课程信息管理&#xff0c;课程类型管理&…

作者头像 李华
网站建设 2026/4/15 15:03:06

etcd核心架构与设计原理简单介绍

1. etcd是什么&#xff1f;etcd是一个分布式、可靠、一致的键值存储系统&#xff0c;专门用于保存分布式系统中的关键数据&#xff0c;并提供可靠的分布式协调服务。2. etcd的核心架构// etcd的层次化架构模型 type EtcdArchitecture struct {// 1. 存储层StorageLayer struct …

作者头像 李华