2025年手写基于MXL文件配置的SpringIoC框架

手写基于MXL文件配置的SpringIoC框架这篇文章只是简单的调用反射机制创建 bean 对象 不是真的要各位写出一个 SpringIoC 框架 意在帮助于大家理解 Spring 是如何制造 Bean 的 加深大家对框架的理解 首先我们要知道 Spring 的底层是如何通过简单的配置文件就可以把我们所需要的 bean 创建到 IoC 容器当中 总的就是三个核心技术

大家好,我是讯享网,很高兴认识大家。

这篇文章只是简单的调用反射机制创建bean对象,不是真的要各位写出一个SpringIoC框架,意在帮助于大家理解Spring是如何制造Bean的,加深大家对框架的理解!

首先我们要知道Spring的底层是如何通过简单的配置文件就可以把我们所需要的bean创建到IoC容器当中,总的就是三个核心技术,解析XML文件,工厂方法设计模式,反射机制,那接下来我就带领大家一步步的去实现一个简单的SpringIoC容器框架!再次声明!不是真的去写一个,我只是实现了它的功能,意在加深大家对底层的了解。
第一步:引入Dom4j的依赖

因为我们解析XMl文件需要用到这个依赖

<dependency> <!--dom4j是一个能够解析xml文件的java组件--> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.1.3</version> </dependency>

讯享网
第二步:准备好要管理的Bean

(我们现在是站在框架的开发者角度,这个Bean在开发完之后要删除的!!!)请注意,不要使用与spring类相关的包名,包名自己起。这里你们就自备了。普通的javaBean即可!

第三步:编写ApplicationContext这个接口

我们该从哪里切入呢!直接上来就干,很明显没有思绪,我们站在框架的使用者来说,如果要get到这个bean,要获取到ClassPathXmlApplicationContext这个实例,调用getBean这个方法传入Bean的id即可获取,从源码可以看出ClassPathXmlApplicationContext这个类实现了ApplicationContext这个接口,并重写了接口的getBean这个方法,现在就可以明确我们要编写ApplicationContext这个接口。以下是我创建的接口。

讯享网/ * Spring框架应用上下文接口 */ public interface ApplicationContext { / * 根据bean的名称获取对应的bean对象 * @param beanName bean标签的id * @return */ Object getBean(String beanName); }
第四步:编写ClassPathXmlApplicationContext

ClassPathXmlApplicationContext这个类实现了ApplicationContext接口,主要有两个目的:

1、实现getBean方法返回对象,底层会调用singletonObjects的get方法去获取到完整的bean实例并返回。

 public Object getBean(String beanName) { return singletonObjects.get(beanName); }}

2、解析XML文件

这个文件的解析过程稍后我的代码里面都会有!会有非常详细的注释不必担心

第五步:准备好配置文件
讯享网<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="catBean" class="org.myspringIoC.Bean.Cat"> <property name="name" value="小橘"/> <property name="age" value="8"/> <property name="character" ref=""/> </bean> <bean id="characterBean" class="org.myspringIoC.Bean.Character"> <property name="color" value="橘色"/> <property name="weight" value="2.5"/> </bean> </beans>
第六步:创建Map集合来存储Bean

在Spring的底层我们创建好的Bean都是放到Map集合中的,id就是XML文件里面配置的beanId,值就是我们通过反射机制创建的Bean

private Map<String,Object> singletonObjects = new HashMap<>();
第七步:解析配置文件后实例化XML文件中的所有Bean,放到Map集合中

回顾我们在创建ClassPathXmlApplicationContext实例对象时,需要在构造方法中传入xml文件的路径,所以需要编写ClassPathXmlApplicationContext的构造方法

讯享网 / * @param configLocation spring配置文件的路径,注意:使用 ClassPathXmlApplicationContext 配置文件应该放到类路径下 */ public ClassPathXmlApplicationContext(String configLocation) { } 

接下来我们开始解析XML文件并实例化对象

