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

实现IOC功能的简单Spring框架

需求分析

设计一个含有IOC的简单Spring,要求含有对象注册、对象管理以及暴露给外部的获取对象功能。

项目设计

  1. 对于注册的对象用一个类BeanInfo来描述其信息,包括对象标识、全类名以及属性名与值的Map。
  2. 对于IOC容器设定一个顶层接口BeanFactory,定义通过对象标识获取对象示例的方法getBean(String id)AbstractBeanFactory实现该接口,在该类中实现解析生成目标对象,以及获取对象方法,并在该类中添加注册器接口,以便能从注册器中读取注册的对象。
  3. 对于注册器,提供一个顶层接口SourceReader,并在其中添加加载用户注册的对象的方法loadBeans(String filePath)。本项目中使用读取XML的方式,从XML中读取出注册的对象,并把它们封装成BeanInfo放入Map中。
  4. 设定一个上下文XMLContext,继承AbstractBeanFactory,负责选择使用哪种注册方式,并决定何时加载注册的对象。

代码实现

BeanInfo类

package ex1;

import java.util.HashMap;
import java.util.Map;

/**
* 该类用于描述注册在容器中的对象
*/
public class BeanInfo {
    private String id; //对象ID,名字
    private String type; //全类名
    private Map<String, Object> properties = new HashMap<>(); //属性名与值的映射集合

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Map<String, Object> getProperties() {
        return properties;
    }

    public void setProperties(Map<String, Object> properties) {
        this.properties = properties;
    }

    public void addProperty(String key,Object value) {
        properties.put(key,value);
    }
}

BeanFactory接口

package ex1;

public interface BeanFactory {
    /**
     * 根据对象的名称标识来获取对象实例
     * @param id 对象名称,即对象描述信息中的对象标识
     * @return 指定名称的对象实例
     */
    Object getBean(String id);
}

AbstractBeanFactory类

package ex1;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
/**
 * 最顶层的IOC实现
 * 该类负责从注册器中取出注册对象
 * 实现从对象描述信息转换为对象实例的过程
 * 实现根据名称获取对象的方法
 */
public abstract class AbstractBeanFactory implements BeanFactory {
    private String filePath;  //注册文件路径
    private Map<String, BeanInfo> container; //注册对象信息Map(IOC容器)
    protected SourceReader reader; //对象注册读取器


    public AbstractBeanFactory(String filePath) {
        this.filePath = filePath;
    }

    /**
     * 抽象方法,需由子类实现,用于指定使用什么样的注册读取器
     * @param reader 指定的注册读取器
     */
    protected abstract void setReader(SourceReader reader);

    //从注册读取器中读取注册对象的信息Map
    public void registerBeans() {
        this.container = this.reader.loadBeans(this.filePath);
    }

    //实现BeanFactory定义的根据名称获取指定对象的方法
    @Override
    public Object getBean(String id) {
        BeanInfo beanInfo = this.container.get(id); //根据对象名称获取该对象的描述信息
        if (beanInfo == null) {
            return null;
        } else {
            //根据对象信息,解析并生存指定对象实例,返回给用户
            return this.parseBean(beanInfo);
        }
    }

