带你手写一个 Spring IoC 容器,原理秒懂

本文详细介绍了如何从头构建一个简易的Spring IoC容器,通过XML配置文件解析和反射机制,动态创建并初始化对象,将其存入Map中,并提供了根据ID获取对象的方法。具体步骤包括创建Maven项目、编写XML配置文件、解析XML、反射创建及初始化对象、存储到Map以及设计getBean方法。最后通过测试类验证了实现的正确性。
摘要由CSDN通过智能技术生成

Spring IoC 底层实现

核心技术点: XML 解析 + 反射

具体的思路:

1、根据需求编写 xml 文件,配置需要创建的 bean

2、编写程序读取 xml 文件,获取 bean 相关信息,类、属性、id。

3、根据第 2 步获取的信息,结合反射机制动态创建对象,同时完成属性的赋值。

4、将创建好的 bean 存入 Map 集合,设置 key - value 映射,key 就是 bean 中 id 值, value 就是 bean 对象。

5、提供方法从 Map 中通过 id 获取对应的 value。

1 前提准备

1、创建一个 maven 项目

2、导入相关依赖,lombok,log4j(需要用 log4j 解析 XML 文件)

<dependencies>
	<!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>
	<!-- dom4j -->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>

</dependencies>

2 创建一个实体类

Car

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {

    private Integer num;

    private String brand;

    private Double price;
}

3 根据需求编写 xml 文件,配置需要创建的 bean

在 resources 资源目录下创建 spring-ioc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="car" class="cc.lucien.entity.Car">
        <property name="num" value="1"></property>
        <property name="brand" value="奥迪"></property>
        <property name="price" value="30.5"></property>
    </bean>

    <bean id="car2" class="cc.lucien.entity.Car">
        <property name="num" value="2"></property>
        <property name="brand" value="奥拓"></property>
    </bean>

</beans>

4 编写程序读取 xml 文件,获取 bean 相关信息,类、属性、id。

模仿 spring,Spring 是有一个 ClassPathXMLApplicationContext 类来读取配置文件,我们创建一个属于自己的 MyClassPathXMLApplicationContext类。

MyClassPathXMLApplicationContext.java

代码上面都有注释,仔细阅读

public class MyClassPathXmlApplicationContext {

    public MyClassPathXmlApplicationContext(String path) {
        // 解析 XML
        parseXML(path);
    }

