手敲简易版 Spring 框架 - IOC与DI

Spring 是目前 javaEE 开发中的主流框架,可以说 spring 让 java 开发人员迎来了一个“春天”,

Spring框架是Spring家族下一个JAVAEE企业开发的一栈式轻量级框架

 从 Spring 架构图可以看出,spring 最核心的是 Core Container ,而在核心容器中核心的,是 Core 和 Bean ,也就是 IOC 控制反转和 DI 依赖注入

Spring 的这两个组件是怎么工作的呢,

在我们使用 spring 时,会设置它的配置文件,在里面配置 bean 标签,然后使用 property 标签依赖注入,这里使用到的逻辑也很简单,就是

  1. 解析这个 xml 文件,获取对应标签的值,
  2. 使用反射来创建对象,调用 set 方法注入
  3. 将对象存入 map 中,键名为 beanid值
  4. 公开一个 getBean 方法,返回Bean

理解它的工作原理之后,我们来自己手写一个类似的容器


项目准备

beans.xml —— spring 配置文件,用于生成 Bean 和依赖注入

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <bean id="user" class="com.dao.User">
        <property name="username" value="TOM"/>
        <property name="password" value="123456"/>
    </bean>
</beans>

User.java

package com.dao;

public class User {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

现在我们就要编写解析 XML 的 SpringAppLicattionContext 类和启动容器的 app 类

解析 XML 是这个容器最重要也是最核心的功能,与之一样重要的就是使用反射来创建对象

package com;

import org.dom4j.Branch;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

/**
 * 一些声明信息
 *
 * @author 谢柯
 * @date: 2022/9/15 23:42
 * @description: 类spring IOC容器
 * @since JDK 1.8
 */
public class SpringApplicationContext {
    private String path;
    private HashMap<String,Object> mapBean=new HashMap<String, Object>();
    private HashMap<String,String> mapElement=new HashMap<String, String>();


    public SpringApplicationContext(String path) throws DocumentException, FileNotFoundException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        this.path = path;
        this.xml();
    }

    public void xml() throws FileNotFoundException, DocumentException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {

//        文件输入流
        InputStream stream = ClassLoader.getSystemResourceAsStream(this.path);

//        new DOM4j的核心对象,SAXReader
        SAXReader saxReader = new SAXReader();

//        通过SAXReader对象的read()方法得到Document对象。该方法需要输入流,该输入流指向要解析的文件
        Document read = saxReader.read(stream);

//        用Document对对象的getRootElement()方法,获取根节点
        Element rootElement = read.getRootElement();

//        用根节点的elements(节点名)方法,获取子节点对象集
        List<Element> beans = rootElement.elements("bean");

//        遍历集合,获得每一个节点对象
        for (Element bean : beans) {

//            获取要创建bean的id名,全类名
            String id = bean.attributeValue("id");
            String _class = bean.attributeValue("class");

//            获取子节点
            List<Element> property = bean.elements("property");

//            遍历子节点标签
            for (Element element : property) {

//                获取子节点标签的属性值
                String name = element.attributeValue("name");

                String value = element.attributeValue("value");

//                将遍历出来的值暂存到map中,这是依赖注入的值
                mapElement.put(name,value);
            }

//            获取字节码文件对象
            Class<?> aClass = Class.forName(_class);

//            直接创建对象
            Object o = aClass.newInstance();

            Set<String> set = mapElement.keySet();

//            执行对象的set方法,注入值
            for (String name : set) {

//                将属性名第一个字母大写
                String MethodName="set"+name.substring(0,1).toUpperCase()+name.substring(1);
                String value = mapElement.get(name);

//                获取set方法
                Method method = aClass.getMethod(MethodName, String.class);

                method.invoke(o,value);
            }

//            加入map
            mapBean.put(id,o);
        }
    }

//    获取Bean,如果传入class的话,可以直接返回指定类型的一个对象
    public Object getBean(String BreanName){

        Object o = mapBean.get(BreanName);
        System.out.println(o.toString());
        return o;
    }

    

}

解析 XML 常见的有 JAXP,JDOM,DOM4J,我这里使用DOM4J

DOM4J的解析流程为:

 1 - 创建DOM4J的核心对象,SAXReader
 2 - 通过SAXReader对象的read()方法得到Document对象。该方法需要输入流指向要解析的文件
 3 -  用Document对对象的getRootElement()方法,获取根节点
 4 - 用根节点的elements(子节点名)方法,获取子节点对象集
 5 - 遍历集合,获得每一个节点对象,并对其处理
    获取属性信息 —— 节点对象 . attributeValue(属性)
    获取文本内容 —— 节点对象 . getText()
    获取下级标签 —— 节点对象 . element(标签名)

另外在编写上述代码时遇到了几个问题,

1)定义成员变量

 private HashMap<String,Object> mapBean=new HashMap<String, Object>();

private HashMap<String,Object> mapBean

的区别

如果使用 new 来创建,那么是创建了一个实例对象,该对象在堆内存拥有内存空间,变量指向该内存空间

如果只是定义一个变量,那么该变量默认值为 null ,无法进行任何操作

2)在创建文件输入流(FileInputStream)时,不论使用相对还是绝对路径,都读不到 xml,我创建的是一个 maven 项目,实在让我纳闷,最后输入流使用了  

ClassLoader.getSystemResourceAsStream(path);

容器启动类 App.java

package com;

import com.itheima.domain.User;
import com.itheima.service.impl.UserServiceImpl;
import org.dom4j.DocumentException;

import java.io.FileNotFoundException;
import java.lang.reflect.InvocationTargetException;

public class App {
    public static void main(String[] args) throws DocumentException, FileNotFoundException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {

//        获取解析容器
        SpringApplicationContext springApplicationContext = new SpringApplicationContext("beans.xml");

//        取出对应Bean,
       User user = (User)springApplicationContext.getBean("user");

//        输出Bean
        System.out.println(user);

    }
}

运行该启动类后,可以在控制台看到

 说明我们的项目已经成功创建了 User 的实例对象并返回,到此,学习结束

后言

“公道时间唯白发,贵人头上不曾饶”

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值