手撕spring(一):实现一个简单的IOC容器

gitee地址:https://gitee.com/zr-zhang2021/easy-framework-spring/tree/v1-base-beanfactory

前言

为了更好地帮助自己阅读spring源码,学习源码中的思想,本次spring手撕系列参考了多方面的资料,目标从最简单的原理出发,不断地逼近spring源码,当然完全去模仿spring的难度太大,这里只是针对spring的核心主线,实现一些基本的功能,理解一些基本的抽象,方便以后能够把这些思想运用到实际工作中。

万丈高楼平地起,一个简单的IOC容器,其本质就是实现:读取配置文件并提取bean;注册bean到容器;从容器里加载bean;本章实现基础的BeanFactory:支持xml方式配置bean,支持加载单例bean

首先从最基本的IOC使用开始:

(1) 定义spring-bean.xml:

<bean id="bean1" class="com.rui.test.TestBean1" scope="singleton">

</bean>

(2) TestBean1.java:

public class TestBean1 {
    public void test(){
        System.out.println("hello...");
    }
}

(3) 测试BeanFactoryTest.java:

public class BeanFactoryTest {
    @Test
    public void testGetBean(){
        Resource resource=new ClassPathResource("spring-bean.xml");
        BeanFactory factory=new XmlBeanFactory(resource);
        TestBean1 bean1 = (TestBean1)factory.getBean("bean1");
        bean1.test();
    }

(4) 结果:

hello...

开始设计:容器的类型?

从前面定义xml文件中可以看出,一个bean中会包含idclassscope等属性,因此需要用用一个类(BeanDefinition)来定义,为了之后除了支持xml定义bean之外还支持注解方式,BeanDefinition被抽象成接口,最后,IOC容器被设计成一个:map<String,BeanDefinition>,下面为了简单展示,scope属性后面再添加。

public interface BeanDefinition {
    //获取该bean的全类名
    String getBeanClassName();
}

GenericBeanDefinition为BeanDefinition最基本的实现类:

public class GenericBeanDefinition implements BeanDefinition {

    private String beanId;
    private String beanClassName;

    public GenericBeanDefinition(String beanId,String beanClassName){
        this.beanId=beanId;
        this.beanClassName=beanClassName;
    }

    @Override
    public String getBeanClassName() {
        return this.beanClassName;
    }
}

抽象:实现Resource

下面我们从(3)中一句一句地分析:首先是Resource resource=new ClassPathResource("spring-bean.xml");

参考《Spring源码深度解析》里的说法,Resource接口抽象了所有Spring内部使用到的底层资源。把不同来源的资源文件(比如File,ClassPath)都封装成相应的Resource实现:FileSystemResource、ClassPathResource等。在这里,Resource接口主要是提供getInputStream()的方法,此外还提供getDescription()方法用来打印错误信息。

public interface Resource {

     InputStream getInputStream() throws IOException;

     String getDesciption();
}

最最最核心的思路

BeanFactory factory=new XmlBeanFactory(resource);这一句中到底做了什么?

掌握最核心的思路,其他的抽象都是基于这个核心引申出来的。
本章一开始有说,IOC容器本质上就是:(1)读取配置文件,把提取的bean信息注册到IOC容器;(2)需要使用bean时再从容器里加载bean。参考一下spring源码,根据单一职责原则,xmlBeanFactory中有两个非常核心的角色,XmlBeanDefinitionReader 的职责对应上面的(1),DefaultBeanFactory的职责对应(2),被xmlBeanFactory继承。

public class XmlBeanFactory extends DefaultBeanFactory {

    private final XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(this);

    public XmlBeanFactory(Resource resource){
        this.reader.loadBeanDefinition(resource);
    }

}
  • BeanFactory:提供注册、加载Bean接口
  • DefaultBeanFactory:实现注册Bean方法,加载Bean方法【非常重要】
  • XmlBeanDefinitionReader:解析xml文件,并利用DefaultBeanFactory的注册方法注册Bean

XMLBeanFactory类图

抽象:实现BeanDefinitionRegistry

对于工厂BeanFactory而言,设计的初衷是获取关于bean对象的信息,BeanDefinition的注册与获取属于“内部”的操作,我们不希望将来BeanFactory接口暴露BeanDefinition注册与获取方法,而只提供关于bean的操作,因此BeanDefinitionRegistry的角色就出现了。

在这里插入图片描述

DefaultBeanFactory实现了BeanDefinitionRegistry接口的方法:

