欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-02-21 10:31:16
动机 现在Springboot越来越便捷,如果简单的Spring应用,已无需再配置xml文件,基本可以实现全注解,即使是SpringCloud的那套东西,也都可以通过yaml配置完成。最近一年一直在用Springboot+JPA或者Springboot+MyBatis,基本上不用Spring和Spr ......

动机

现在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()。

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

修改后的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方法。

Spring源码试读--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来抽象资源

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

除了从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了。