public class ClassPathXmlApplicationContext implements ApplicationContext { private Map<String,Object> singletonObjects = new HashMap<>(); / * 解析spring的配置文件,然后初始化所有的bean对象 * @param configLocation spring配置文件的路径,注意:使用 ClassPathXmlApplicationContext 配置文件应该放到类路径下 */ public ClassPathXmlApplicationContext(String configLocation) { //解析myspring.xml文件,然后实例化bean,将bean存放到singletonObjects集合当中 try { SAXReader saxReader = new SAXReader(); Document document = saxReader.read(ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation)); //获取XML文件中所有的Bean标签 List<Node> nodes = document.selectNodes("//bean"); //第一个斜杠是转义字符 //遍历集合,获取所有的Beanid和className nodes.forEach(node -> { Element beanEle = (Element) node; //转成Element类型的对象,可以使用更多方法 //调用attributeValue获取id String id = beanEle.attributeValue("id"); //获取Class的信息,以便后续调用反射机制实例化对象 String beanClass = beanEle.attributeValue("class"); //已经拿到了beanClass的相关信息,开始通过反射机制创建对象 try { Class<?> beanClazz = Class.forName(beanClass); //获取构造方法 Constructor<?> declaredConstructor = beanClazz.getDeclaredConstructor(); //调用declaredConstructor的newInstance方法实例化对象 Object beanObject = declaredConstructor.newInstance(); //将bean存储到Map集合中 singletonObjects.put(id,beanObject); } catch (Exception e) { //扩大了异常的范围 e.printStackTrace(); } }); } catch (DocumentException e) { e.printStackTrace(); } } @Override public Object getBean(String beanName) { return singletonObjects.get(beanName); }}
第八步:测试能不能拿到Bean的实例
讯享网public class TestSpring { @Test public void testSpring(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); Object catBean = applicationContext.getBean("catBean"); Object characterBean = applicationContext.getBean("characterBean"); System.out.println(catBean); System.out.println(catBean); } }

注意:ApplicationContext和ClassPathXmlApplicationContext看清楚了,不要导成spring的包了,导自己的包

运行结果:


讯享网

拿到了Bean这个实例,但是是空的,接下来我们要对这个Bean的属性进行注入

第九步:给Bean的属性赋值

我们前面已经使用了反射机制,接下来通过反射调用set方法,给Bean的属性赋值

继续在原先代码上继续书写

public class ClassPathXmlApplicationContext implements ApplicationContext { private Map<String,Object> singletonObjects = new HashMap<>(); / * 解析spring的配置文件,然后初始化所有的bean对象 * @param configLocation spring配置文件的路径,注意:使用 ClassPathXmlApplicationContext 配置文件应该放到类路径下 */ public ClassPathXmlApplicationContext(String configLocation) { //解析myspring.xml文件,然后实例化bean,将bean存放到singletonObjects集合当中 try { SAXReader saxReader = new SAXReader(); Document document = saxReader.read(ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation)); //获取XML文件中所有的Bean标签 List<Node> nodes = document.selectNodes("//bean"); //第一个斜杠是转义字符 //遍历集合,获取所有的Beanid和className nodes.forEach(node -> { //向下转型的目的是Element接口中可以使用更多方法 Element beanEle = (Element) node; //调用attributeValue获取id String id = beanEle.attributeValue("id"); //获取Class的信息,以便后续调用反射机制实例化对象 String beanClass = beanEle.attributeValue("class"); //已经拿到了beanClass的相关信息,开始通过反射机制创建对象 try { Class<?> beanClazz = Class.forName(beanClass); //获取无参数构造方法 Constructor<?> declaredConstructor = beanClazz.getDeclaredConstructor(); //调用declaredConstructor的newInstance方法实例化对象 Object beanObject = declaredConstructor.newInstance(); //将bean存储到Map集合中 singletonObjects.put(id,beanObject); } catch (Exception e) { //扩大了异常的范围 e.printStackTrace(); } }); //我们需要再次遍历集合,给Bean的属性赋值 //为什么不在集合遍历时给Bean注入呢?还要再一次遍历集合 //想想Spring是如何解决循环依赖问题的,实例化和属性赋值分开了! nodes.forEach(node -> { try { Element beanEl = (Element) node; //获取id String id = beanEl.attributeValue("id"); //获取ClassName String classN = beanEl.attributeValue("class"); //获取Class,后续需要得到set方法 Class<?> aClass = Class.forName(classN); //获取该bean标签下所有的属性property标签 List<Element> propertys = beanEl.elements("property"); //遍历所有的属性标签 propertys.forEach(property ->{ try { //获取属性名 String propertyName = property.attributeValue("name"); //获取属性类型 Field field = aClass.getDeclaredField(propertyName); //获取set方法 String setName = "set" + propertyName.toUpperCase(Locale.ROOT).charAt(0) + propertyName.substring(1); //获取set方法 Method setMethod = aClass.getDeclaredMethod(setName,field.getType()); //调用set方法 //还要传入参数,如何获取具体的值呢? //获取简单值 String value = property.attributeValue("value"); Object actVal = null; //真实要注入的值 //获取引用对象 String ref = property.attributeValue("ref"); //判断两个值是否为空 if (value != null) { //说明这个值是简单类型 //关键在于不知道value的属性是什么,所有在此声明!该框架只支持简单类型 // byte short int long float double boolean char // Byte Short Integer Long Float Double Boolean Character //String //获取属性类型名 String propertyTypeSimpleName = field.getType().getSimpleName(); //匹配属性名 switch (propertyTypeSimpleName){ case "byte": actVal = Byte.parseByte(value); //基本数据类型赋值给一个包装类是没有问题的,自动装箱拆箱 break; case "short": actVal = Short.parseShort(value); break; case "int": actVal = Integer.parseInt(value); break; case "long": actVal = Long.parseLong(value); break; case "float": actVal = Float.parseFloat(value); break; case "double": actVal = Double.parseDouble(value); break; case "boolean": actVal = Boolean.parseBoolean(value); break; case "char": actVal = value.charAt(0); break; case "Byte": actVal = Byte.valueOf(value); break; case "Short": actVal = Short.valueOf(value); break; case "Integer": actVal = Integer.valueOf(value); break; case "Long": actVal = Long.valueOf(value); break; case "Float": actVal = Float.valueOf(value); break; case "Double": actVal = Double.valueOf(value); break; case "Boolean": actVal = Boolean.valueOf(value); break; case "Character": actVal = Character.valueOf(value.charAt(0)); break; case "String": actVal = value; //不用处理,默认类型就是String } setMethod.invoke(singletonObjects.get(id),actVal); } if (ref != null) { //说明这个值是非简单类型 //引用对象也在Map集合中,直接get到,然后注入就行 setMethod.invoke(singletonObjects.get(id),singletonObjects.get(ref)); //ref就是引用对象的id } }catch (Exception e){ e.printStackTrace(); } }); }catch (Exception e){ e.printStackTrace(); } }); } catch (DocumentException e) { e.printStackTrace(); } } 
  第十步:测试程序:

javaBean:

讯享网package org.myspringIoC.Bean; public class Cat { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Cat{" + "name='" + name + '\'' + ", age=" + age + '}'; } }

xml配置(这个xml文件是我自己写的,不是spring自带的):

<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="catBean" class="org.myspringIoC.Bean.Cat"> <property name="name" value="小橘"/> <property name="age" value="8"/> </bean> </beans>

编写测试类:

讯享网package com.gxy.test; import org.junit.Test; import org.myspringframework.core.ApplicationContext; import org.myspringframework.core.ClassPathXmlApplicationContext; public class TestSpring { @Test public void testSpring(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); Object catBean = applicationContext.getBean("catBean"); System.out.println(catBean); } }

运行结果:可以观察到拿到了bean对象

