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+SpringMVC+JDBC实现登录的示例(附源码)
-
Spring Cloud动态配置实现原理与源码分析
-
Hook实现Android 微信、陌陌 、探探位置模拟(附源码下载)
-
SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?
-
Spring源码剖析7:AOP实现原理详解
-
spring单元测试下模拟rabbitmq的实现
-
九、Spring之BeanFactory源码分析(一)
-
深入源码分析Spring注解的实现原理---@Import
-
Spring源码试读--BeanFactory模拟实现
-
简单实现Spring中BeanFactory原理