动机

现在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. IDE - IDEA - tab - 方法相关的移动

    1. 概述 标题可能会改 一个 tab 里方法相关的操作 2. 前提 以默认的模式编辑 tab 对我来说, 就关掉 vim 插件 3. 操作 1. 查看文件结构 概述 唤出当前文件的 结构 唤出后可以 ...

  2. Linux shell lrzsz上传下载命令

    安装lrzsz做上传下载 工具使用secretCRT yum install -y lrzsz 1. 服务器<发送>文件,使用命令sz 2. 服务器<接收>文件,使用命令rz

  3. 毕向东java基础总结

    Java基础知识总结(超级经典) 写代码: 1,明确需求.我要做什么? 2,分析思路.我要怎么做?1,2,3. 3,确定步骤.每一个思路部分用到哪些语句,方法,和对象. 4,代码实现.用具体的java ...

  4. 解决:执行python脚本,提示错误:/usr/bin/python^M: 解释器错误: 没有那个文件或目录。

    执行python脚本,提示错误: /usr/bin/python^M: 解释器错误: 没有那个文件或目录. 产生错误原因: \r字符被显示为^M,这时候只需要删除这个字符就可以了. Linux环境下: ...

  5. 【网搜】禁止 number 输入非数字(Android仍有问题)

    目的:使用 number 表单,让其只可输入数字. 问题:ios 可正常限制,Android 仍可输入  [ e | . |  - |  + ]   这4个字符.猜测这4个字符在数值中为科学记数.小数 ...

  6. Kubernetes中网络相关知识

    流量转发和桥接 Kubernetes的核心是依靠Netfilter内核模块来设置低级别的集群IP负载均衡.需要两个关键的模块:IP转发和桥接 IP转发(IP Forward) IP forward 是 ...

  7. 安卓基础(Navigation)

    今天学习了简单的Navigation:页面导航. 页面导航的简单例子: MainAcitivity: package com.example.navigation; import android.su ...

  8. 吴裕雄 python 机器学习——数据预处理嵌入式特征选择

    import numpy as np import matplotlib.pyplot as plt from sklearn.svm import LinearSVC from sklearn.li ...

  9. Servlet_001 我的第一个servlet程序

    今天开启servlet学习 一.第一个Servlet程序 首先写我们的第一个servlet程序 第一步:新建我们的servlet程序(Web Project),命名为Servlet_001 第二步 : ...

  10. Laravel Vuejs 实战:开发知乎 (8)美化编辑器

    1.使用UEditor增量包: simple-ueditors 执行下载: git clone https://github.com/JellyBool/simple-ueditor.git 2.用此 ...