    public void parseXML(String path) {
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read("src/main/resources/" + path);
            Element root = document.getRootElement();
            Iterator<Element> rootIter = root.elementIterator();
            while (rootIter.hasNext()) {
                // 遍历父节点下的子节点,获取子节点
                Element bean = rootIter.next();
                // 拿到子节点的属性值
                String idStr = bean.attributeValue("id");
                String className = bean.attributeValue("class");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

}

5 根据第 2 步获取的信息,结合反射机制动态创建对象,同时完成属性的赋值。

public class MyClassPathXmlApplicationContext {

    public MyClassPathXmlApplicationContext(String path) {
        // 解析 XML
        parseXML(path);
    }

    public void parseXML(String path) {
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read("src/main/resources/" + path);
            Element root = document.getRootElement();
            Iterator<Element> rootIter = root.elementIterator();
            while (rootIter.hasNext()) {
                // 遍历父节点下的子节点,获取子节点
                Element bean = rootIter.next();
                // 拿到子节点的属性值
                String idStr = bean.attributeValue("id");
                String className = bean.attributeValue("class");
                // 反射动态创建对象
                Class clazz = Class.forName(className);
                // 获取无参构造方法
                Constructor constructor = clazz.getConstructor();
                // 通过无参构造创建对象
                Object object = constructor.newInstance();
                // 给属性赋值
                Iterator<Element> beanIter = bean.elementIterator();
                while (beanIter.hasNext()) {
                    // 获取子节点
                    Element property = beanIter.next();
                    // 获取子节点的属性值
                    String propertyName = property.attributeValue("name");
                    String propertyValue = property.attributeValue("value");
                    // 获取 setter 方法
                    // num --> setNum, brand --> setBrand
                    String methodName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
                    // 获取属性
                    Field field = clazz.getDeclaredField(propertyName);
                    // 获取属性的类型,也是setter方法中传参的类型
                    Class<?> type = field.getType();
                    Method method = clazz.getMethod(methodName, type);
                    // 类型转换
                    Object value = propertyValue;
                    switch (type.getName()) {
                        case "java.lang.Integer":
                            value = Integer.parseInt(propertyValue);
                            break;
                        case "java.lang.Double":
                            value = Double.parseDouble(propertyValue);
                            break;
                    }
                    // 调用方法
                    method.invoke(object, value);
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

}

6 将创建好的 bean 存入 Map 集合,设置 key - value 映射,key 就是 bean 中 id 值, value 就是 bean 对象。

public class MyClassPathXmlApplicationContext {

    private Map<String, Object> iocMap;

    public MyClassPathXmlApplicationContext(String path) {
        iocMap = new HashMap<>();
        // 解析 XML
        parseXML(path);
    }

    public void parseXML(String path) {
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read("src/main/resources/" + path);
            Element root = document.getRootElement();
            Iterator<Element> rootIter = root.elementIterator();
            while (rootIter.hasNext()) {
                // 遍历父节点下的子节点,获取子节点
                Element bean = rootIter.next();
                // 拿到子节点的属性值
                String idStr = bean.attributeValue("id");
                String className = bean.attributeValue("class");
                // 反射动态创建对象
                Class clazz = Class.forName(className);
                // 获取无参构造方法
                Constructor constructor = clazz.getConstructor();
                // 通过无参构造创建对象
                Object object = constructor.newInstance();
                // 给属性赋值
                Iterator<Element> beanIter = bean.elementIterator();
                while (beanIter.hasNext()) {
                    // 获取子节点
                    Element property = beanIter.next();
                    // 获取子节点的属性值
                    String propertyName = property.attributeValue("name");
                    String propertyValue = property.attributeValue("value");
                    // 获取 setter 方法
                    // num --> setNum, brand --> setBrand
                    String methodName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
                    // 获取属性
                    Field field = clazz.getDeclaredField(propertyName);
                    // 获取属性的类型,也是setter方法中传参的类型
                    Class<?> type = field.getType();
                    Method method = clazz.getMethod(methodName, type);
                    // 类型转换
                    Object value = propertyValue;
                    switch (type.getName()) {
                        case "java.lang.Integer":
                            value = Integer.parseInt(propertyValue);
                            break;
                        case "java.lang.Double":
                            value = Double.parseDouble(propertyValue);
                            break;
                    }
                    // 调用方法
                    method.invoke(object, value);
                }
                // 存放到 map 集合中
                iocMap.put(idStr, object);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

}

7 设计一个 getBean 方法用来从 map 中通过 key 来取 value

public class MyClassPathXmlApplicationContext {

    private Map<String, Object> iocMap;

    public MyClassPathXmlApplicationContext(String path) {
        iocMap = new HashMap<>();
        // 解析 XML
        parseXML(path);
    }

    public void parseXML(String path) {
        // 省略....
    }

    public Object getBean(String name) {
        return iocMap.get(name);
    }

}

8 创建测试类,进行测试

Test.java

public class Test {
    public static void main(String[] args) {
        MyClassPathXmlApplicationContext context = new MyClassPathXmlApplicationContext("spring-ioc.xml");
        Car car = (Car) context.getBean("car");
        Car car2 = (Car) context.getBean("car2");
        System.out.println(car);
        System.out.println(car2);
    }
}

运行结果:

Car(num=1, brand=奥迪, price=30.5)
Car(num=2, brand=奥拓, price=null)

9 gitee 源码地址

点击跳转:gitee 地址

完结,散花…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值