  第十一步: 测试是否解决循环依赖问题

在第九步给bean的属性赋值过程中,我是先调用无参数构造方法创建出bean的实例,再次遍历map集合给bean的属性赋值,从而将bean的实例化和给属性赋值两个阶段分开来,从而解决了循环依赖问题,接下来是测试

创建两个循环依赖的bean,A循环依赖于B

package org.myspringIoC.Bean; public class A { private String name; private B b; public String getName() { return name; } public void setName(String name) { this.name = name; } public B getB() { return b; } public void setB(B b) { this.b = b; } @Override public String toString() { return "A{" + "name='" + name + '\'' + ", b=" + b.getName() + '}'; } } 
讯享网package org.myspringIoC.Bean; public class B { private String name; private A a; public String getName() { return name; } public void setName(String name) { this.name = name; } public A getA() { return a; } public void setA(A a) { this.a = a; } @Override public String toString() { return "B{" + "name='" + name + '\'' + ", a=" + a.getName() + '}'; } } 

xml配置文件:

<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="aClass" class="org.myspringIoC.Bean.A"> <property name="name" value="我是A"/> <property name="b" ref = "bClass"/> </bean> <bean id="bClass" class="org.myspringIoC.Bean.B"> <property name="name" value="我是B"/> <property name="a" ref = "aClass"/> </bean> </beans>

编写测试类:

运行:

通过运行结果可以发现,在单例bean加setter注入下,可以解决循环依赖问题,那如果是构造注入可以吗?

很明显不行!Spring解决循环依赖的关键是将实例化和注入两个阶段分开做了,如果是构造注入的话,两个过程没办法分开,所有在IoC容器启动阶段就会报错,

小讯
上一篇 2025-03-22 10:17
下一篇 2025-03-04 16:10

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/124356.html