1. 程式人生 > >Spring源碼試讀--BeanFactory模擬實現

Spring源碼試讀--BeanFactory模擬實現

發生 throwable 根據 scrip content .get fig equals eno

動機

現在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模擬實現