    @Override
    public void registerBeanDefinition(String beanId, BeanDefinition bd) {
        this.beanDefinitionMap.put(beanId,bd);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanId) {
        return this.beanDefinitionMap.get(beanId);
    }

【重要】实现XmlBeanDefinitionReader的解析、注册

public class XmlBeanDefinitionReader {
    //依赖于BeanDefinition注册器
    private BeanDefinitionRegistry registry;
    //构造函数,把注册器传进来
    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry){
        this.registry=registry;
    }
    //解析资源
    public void loadBeanDefinition(Resource resource){

        try {
            InputStream inputStream = resource.getInputStream();
            SAXReader saxReader = new SAXReader();
            Document doc = saxReader.read(inputStream);//利用dom4j工具读取resource的输入流
            Element root= doc.getRootElement();//beans标签
            Iterator<Element> iterator = root.elementIterator();
            while(iterator.hasNext()){
                Element element= iterator.next();//bean标签
                String beanId = element.attributeValue("id");//bean标签里面的id属性
                String beanClassName = element.attributeValue("class");//bean标签里面的class属性
                //注册该bean
                GenericBeanDefinition bd = new GenericBeanDefinition(beanId, beanClassName);//BeanDefinition的实现类
                registry.registerBeanDefinition(beanId,bd);//利用注册器的注册方法注册BeanDefinition
            }
        } catch (Exception e) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + resource.getDesciption(),e);
        }
    }
}

【重要】实现DefaultBeanFactory加载Bean

TestBean1 bean1 = (TestBean1)factory.getBean("bean1");这一句到底经历了什么呢?

前面也说过实质上是DefaultBeanFactory实现了getBean(String beanId)这个方法的。

DefaultBeanFactory:

public class DefaultBeanFactory implements BeanFactory,BeanDefinitionRegistry{

    private final ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap= new ConcurrentHashMap<String,BeanDefinition>(64);

    private ClassLoader classLoader;

    @Override
    public Object getBean(String beanId) {
        BeanDefinition bd = this.getBeanDefinition(beanId);
        if (bd==null){
            return null;
        }
        return this.createBean(bd);
    }

    private Object createBean(BeanDefinition bd){
        String beanClassName = bd.getBeanClassName();
        ClassLoader classLoader = this.getClassLoader();
        try {
            Class<?> clazz = classLoader.loadClass(beanClassName);
            return clazz.newInstance();
        } catch (Exception e) {
            throw new BeanCreationException("create bean for "+ beanClassName +" failed",e);
        }
    }
    public ClassLoader getClassLoader(){
        return this.classLoader!=null?this.classLoader: ClassUtils.getDefaultClassLoader();
    }

到这里,一个简单的IOC容器的雏形就已经完成了,其支持了Bean的注册与加载。

【重要】实现单例singleton

singleton单例模式也就是说在xml声明bean时,scope属性设置为singleton(默认也是单例),此时该bean实例在IOC容器里只存在一个,而不会出现加载一次bean就产生一个新的对象,怎么实现呢?

首先,在BeanDefinition接口中添加scope属性的设置和获取,然后还有单例状态(isSingleton)和多例状态(isPrototype)

public interface BeanDefinition {

    public static final String SCOPE_SINGLETON="singleton";
    public static final String SCOPE_PROTOTYPE="prototype";
    public static final String SCOPE_DEFAULT="";

    boolean isSingleton();
    boolean isPrototype();

    String getScope();
    void setScope(String scope);

    String getBeanClassName();
}

其次,GenericBeanDefinition再实现这些方法,也就是说在reader解析注册bean时能够设置scope的属性和单例/多例状态。

public class GenericBeanDefinition implements BeanDefinition {

    private String beanId;
    private String beanClassName;
    private boolean isSingleton=true;
    private boolean isPrototype=false;
    private String scope=SCOPE_DEFAULT;

    @Override
    public void setScope(String scope) {
        this.scope=scope;
        this.isSingleton=SCOPE_SINGLETON.equals(scope)||SCOPE_DEFAULT.equals(scope);
        this.isPrototype=SCOPE_PROTOTYPE.equals(scope);
    }
    ......
}

然后,需要在getBean时维护一个单例,也就是在每次加载bean时判断是否加载过该bean实例。这里抽象出SingletonBeanRegistry。

在这里插入图片描述

该实现类DefaultSingletonBeanDefinition里面用一个map(singletonBeanObjectMap)维护单例对象。

public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
    public final ConcurrentHashMap<String,Object> singletonBeanObjectMap=new ConcurrentHashMap<>(64);

    @Override
    public void registerSingleton(String beanId, Object singletonObject) {
        singletonBeanObjectMap.put(beanId,singletonObject);
    }

    @Override
    public Object getSingleton(String beanId) {
        return this.singletonBeanObjectMap.get(beanId);
    }
}

