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模擬實現