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

Posted Nobody Knows

tags:

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

以上是关于Spring源码试读--BeanFactory模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

Spring源码解析 - BeanFactory接口体系解读

Spring源码解析 - BeanFactory

Spring BeanFactory源码学习

Spring源码学习之BeanFactory体系结构

Spring源码 BeanFactory和FactoryBean是什么?

Spring IOC 源码简单分析 01 - BeanFactory