最后,在DefaultBeanFactory里加载bean时就可以先获取singletonBeanObjectMap里获取这个singleton,如果没有,再createBean()。

public class DefaultBeanFactory extends DefaultSingletonBeanRegistry
        implements BeanFactory,BeanDefinitionRegistry{

    @Override
    public Object getBean(String beanId) {
        BeanDefinition bd = this.getBeanDefinition(beanId);
        if (bd==null){
            return null;
        }
        if (bd.isSingleton()){
            Object singleton = this.getSingleton(beanId);
            if (singleton==null){
                singleton = this.createBean(bd);
                this.registerSingleton(beanId, singleton);
            }
            return singleton;
        }

        return this.createBean(bd);
    }

小扩展:ApplicationContext的实现

在实际的应用的,大部分使用的是ApplicationContext来取代BeanFactory,在spring源码中ApplicationContext继承了BeanFactory,并扩展了一些功能,具体扩展了哪些地方,在后面再详细说吧。在这里为了迎合ApplicationContext的使用,先简单设计并实现ApplicationContext。

先写个测试用例:

public class ApplicationContextTest {
    @Test
    public void testApplicationContext(){
        ApplicationContext context=new ClassPathXmlApplicationContext("spring-bean.xml");
        TestBean1 bean1=(TestBean1)context.getBean("bean1");
        bean1.test();
    }
}

ApplicationContext继承了BeanFactory的getBean功能:

在这里插入图片描述

ClassPathXmlApplicationContext实现类:

public class ClassPathXmlApplicationContext implements ApplicationContext {

    private DefaultBeanFactory factory;

    public ClassPathXmlApplicationContext(String configFile) {
        this.factory=new DefaultBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        Resource resource = new ClassPathResource(configFile);
        reader.loadBeanDefinition(resource);
    }

    @Override
    public Object getBean(String beanId) {
        return factory.getBean(beanId);
    }
}

到这里,测试用例已经可以顺利运行了,但是会产生两个小问题需要优化。

设计模式:模板方法模式的运用

接着上面的,第一个问题是ApplicationContext的扩展问题,前面有提过我们的resource资源可以来自不同的来源,ResourceClassPathResource也有FileSystemResource

在这里插入图片描述

相应的ApplicationContextClassPathXmlApplicationContext也有FileSystemXmlApplicationContext,如果以后有其他ApplicationContext的实现类,例如FileSystemXmlApplicationContext,那么就要在里面重新写一遍ClassPathXmlApplicationContext的内容,不同的是把输入转化为Resource时用到FileSystemResource

public class FileSystemXmlApplicationContext extends AbstractApplicationContext {

    private DefaultBeanFactory factory;

    public FileSystemXmlApplicationContext(String configFile) {
        this.factory=new DefaultBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        Resource resource = new FileSystemResource(configFile);
        reader.loadBeanDefinition(resource);
    }

    @Override
    public Object getBean(String beanId) {
        return factory.getBean(beanId);
    }
}

针对这个问题,首先需要抽象出新的类AbstractApplicationContext,来解决各实现类重复代码的问题,再者运用模板方法模式,也就是在父类定义好执行的模板/骨架(包含抽象方法),并在子类各自重写抽象方法。来解决对不同来源的文件转化为Resource时用不同Resource实现类(ClassPathResource和FileSystemResource)的问题。

在这里插入图片描述

AbstractApplicationContext抽象类:

public abstract class AbstractApplicationContext implements ApplicationContext {

	private DefaultBeanFactory factory = null;
	
	public AbstractApplicationContext(String configFile){
		factory = new DefaultBeanFactory();
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);	
		Resource resource = this.getResourceByPath(configFile);
		reader.loadBeanDefinitions(resource);
		
	}
	
	public Object getBean(String beanID) {
		
		return factory.getBean(beanID);
	}
	
	protected abstract Resource getResourceByPath(String path);

}

ClassPathXmlApplicationContext:

public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
	public ClassPathXmlApplicationContext(String configFile) {
		super(configFile);
		
	}

	@Override
	protected Resource getResourceByPath(String path) {
		
		return new ClassPathResource(path);
	}

}

抽象:实现ConfigurableBeanFactory

第二个小问题是关于ClassLoader的问题,很多地方都用到getBeanClassLoader,比如说ClassPathResource获取流的时候,还有DefaultBeanFactory在createBean的时候,但目前的实现都是使用默认写死的(ClassUtils.getDefaultClassLoader()),我们希望支持ClassLoader的传入,因此抽象出ConfigurableBeanFactory接口。

在这里插入图片描述

本节类图全家福

在这里插入图片描述

posted @ 2021-10-10 23:44  zr-zhang2019  阅读(116)  评论(0编辑  收藏  举报