    /**
     * 解析并生成对象实例
     * 该方法主要通过反射完成,步骤如下:
     * 1.根据类名,加载指定类,并获取该类的Class对象clazz
     * 2.使用clazz实例化该类,获取一个对象,注意,这里采用无参构造方法
     * 3.逐个设置对象子段的值,这里采用setter Method方式,而不是直接使用Field对象
     * 4.返回对象实例
     * @param beanInfo 指定对象的描述信息
     * @return
     */
    protected Object parseBean(BeanInfo beanInfo) {
        Class clazz;
        Object bean = null;
        try {
            clazz = Class.forName(beanInfo.getType()); //根据对象的全类名,指定类
            bean = clazz.newInstance(); //使用注册对象的无参构造函数,实例化对象
            Method[] methods = clazz.getMethods(); //获取所有公共方法(其实Spring获取的是所有方法,包括非公有是)
            for (String property : beanInfo.getProperties().keySet()) {
                //首字母大写
                String name = property.toUpperCase().charAt(0) + property.toLowerCase().substring(1);
                //获取属性的setter方法名称(命名规范)
                String setter = "set" + name;
                for (Method method : methods) {
                    if (method.getName().equals(setter)) {
                        Object value = beanInfo.getProperties().get(property);
                        method.invoke(bean, value); //通过反射对属性赋值
                        break;
                    }
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return bean;
    }
}

SourceReader接口

package ex1;

import java.util.Map;
/**
 * 注册读取器接口
 * 复制读取用户注册的对象
 * 继承该接口的类可以实现多种读取方式,如从配置文件中读取,根据标注读取,从网络中读取等
 */
public interface SourceReader {
    /**
     * 读取用户注册的对象信息
     * @param filePath 注册路径
     * @return 注册对象信息Map
     */
    Map<String, BeanInfo> loadBeans(String filePath);
}

XMLSourceReader类

这里实现从xml配置文件中读取。需要用到dom4j包,用来解析XML文件。本项目中XML文件放置在根目录下,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="Person" class="ex1.Person">
        <property name="name" value="fang"/>
    </bean>
</beans>

实现代码

package ex1;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
 * XML注册读取器
 * 该类继承了注册读取器接口,并模拟实现了读取注册对象信息的方法
 */
public class XMLSourceReader implements SourceReader {
    /**
     * 实现读取注册对象信息方法
     * 自己编写通过配置文件读取的实现
     */
    @Override
    public Map<String, BeanInfo> loadBeans(String filePath) {
        BeanInfo info = new BeanInfo(); //注册对象的info
        InputStream is = XMLContext.class.getClassLoader().getResourceAsStream(filePath);//获取xml文件
        SAXReader reader = new SAXReader();
        Map<String,BeanInfo> beanMap = new HashMap<>();
        try {
            Document document = reader.read(is);
            Element root = document.getRootElement(); //获取根标签,这里是beans
            //遍历所有bean
            for(Iterator iterator = root.elementIterator("bean");iterator.hasNext();){
                Element element = (Element)iterator.next();
                //获取id和class
                Attribute id = element.attribute("id");
                Attribute clazzName = element.attribute("class");
                info.setId(id.getText());
                info.setType(clazzName.getText());
                //遍历该bean的property
                for(Iterator it=element.elementIterator("property");it.hasNext();){
                    Element tmp = (Element)it.next();
                    //获取name和value
                    Attribute name = tmp.attribute("name");
                    Attribute value = tmp.attribute("value");
                    info.addProperty(name.getText(),value.getText());
                }
                beanMap.put(id.getText(),info);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return beanMap;
    }
}

XMLContext类

package ex1;

public class XMLContext extends AbstractBeanFactory {
    /**
     * 上下文的构造方法
     * 该方法中指明注册读取器
     * 并在构造该方法时一次性加载注册的对象
     * @param filePath
     */
    public XMLContext(String filePath) {
        super(filePath);
        this.setReader(new XMLSourceReader());//添加注册读取器
        this.registerBeans(); //加载注册的对象信息
    }

    //设置注册读取器
    @Override
    protected void setReader(SourceReader reader) {
        this.reader = reader;
    }
}

测试类

Speakable接口

package ex1;

public interface Speakable {
    void speak(String message);
}

Person类

package ex1;

public class Person implements Speakable {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void speak(String message) {
        System.out.println(this.name + " say: " + message);
    }
}

Bootstrap类

package ex1;

public class Bootstrap {
    public static void main(String[] args) {
        BeanFactory beanFactory = new XMLContext("bean.xml");
        Speakable speakable = (Speakable) beanFactory.getBean("Person");
        speakable.speak("Experience One!");
    }
}

运行结果

fang say: Experience One!

转载于:https://www.cnblogs.com/fht-litost/p/9880437.html

相关文章:

  • 如何将GridViewEX升级到UWP(Universal Windows Platform)平台
  • Windows环境下安装 mysql-8.0.11-winx64 遇到的问题解决办法
  • LinuxMint下Docker的安装部署和验证
  • Vue-cli / webpack 加载静态js文件的方法
  • python函数的动态传参.作用域与命名空间
  • static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store); 的作用
  • 几分钟内提升技能的8个 JavaScript 方法!
  • HTTP请求中的form data和request payload的区别
  • Linux内核-协议栈-从BSD Socket接口层到传输层1
  • centos7全离线安装MongoDB3.2.8集群
  • Java基础学习18(类的多态性以及子父类之间的转换机制)
  • Python的三种基本数据类型
  • 窗体数据Excle导入功能详解
  • Linux -LAMP架构介绍
  • 2018-2019-1 20165226 20165310 20165315 实验二 固件程序设计
  • [LeetCode] Wiggle Sort
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • 4个实用的微服务测试策略
  • es6要点
  • JavaSE小实践1:Java爬取斗图网站的所有表情包
  • Java面向对象及其三大特征
  • js中forEach回调同异步问题
  • 程序员最讨厌的9句话,你可有补充?
  • 罗辑思维在全链路压测方面的实践和工作笔记
  • 区块链共识机制优缺点对比都是什么
  • ​​​​​​​​​​​​​​Γ函数
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #ubuntu# #git# repository git config --global --add safe.directory
  • #Z2294. 打印树的直径
  • $$$$GB2312-80区位编码表$$$$
  • (LeetCode 49)Anagrams
  • (分享)自己整理的一些简单awk实用语句
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (南京观海微电子)——COF介绍
  • (数据结构)顺序表的定义
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (原創) 如何安裝Linux版本的Quartus II? (SOC) (Quartus II) (Linux) (RedHat) (VirtualBox)
  • (转) Android中ViewStub组件使用
  • (转) SpringBoot:使用spring-boot-devtools进行热部署以及不生效的问题解决
  • (转)3D模板阴影原理
  • (轉貼) 寄發紅帖基本原則(教育部禮儀司頒布) (雜項)
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .NET Micro Framework初体验
  • .NET导入Excel数据
  • [BZOJ 3680]吊打XXX(模拟退火)
  • [C#][DevPress]事件委托的使用
  • [C#]winform制作仪表盘好用的表盘控件和使用方法
  • [C++]C++类基本语法
  • [Contest20180313]灵大会议
  • [docker]docker网络-直接路由模式
  • [Electron]ipcMain.on和ipcMain.handle的区别
  • [ExtJS5学习笔记]第三十节 sencha extjs 5表格gridpanel分组汇总
  • [flink总结]什么是flink背压 ,有什么危害? 如何解决flink背压?flink如何保证端到端一致性?
  • [hive] sql中distinct的用法和注意事项