gitee地址:https://gitee.com/zr-zhang2021/easy-framework-spring/tree/v1-base-beanfactory
前言
为了更好地帮助自己阅读spring源码,学习源码中的思想,本次spring手撕系列参考了多方面的资料,目标从最简单的原理出发,不断地逼近spring源码,当然完全去模仿spring的难度太大,这里只是针对spring的核心主线,实现一些基本的功能,理解一些基本的抽象,方便以后能够把这些思想运用到实际工作中。
万丈高楼平地起,一个简单的IOC
容器,其本质就是实现:读取配置文件并提取bean
;注册bean
到容器;从容器里加载bean
;本章实现基础的BeanFactory
:支持xml
方式配置bean
,支持加载单例bean
。
首先从最基本的IOC使用开始:
(1) 定义spring-bean.xml:
<bean id="bean1" class="com.rui.test.TestBean1" scope="singleton">
</bean>
(2) TestBean1.java:
public class TestBean1 {
public void test(){
System.out.println("hello...");
}
}
(3) 测试BeanFactoryTest.java:
public class BeanFactoryTest {
@Test
public void testGetBean(){
Resource resource=new ClassPathResource("spring-bean.xml");
BeanFactory factory=new XmlBeanFactory(resource);
TestBean1 bean1 = (TestBean1)factory.getBean("bean1");
bean1.test();
}
(4) 结果:
hello...
开始设计:容器的类型?
从前面定义xml文件中可以看出,一个bean
中会包含id
、class
、scope
等属性,因此需要用用一个类(BeanDefinition)来定义,为了之后除了支持xml定义bean
之外还支持注解方式,BeanDefinition
被抽象成接口,最后,IOC
容器被设计成一个:map<String,BeanDefinition>,下面为了简单展示,scope属性后面再添加。
public interface BeanDefinition {
//获取该bean的全类名
String getBeanClassName();
}
GenericBeanDefinition为BeanDefinition最基本的实现类:
public class GenericBeanDefinition implements BeanDefinition {
private String beanId;
private String beanClassName;
public GenericBeanDefinition(String beanId,String beanClassName){
this.beanId=beanId;
this.beanClassName=beanClassName;
}
@Override
public String getBeanClassName() {
return this.beanClassName;
}
}
抽象:实现Resource
下面我们从(3)中一句一句地分析:首先是Resource resource=new ClassPathResource("spring-bean.xml");
参考《Spring源码深度解析》里的说法,Resource接口抽象了所有Spring内部使用到的底层资源。把不同来源的资源文件(比如File,ClassPath)都封装成相应的Resource实现:FileSystemResource、ClassPathResource等。在这里,Resource接口主要是提供getInputStream()的方法,此外还提供getDescription()方法用来打印错误信息。
public interface Resource {
InputStream getInputStream() throws IOException;
String getDesciption();
}
最最最核心的思路
BeanFactory factory=new XmlBeanFactory(resource);这一句中到底做了什么?
掌握最核心的思路,其他的抽象都是基于这个核心引申出来的。
本章一开始有说,IOC
容器本质上就是:(1)读取配置文件,把提取的bean
信息注册到IOC
容器;(2)需要使用bean
时再从容器里加载bean
。参考一下spring
源码,根据单一职责原则,xmlBeanFactory
中有两个非常核心的角色,XmlBeanDefinitionReader
的职责对应上面的(1),DefaultBeanFactory
的职责对应(2),被xmlBeanFactory
继承。
public class XmlBeanFactory extends DefaultBeanFactory {
private final XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource){
this.reader.loadBeanDefinition(resource);
}
}
BeanFactory
:提供注册、加载Bean
接口DefaultBeanFactory
:实现注册Bean
方法,加载Bean
方法【非常重要】XmlBeanDefinitionReader
:解析xml文件,并利用DefaultBeanFactory
的注册方法注册Bean
抽象:实现BeanDefinitionRegistry
对于工厂BeanFactory
而言,设计的初衷是获取关于bean
对象的信息,BeanDefinition
的注册与获取属于“内部”的操作,我们不希望将来BeanFactory
接口暴露BeanDefinition
注册与获取方法,而只提供关于bean
的操作,因此BeanDefinitionRegistry
的角色就出现了。
DefaultBeanFactory实现了BeanDefinitionRegistry接口的方法:
@Override
public void registerBeanDefinition(String beanId, BeanDefinition bd) {
this.beanDefinitionMap.put(beanId,bd);
}
@Override
public BeanDefinition getBeanDefinition(String beanId) {
return this.beanDefinitionMap.get(beanId);
}
【重要】实现XmlBeanDefinitionReader的解析、注册
public class XmlBeanDefinitionReader {
//依赖于BeanDefinition注册器
private BeanDefinitionRegistry registry;
//构造函数,把注册器传进来
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry){
this.registry=registry;
}
//解析资源
public void loadBeanDefinition(Resource resource){
try {
InputStream inputStream = resource.getInputStream();
SAXReader saxReader = new SAXReader();
Document doc = saxReader.read(inputStream);//利用dom4j工具读取resource的输入流
Element root= doc.getRootElement();//beans标签
Iterator<Element> iterator = root.elementIterator();
while(iterator.hasNext()){
Element element= iterator.next();//bean标签
String beanId = element.attributeValue("id");//bean标签里面的id属性
String beanClassName = element.attributeValue("class");//bean标签里面的class属性
//注册该bean
GenericBeanDefinition bd = new GenericBeanDefinition(beanId, beanClassName);//BeanDefinition的实现类
registry.registerBeanDefinition(beanId,bd);//利用注册器的注册方法注册BeanDefinition
}
} catch (Exception e) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + resource.getDesciption(),e);
}
}
}
【重要】实现DefaultBeanFactory加载Bean
TestBean1 bean1 = (TestBean1)factory.getBean("bean1");这一句到底经历了什么呢?
前面也说过实质上是DefaultBeanFactory实现了getBean(String beanId)这个方法的。
DefaultBeanFactory:
public class DefaultBeanFactory implements BeanFactory,BeanDefinitionRegistry{
private final ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap= new ConcurrentHashMap<String,BeanDefinition>(64);
private ClassLoader classLoader;
@Override
public Object getBean(String beanId) {
BeanDefinition bd = this.getBeanDefinition(beanId);
if (bd==null){
return null;
}
return this.createBean(bd);
}
private Object createBean(BeanDefinition bd){
String beanClassName = bd.getBeanClassName();
ClassLoader classLoader = this.getClassLoader();
try {
Class<?> clazz = classLoader.loadClass(beanClassName);
return clazz.newInstance();
} catch (Exception e) {
throw new BeanCreationException("create bean for "+ beanClassName +" failed",e);
}
}
public ClassLoader getClassLoader(){
return this.classLoader!=null?this.classLoader: ClassUtils.getDefaultClassLoader();
}
到这里,一个简单的IOC容器的雏形就已经完成了,其支持了Bean
的注册与加载。
【重要】实现单例singleton
singleton单例模式也就是说在xml声明bean
时,scope
属性设置为singleton(默认也是单例),此时该bean
实例在IOC容器里只存在一个,而不会出现加载一次bean
就产生一个新的对象,怎么实现呢?
首先,在BeanDefinition
接口中添加scope
属性的设置和获取,然后还有单例状态(isSingleton)和多例状态(isPrototype)
public interface BeanDefinition {
public static final String SCOPE_SINGLETON="singleton";
public static final String SCOPE_PROTOTYPE="prototype";
public static final String SCOPE_DEFAULT="";
boolean isSingleton();
boolean isPrototype();
String getScope();
void setScope(String scope);
String getBeanClassName();
}
其次,GenericBeanDefinition再实现这些方法,也就是说在reader解析注册bean
时能够设置scope
的属性和单例/多例状态。
public class GenericBeanDefinition implements BeanDefinition {
private String beanId;
private String beanClassName;
private boolean isSingleton=true;
private boolean isPrototype=false;
private String scope=SCOPE_DEFAULT;
@Override
public void setScope(String scope) {
this.scope=scope;
this.isSingleton=SCOPE_SINGLETON.equals(scope)||SCOPE_DEFAULT.equals(scope);
this.isPrototype=SCOPE_PROTOTYPE.equals(scope);
}
......
}
然后,需要在getBean时维护一个单例,也就是在每次加载bean
时判断是否加载过该bean实例。这里抽象出SingletonBeanRegistry。
该实现类DefaultSingletonBeanDefinition里面用一个map(singletonBeanObjectMap)维护单例对象。
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
public final ConcurrentHashMap<String,Object> singletonBeanObjectMap=new ConcurrentHashMap<>(64);
@Override
public void registerSingleton(String beanId, Object singletonObject) {
singletonBeanObjectMap.put(beanId,singletonObject);
}
@Override
public Object getSingleton(String beanId) {
return this.singletonBeanObjectMap.get(beanId);
}
}
最后,在DefaultBeanFactory里加载bean时就可以先获取singletonBeanObjectMap里获取这个singleton,如果没有,再createBean()。
public class DefaultBeanFactory extends DefaultSingletonBeanRegistry
implements BeanFactory,BeanDefinitionRegistry{
@Override
public Object getBean(String beanId) {
BeanDefinition bd = this.getBeanDefinition(beanId);
if (bd==null){
return null;
}
if (bd.isSingleton()){
Object singleton = this.getSingleton(beanId);
if (singleton==null){
singleton = this.createBean(bd);
this.registerSingleton(beanId, singleton);
}
return singleton;
}
return this.createBean(bd);
}
小扩展:ApplicationContext的实现
在实际的应用的,大部分使用的是ApplicationContext来取代BeanFactory,在spring源码中ApplicationContext继承了BeanFactory,并扩展了一些功能,具体扩展了哪些地方,在后面再详细说吧。在这里为了迎合ApplicationContext的使用,先简单设计并实现ApplicationContext。
先写个测试用例:
public class ApplicationContextTest {
@Test
public void testApplicationContext(){
ApplicationContext context=new ClassPathXmlApplicationContext("spring-bean.xml");
TestBean1 bean1=(TestBean1)context.getBean("bean1");
bean1.test();
}
}
ApplicationContext继承了BeanFactory的getBean功能:
ClassPathXmlApplicationContext实现类:
public class ClassPathXmlApplicationContext implements ApplicationContext {
private DefaultBeanFactory factory;
public ClassPathXmlApplicationContext(String configFile) {
this.factory=new DefaultBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Resource resource = new ClassPathResource(configFile);
reader.loadBeanDefinition(resource);
}
@Override
public Object getBean(String beanId) {
return factory.getBean(beanId);
}
}
到这里,测试用例已经可以顺利运行了,但是会产生两个小问题需要优化。
设计模式:模板方法模式的运用
接着上面的,第一个问题是ApplicationContext
的扩展问题,前面有提过我们的resource
资源可以来自不同的来源,Resource
有ClassPathResource
也有FileSystemResource
。
相应的ApplicationContext
有ClassPathXmlApplicationContext
也有FileSystemXmlApplicationContext
,如果以后有其他ApplicationContext
的实现类,例如FileSystemXmlApplicationContext
,那么就要在里面重新写一遍ClassPathXmlApplicationContext
的内容,不同的是把输入转化为Resource
时用到FileSystemResource
。
public class FileSystemXmlApplicationContext extends AbstractApplicationContext {
private DefaultBeanFactory factory;
public FileSystemXmlApplicationContext(String configFile) {
this.factory=new DefaultBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Resource resource = new FileSystemResource(configFile);
reader.loadBeanDefinition(resource);
}
@Override
public Object getBean(String beanId) {
return factory.getBean(beanId);
}
}
针对这个问题,首先需要抽象出新的类AbstractApplicationContext,来解决各实现类重复代码的问题,再者运用模板方法模式,也就是在父类定义好执行的模板/骨架(包含抽象方法),并在子类各自重写抽象方法。来解决对不同来源的文件转化为Resource时用不同Resource实现类(ClassPathResource和FileSystemResource)的问题。
AbstractApplicationContext抽象类:
public abstract class AbstractApplicationContext implements ApplicationContext {
private DefaultBeanFactory factory = null;
public AbstractApplicationContext(String configFile){
factory = new DefaultBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Resource resource = this.getResourceByPath(configFile);
reader.loadBeanDefinitions(resource);
}
public Object getBean(String beanID) {
return factory.getBean(beanID);
}
protected abstract Resource getResourceByPath(String path);
}
ClassPathXmlApplicationContext:
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
public ClassPathXmlApplicationContext(String configFile) {
super(configFile);
}
@Override
protected Resource getResourceByPath(String path) {
return new ClassPathResource(path);
}
}
抽象:实现ConfigurableBeanFactory
第二个小问题是关于ClassLoader的问题,很多地方都用到getBeanClassLoader,比如说ClassPathResource获取流的时候,还有DefaultBeanFactory在createBean的时候,但目前的实现都是使用默认写死的(ClassUtils.getDefaultClassLoader()),我们希望支持ClassLoader的传入,因此抽象出ConfigurableBeanFactory接口。