动机

现在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模拟实现的更多相关文章

  1. Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md

    写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...

  2. Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签

    写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...

  3. Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作

    写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...

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

    写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设 ...

  5. Spring源码学习之BeanFactory体系结构

    一.BeanFactory BeanFactory是Spring IOC容器的鼻祖,是IOC容器的基础接口,所有的容器都是从它这里继承实现而来.可见其地位.BeanFactory提供了最基本的IOC容 ...

  6. Spring源码 20 手写模拟源码

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  7. Java集合&Spring源码浅读

    记录自己现在知道的,以后了解了更多的话,再继续补上来 Java集合类 Collection 接口 说明:是List,set 的父类.定义了集合初始模样.集合只存储对象. Jdk8文档,内部方法定义有: ...

  8. Spring源码学习之:模拟实现BeanFactory,从而说明IOC容器的大致原理

    spring的IOC容器能够帮我们自动new对象,对象交给spring管之后我们不用自己手动去new对象了.那么它的原理是什么呢?是怎么实现的呢?下面我来简单的模拟一下spring的机制,相信看完之后 ...

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

    写在前面 上面四篇文章讲了Spring是如何将配置文件一步一步转化为BeanDefinition的整个流程,下面就到了正式创建Bean对象实例的环节了,我们一起继续学习吧. 2.初始化Bean对象实例 ...

随机推荐

  1. 获取表格数据转换为JSON字符串

    核心代码JavaScript代码: 方法一 function sc () { var myTable=document.getElementById("myTable"); //获 ...

  2. opencv:图像模糊

    高斯模糊 #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespa ...

  3. qq音乐解析API

    文档:www.tjit.net 开放的接口:api88.net 个人代码: input2(event){ //将字符转化为encodeURL编码,才能进行正确请求,这是这个接口要求的 //js自带的转 ...

  4. SPAN, RSPAN, ERSPAN

    该文档摘自:Home > CCIE Routing and Switching Study Group > Discussions 由 Deben 于 2015-2-6 上午6:50 创建 ...

  5. 五、request模块

    描述:requests是python的一个第三方HTTP(Hypertext Transfer Protocol,超文本传输协议)库,它比python自带的网络库urllib更加简单.方便和人性化:使 ...

  6. Color the ball(差分数组)

    N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色.但 ...

  7. zabbix4.4安装和简要设置

    简介 Zabbix 是一个基于 WEB 界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案.它能监视各种网络参数,保证服务器系统的安全运营:并提供灵活的通知机制以让系统管理员快速定位/解决 ...

  8. 三星前有note7,现有GalaxyS10,爆炸原因外力?

    编辑 | 于斌 出品 | 于见(mpyujian) 提到三星,不知道大家什么感觉,反正首先映入脑海的是在Note 7系列爆炸中,三星就让中国消费者欲哭无泪的画面.而也正是三星的态度,三星手机在这件事情 ...

  9. 计算机二级C语言选择题错题知识点记录。

    计算机二级C语言选择题错题知识点记录. 1,在数据流图中,用标有名字的箭头表示数据流.在程序流程图中,用标有名字的箭头表示控制流. 2,结构化程序设计的基本原则:自顶向下,逐步求精,模块化,限制使用g ...

  10. Ubuntu国内镜像

    编辑文件:sudo vim /etc/apt/sources.list 全部替换为:如下根据需要替换一个即可,修改保存后记得要执行更新软件包列表命令: sudo apt-get update 一.清华 ...