Spring源码试读--BeanFactory模拟实现

动机

现在Springboot越来越便捷,如果简单的Spring应用,已无需再配置xml文件,基本可以实现全注解,即使是SpringCloud的那套东西,也都可以通过yaml配置完成。最近一年一直在用Springboot+JPA或者Springboot+MyBatis,基本上不用Spring和SpringMVC了,心血来潮想着趁国庆假期试着一点点实现一下Spring的基本功能(当然是会对照源码的,毕竟很多细节想不到,变量命名也会按照源码来),基本思路就是先按照Spring的类图试着自己写,争取实现相同的功能,然后再看源码的实现方式,再重构。

第一篇先实现Spring的基本组件--bean容器

雏形

定义两个接口BeanFactory和BeanDefinition

public interface BeanFactory {

    BeanDefinition getBeanDefinition(String beanID)
    Object getBean(String beanID);
}
public interface BeanDefinition {

    public String getBeanClassName();
}

两个实现类DefaultBeanFactory和GenericBeanDefinition分别实现这两个接口:


public class DefaultBeanFactory implements BeanFactory {
    public static final String ID_ATTRIBUTE="id";
    public static final String CLASS_ATTRIBUTE="class";
    private Map<String,BeanDefinition> beanDefinitionMap=new ConcurrentHashMap<String, BeanDefinition>();
    public DefaultBeanFactory(String configFile) {
        loadBeanDefinition(configFile);

    }

