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源码试读--BeanFactory模拟实现的更多相关文章
- Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md
写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...
- Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签
写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...
- Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作
写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...
- Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件
写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设 ...
- Spring源码学习之BeanFactory体系结构
一.BeanFactory BeanFactory是Spring IOC容器的鼻祖,是IOC容器的基础接口,所有的容器都是从它这里继承实现而来.可见其地位.BeanFactory提供了最基本的IOC容 ...
- Spring源码 20 手写模拟源码
参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...
- Java集合&Spring源码浅读
记录自己现在知道的,以后了解了更多的话,再继续补上来 Java集合类 Collection 接口 说明:是List,set 的父类.定义了集合初始模样.集合只存储对象. Jdk8文档,内部方法定义有: ...
- Spring源码学习之:模拟实现BeanFactory,从而说明IOC容器的大致原理
spring的IOC容器能够帮我们自动new对象,对象交给spring管之后我们不用自己手动去new对象了.那么它的原理是什么呢?是怎么实现的呢?下面我来简单的模拟一下spring的机制,相信看完之后 ...
- Spring源码学习-容器BeanFactory(五) Bean的创建-探寻Bean的新生之路
写在前面 上面四篇文章讲了Spring是如何将配置文件一步一步转化为BeanDefinition的整个流程,下面就到了正式创建Bean对象实例的环节了,我们一起继续学习吧. 2.初始化Bean对象实例 ...
随机推荐
- web项目获取路径
Java获取路径的各种方法: (1).request.getRealPath("/"); //不推荐使用获取工程的根路径 (2).request.getRealPath(requ ...
- Flask 学习之 路由
一.路由的基本定义 # 指定访问路径为 demo1 @app.route('/demo1') def demo1(): return 'demo1' 二.常用路由设置方式 @app.route('/u ...
- MyBatis-Spring整合之方式2
提前叨叨:此方法优化了上一个方式的事务支持,同时简化了一个bean的配置 1.在方式1的基础上修改UserDaoImp文件,改用使用继承SqlSessionDaoSupport的方式.代码如下: pu ...
- Windows上搭建hexo博客
1.windows上下载git(官网太慢),建议去其他地方下载啊(右键出现 Git Bash Here 的标志就安装完成) 2.安装npm:http://nodejs.cn/download/ 3.安 ...
- js把树形数据转成扁平数据
我就直接上代码了都是实际项目里面用到的 1.假设这个json就已经是树型结构数据了(如果不知道怎么实现树型结构数据请看我另一篇博客) var compressedArr=afcommon.treeDa ...
- java 对图片的添加文字描述,以及两张图片合成一张
最近公司一个需要,需要把商品的优惠卷分享链接,生成一个二维码然后和商品主图合成一张,并且在新合成的主图增加商品信息的描述,好了直接看合成后图片的样式 下面我就直接贴代码,首先是Contorller层 ...
- 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/ ...
- 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 ...
- sql语句查询指定月份数据
要求:查询出emp表中1981年2月份入职的员工 emp表 常用的两种方式: 1.YEAR查询年,MONTH查询月 SELECT * FROM emp WHERE ' 2.date_format (使 ...
- XMPP详解
https://www.jianshu.com/p/84d15683b61e https://www.cnblogs.com/lurenq/p/7026983.html 1. xmpp简介 XMPP ...