实现IOC功能的简单Spring框架

Posted litos

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现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!

以上是关于实现IOC功能的简单Spring框架的主要内容,如果未能解决你的问题,请参考以下文章

Spring框架介绍和IoC容器中Bean配置(Spring框架)

Spring我抄袭了Spring,手写一套MySpring框架。。。

Java Spring 框架初步学习总结简单实现 IoC 和 AOP

白话系列之IOC,三个类实现简单的Ioc

spring IOC加载流程

基于注解实现简单Spring框架:完成IOC容器和声明式事务控制