    private void loadBeanDefinition(String configFile) {
        InputStream is= null;
        ClassLoader classLoader = this.getClass().getClassLoader();
        is=classLoader.getResourceAsStream(configFile);
        //需要dom4j
        SAXReader saxReader = new SAXReader();
        try {
            Document doc = saxReader.read(is);
            Element root = doc.getRootElement();
            Iterator iterator = root.elementIterator();
            while (iterator.hasNext()){
                Element element = (Element)iterator.next();
                String id=element.attributeValue(ID_ATTRIBUTE);
                String className=element.attributeValue(CLASS_ATTRIBUTE);
                BeanDefinition beanDefinition = new GenericBeanDefinition(id, className);
                beanDefinitionMap.put(id,beanDefinition);
            }
        } catch (DocumentException e) {
           throw new BeanDefinitionStoreException("Load and parsing XML failed",new Throwable());
        }finally {
            if(is!=null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public BeanDefinition getBeanDefinition(String beanID) {
        if(beanDefinitionMap.containsKey(beanID))
            return beanDefinitionMap.get(beanID);
        return null;
    }
    //职责2:创建bean实例
    public Object getBean(String beanID) {
        BeanDefinition beanDefinition = this.getBeanDefinition(beanID);
        if(beanDefinition==null){
            throw new BeanCreationException("Bean Definition does not exist");
        }
        ClassLoader classLoader = this.getClass().getClassLoader();
        try {
            Class<?> clz = classLoader.loadClass(beanDefinition.getBeanClassName());
            return clz.newInstance();
            //捕获所有异常,然后抛出自定义异常
        } catch (Exception e) {
            throw new BeanCreationException("create bean for "+beanDefinition.getBeanClassName()+" failed.");
        }

    }
}
public class GenericBeanDefinition implements BeanDefinition {
    private String id;
    private String beanClassName;
    public GenericBeanDefinition(String id, String beanClassName) {
        this.id = id;
        this.beanClassName = beanClassName;
    }
    public String getBeanClassName() {

        return this.beanClassName;
    }

}

主要逻辑在DefaultBeanFactory中,通过解析xml来生成一个bean实例并保存到Map中。

单一指责原则

  • 核心思想:一个类应该有且只有一个变化的原因。
  • 为什么引入单一职责:

    在SRP中,把职责定义为变化的原因。当需求变化时,将通过更改职责相关的类来体现。如果一个类拥有多于一个的职责,则多个职责耦合在一起,会有多于一个原因来导致这个类发生变化。一个职责的变化可能会影响到其他的职责,另外,把多个职责耦合在一起,影响复用性。如:DefaultBeanFactory类目前有两个指责:1.加载和读取XML文件;2.创建bean实例

    我们把读取XML的职责拆分出来给一个新类XMLBeanDefinitionReader,同时,BeanFactory是供给client使用的,而BeanDefinition是一个内部的概念,应该对client是透明的,所以不应该对外暴露,所以把getBeanDefinition和注册(即之前的添加到Map)职责分出来给一个新接口BeanDefinitionRegistry。DefaultBeanFactory实现BeanDefinitionRegistry,下一节会用一个ApplicationContext包装DefaultBeanFactory,进而对用户屏蔽getBeanDefinition()和registerBeanDefinition()。

修改后的DefaultBeanFactory

public class DefaultBeanFactory implements BeanFactory,BeanDefinitionRegistry {
    private Map<String,BeanDefinition> beanDefinitionMap=new ConcurrentHashMap<String, BeanDefinition>();
    public DefaultBeanFactory(){

    }

    public BeanDefinition getBeanDefinition(String beanID) {
        if(beanDefinitionMap.containsKey(beanID))
            return beanDefinitionMap.get(beanID);
        return null;
    }

    public void registerBeanDefinition(String beanID, BeanDefinition beanDefinition) {
        this.beanDefinitionMap.put(beanID,beanDefinition);
    }

    public Object getBean(String beanID) {
        BeanDefinition beanDefinition = this.getBeanDefinition(beanID);
        if(beanDefinition==null){
            throw new BeanCreationException("Bean Definition does not exist");
        }
        ClassLoader classLoader = this.getClass().getClassLoader();
        try {
            Class<?> clz = classLoader.loadClass(beanDefinition.getBeanClassName());
            return clz.newInstance();
            //捕获所有异常,然后抛出自定义异常
        } catch (Exception e) {
            throw new BeanCreationException("create bean for "+beanDefinition.getBeanClassName()+" failed.");
        }

    }
}

BeanDefinitionRegistry接口:

public interface BeanDefinitionRegistry {
    BeanDefinition getBeanDefinition(String beanID);
    void registerBeanDefinition(String beanID,BeanDefinition beanDefinition);
}

XmlBeanDefinitionReader类:用来读取XML并调用BeanDefinitionRegistry的registerBeanDefinition方法注册beanDefinition。

public class XmlBeanDefinitionReader {

    public static final String ID_ATTRIBUTE = "id";

    public static final String CLASS_ATTRIBUTE = "class";

    public static final String SCOPE_ATTRIBUTE = "scope";

    BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this.registry = registry;
    }

    public void loadBeanDefinition(String configFile) {
        InputStream is = null;
        ClassLoader classLoader = this.getClass().getClassLoader();
        is = classLoader.getResourceAsStream(configFile);
        SAXReader saxReader = new SAXReader();
        try {
            Document doc = saxReader.read(is);
            Element root = doc.getRootElement();
            Iterator iterator = root.elementIterator();
            while (iterator.hasNext()) {
                Element element = (Element) iterator.next();
                String id = element.attributeValue(ID_ATTRIBUTE);
                String className = element.attributeValue(CLASS_ATTRIBUTE);
                BeanDefinition beanDefinition = new GenericBeanDefinition(id, className);
                registry.registerBeanDefinition(id, beanDefinition);
            }
        } catch (DocumentException e) {
            throw new BeanDefinitionStoreException("Load and parsing XML failed", new Throwable());
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

ApplicationContext

Spring中通常不会直接访问BeanFactory,而是通过ApplicationContext来得到bean,即通过ApplicationContext调用BeanFactory方法。

定义一个接口ApplicationContext继承BeanFactory:

public interface ApplicationContext extends BeanFactory {
}

创建一个实现类ClassPathXmlApplicationContext,从ClassPath下读取XML,内部持有一个DefaultBeanFactory实例,对外只暴露getBean()方法,屏蔽了getBeanDefinition()和registerBeanDefinition():

public class ClassPathXmlApplicationContext implements ApplicationContext {
    private DefaultBeanFactory factory=null;
    public ClassPathXmlApplicationContext(String configFile) {
        factory=new DefaultBeanFactory();
        XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinition(configFile);
    }

    public Object getBean(String beanID) {
        return factory.getBean(beanID);
    }
}

Resource

使用Resource来抽象资源

除了从ClassPath读取XML,还可以从FileSystem读取,最终都是要转换成为一个InputStream,所以抽象出一个Resource接口,并创建两个实现类来分别处理从两种途径读取XML。


public interface Resource {
    InputStream getInputStream() throws IOException;
    String getDescription();

}
public class ClassPathResource implements Resource {

    private String path;
    private ClassLoader classLoader;

    public ClassPathResource(String path) {
        this(path, (ClassLoader) null);
    }
    public ClassPathResource(String path, ClassLoader classLoader) {
        this.path = path;
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }

    public InputStream getInputStream() throws IOException {
        InputStream is = this.classLoader.getResourceAsStream(this.path);

        if (is == null) {
            throw new FileNotFoundException(path + " cannot be opened");
        }
        return is;

    }
    public String getDescription(){
        return this.path;
    }

}
public class FileSystemResource implements Resource {

    private final String path;
    private final File file;

    public FileSystemResource(String path) {
        //这里的Assert不是junit的Assert,是自定义的一个工具类,就是判空处理并提示指定信息,逻辑简单不贴代码了
        Assert.notNull(path, "Path must not be null");
        this.file = new File(path);
        this.path = path;
    }

    public InputStream getInputStream() throws IOException {
        return new FileInputStream(this.file);
    }

    public String getDescription() {
        return "file [" + this.file.getAbsolutePath() + "]";
    }

}

现在DefaultBeanFactory中的loadBeanDefinition可以接收一个Resource对象,从中获取InputStream,而不用管是从classpath还是从FileSystem读取的。同时可以创建一个与ClassPathXmlApplicationContext相对应的FileSystemXmlApplicationContext类来完成从FileSystem读取XML并获取bean:

public class FileSystemXmlApplicationContext implements ApplicationContext {
    DefaultBeanFactory factory=null;
    public FileSystemXmlApplicationContext(String path) {
        factory=new DefaultBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        //这是与ClassPathXmlApplicationContext唯一的区别
        Resource resource=new FileSystemResource(path);
        reader.loadBeanDefinition(resource);
    }
    public Object getBean(String beanID){
        return factory.getBean(beanID);

    }
}

可以发现这个类和ClassPathXmlApplicationContext唯一的区别就是Resource不同,为了避免重复代码,用模板方法重构,新建一个抽象类AbstractApplicationContext,然后两个ApplicationContext类继承并实现getResourceByPath。

public abstract class AbstractApplicationContext implements ApplicationContext {

    private DefaultBeanFactory factory = null;
    private ClassLoader beanClassLoader=null;

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

    public Object getBean(String beanID) {

        return factory.getBean(beanID);
    }

    protected abstract Resource getResourceByPath(String path);
}

Scope

Spring中的bean有一个scope属性用来指定bean是否是单例。而Spring是如何管理单例对象的呢?肯定不是把类设计成单例模式,而是Spring统一管理bean,然后根据scope属性来提供bean实例。

先定义一个接口SingletonBeanRegistry:

public interface SingletonBeanRegistry {

    void registerSingleton(String beanName, Object singletonObject);

    Object getSingleton(String beanName);
}

它的实现类DefaultSingletonBeanRegistry,通过一个Map管理单例对象:

public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {

    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

    public void registerSingleton(String beanName, Object singletonObject) {

        Assert.notNull(beanName, "‘beanName‘ must not be null");

        Object oldObject = this.singletonObjects.get(beanName);
        if (oldObject != null) {
            throw new IllegalStateException("Could not register object [" + singletonObject +
                    "] under bean name ‘" + beanName + "‘: there is already object [" + oldObject + "] bound");
        }
        this.singletonObjects.put(beanName, singletonObject);

    }

    public Object getSingleton(String beanName) {

        return this.singletonObjects.get(beanName);
    }

}

咱们的DefaultBeanFactory要继承DefaultSingletonBeanRegistry(也可以内部持有一个DefaultSingletonBeanRegistry对象,采用组合模式),修改getBean()方法:

public Object getBean(String beanID) {
    BeanDefinition beanDefinition = this.getBeanDefinition(beanID);
    if(beanDefinition==null){
        throw new BeanCreationException("Bean Definition does not exist");
    }
    if(beanDefinition.isSingleton()){
        Object bean = this.getSingleton(beanID);
        if(bean == null){
            bean = createBean(beanDefinition);
            this.registerSingleton(beanID, bean);
        }
        return bean;
    }
    return createBean(beanDefinition);

}

同时我们的BeanDefinition和GenericBeanDefinition也要修改,增加Singleton相关的属性:

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

    public boolean isSingleton();
    public boolean isPrototype();
    String getScope();
    void setScope(String scope);

    public String getBeanClassName();
}
public class GenericBeanDefinition implements BeanDefinition {
    private String id;
    private String beanClassName;
    private boolean singleton = true;
    private boolean prototype = false;
    private String scope = SCOPE_DEFAULT;
    public GenericBeanDefinition(String id, String beanClassName) {

        this.id = id;
        this.beanClassName = beanClassName;
    }
    public String getBeanClassName() {

        return this.beanClassName;
    }

    public boolean isSingleton() {
        return this.singleton;
    }
    public boolean isPrototype() {
        return this.prototype;
    }
    public String getScope() {
        return this.scope;
    }
    public void setScope(String scope) {
        this.scope = scope;
        this.singleton = SCOPE_SINGLETON.equals(scope) || SCOPE_DEFAULT.equals(scope);
        this.prototype = SCOPE_PROTOTYPE.equals(scope);

    }
}

XmlBeanDefinitionReader类中的loadBeanDefinition()也要修改,使其能读取XML文件中的scope属性。

至此,基本的BeanFactory就实现了。我们可以通过Xml文件装载Bean了。

原文地址:https://www.cnblogs.com/yzykkpl/p/10356228.html

时间: 2024-11-08 13:25:39

Spring源码试读--BeanFactory模拟实现的相关文章

Bootstrap 3.2.0 源码试读 2014/08/09

第一部分 normalize.css 104至110行 code,    /* 编辑代码 */ kbd,    /* 键盘输入的文本 */ pre, samp {    /* 范例,sample的简写 */   font-family: monospace, monospace;    /* 这个地方应该是写错了,第二字体应该是serif */   font-size: 1em; } 设置字体的大小为1em,字体为monospace. 111至119行 button, input, optgro

Bootstrap 3.2.0 源码试读 2014/08/04

第一部分 normalize.css 用于解决不同浏览器下显示不一致的问题 8至12行 html {   font-family: sans-serif;    /* 设置默认字体为 sans-serif */   -webkit-text-size-adjust: 100%;    /* 手机等设备转屏时,字体大小随着自动调整 */       -ms-text-size-adjust: 100%;    /* 但是如果禁用了缩放功能,则此设置无效 */ } text-size-adjust由

Bootstrap 3.2.0 源码试读 2014/08/11

第二部 @media print 212至217行   pre,   blockquote {    /* 块引用 */     border: 1px solid #999;     page-break-inside: avoid;   } 215行是个空格,是什么个意思?闲着蛋疼往前翻了翻,3.0.X版本没这个问题.根本没这行. 设置边框宽度为1px,实心,颜色#999,并且避免在内容里挺入分页符. page-break-inside据说只有opera支持,可用的值有3个 auto,默认值

Bootstrap 3.2.0 源码试读 2014/08/07

第一部分 normalize.css 70至72行 small {   font-size: 80%; } 设置small标签的字体大小为父容器字体的80%. 73至79行 sub, sup {   position: relative;   font-size: 75%;   line-height: 0;   vertical-align: baseline; } sup {   top: -.5em; } sub {   bottom: -.25em; } 先设置上标sup及下标sub,位

Bootstrap 3.2.0 源码试读 2014/08/10

第一部分 normalize.css 167至171行 fieldset {   padding: .35em .625em .75em;   margin: 0 2px;   border: 1px solid #c0c0c0; } legend {   padding: 0;   border: 0; } 表单分组标题的内添充为上 .23em,下 .75em, 左右 .625em.外边距为上下0,左右2px,边框为#c0c0c0.实心的1px宽. 分组标题的内添充为0,边框为0. 176至1

Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件

写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设计精巧,代码优美,值得每一名开发人员学习阅读. 在我最开始学习javaEE时,第一次接触Spring是从一个S(Struts)S(Spring)H(Herbinate)的框架开始.由java原生开发到框架开发转换过程中,那时我的印象里Struts负责控制层,herbinate负责数据层,而Sprin

Spring源码学习之BeanFactory体系结构

一.BeanFactory BeanFactory是Spring IOC容器的鼻祖,是IOC容器的基础接口,所有的容器都是从它这里继承实现而来.可见其地位.BeanFactory提供了最基本的IOC容器的功能,即所有的容器至少需要实现的标准. BeanFactory体系结构是典型的工厂方法模式,即什么样的工厂生产什么样的产品.BeanFactory是最基本的抽象工厂,而其他的IOC容器只不过是具体的工厂,对应着各自的Bean定义方法.但同时,其他容器也针对具体场景不同,进行了扩充,提供具体的服务

Spring源码系列:BeanFactory的创建

Spring的Ioc容器其实就是一个bean的关系网,依赖于core,bean,context三个组件来构建的.在spring中最核心的就是对于bean的管理.而bean又依托于我们的容器.本文将从顶层分析一下spring中beanFactory的具体创建过程,为后续的bean的生命周期提供一个基础. BeanFactory的继承体系 图片描述(最多50字) 从上图可以看到,BeanFactory有三个子类:ListableBeanFactoryHierarchicalBeanFactoryAu

Spring源码学习-容器BeanFactory(五) Bean的创建-探寻Bean的新生之路

写在前面 上面四篇文章讲了Spring是如何将配置文件一步一步转化为BeanDefinition的整个流程,下面就到了正式创建Bean对象实例的环节了,我们一起继续学习吧. 2.初始化Bean对象实例 Resource resource = new ClassPathResource("beanFactory.xml"); BeanFactory beanFactory = new XmlBeanFactory(resource); Student student = beanFact