动机

现在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. web项目获取路径

    Java获取路径的各种方法:  (1).request.getRealPath("/"); //不推荐使用获取工程的根路径 (2).request.getRealPath(requ ...

  2. Flask 学习之 路由

    一.路由的基本定义 # 指定访问路径为 demo1 @app.route('/demo1') def demo1(): return 'demo1' 二.常用路由设置方式 @app.route('/u ...

  3. MyBatis-Spring整合之方式2

    提前叨叨:此方法优化了上一个方式的事务支持,同时简化了一个bean的配置 1.在方式1的基础上修改UserDaoImp文件,改用使用继承SqlSessionDaoSupport的方式.代码如下: pu ...

  4. Windows上搭建hexo博客

    1.windows上下载git(官网太慢),建议去其他地方下载啊(右键出现 Git Bash Here 的标志就安装完成) 2.安装npm:http://nodejs.cn/download/ 3.安 ...

  5. js把树形数据转成扁平数据

    我就直接上代码了都是实际项目里面用到的 1.假设这个json就已经是树型结构数据了(如果不知道怎么实现树型结构数据请看我另一篇博客) var compressedArr=afcommon.treeDa ...

  6. java 对图片的添加文字描述,以及两张图片合成一张

    最近公司一个需要,需要把商品的优惠卷分享链接,生成一个二维码然后和商品主图合成一张,并且在新合成的主图增加商品信息的描述,好了直接看合成后图片的样式 下面我就直接贴代码,首先是Contorller层 ...

  7. https://www.cnblogs.com/chanshuyi/p/alibaba_review_3_level.html

    https://www.cnblogs.com/chanshuyi/p/alibaba_review_3_level.html http://www.cnblogs.com/skywang12345/ ...

  8. python linux windows 历史版本下载

    Index of /ftp/python/ ../ 2.0/ 14-Feb-2019 14:53 - 2.0.1/ 06-Aug-2001 02:14 - 2.1/ 06-Aug-2001 02:14 ...

  9. sql语句查询指定月份数据

    要求:查询出emp表中1981年2月份入职的员工 emp表 常用的两种方式: 1.YEAR查询年,MONTH查询月 SELECT * FROM emp WHERE ' 2.date_format (使 ...

  10. XMPP详解

    https://www.jianshu.com/p/84d15683b61e https://www.cnblogs.com/lurenq/p/7026983.html 1. xmpp简介 XMPP ...