我們將分為幾步來撰寫簡易 IOC,首先設計組件,再設計介面,然后關注實作,
1. 設計組件,
我們還記得Spring中最重要的有哪些組件嗎?BeanFactory 容器,BeanDefinition Bean的基本資料結構,當然還需要加載Bean的資源加載器,大概最后最重要的就是這幾個組件,
容器用來存放初始化好的Bean,BeanDefinition 就是Bean的基本資料結構,比如Bean的名稱,Bean的屬性 PropertyValue,Bean的方法,是否延遲加載,依賴關系等,資源加載器就簡單了,就是一個讀取XML組態檔的類,讀取每個標簽并決議,
2. 設計介面
首先肯定需要一個BeanFactory,就是Bean容器,容器介面至少有2個最簡單的方法,一個是獲取Bean,一個注冊Bean.
/**
* 需要一個beanFactory 定義ioc 容器的一些行為 比如根據名稱獲取bean, 比如注冊bean,引數為bean的名稱,bean的定義
*
* @author stateis0
* @version 1.0.0
* @Date 2017/11/30
*/public interface BeanFactory {
/**
* 根據bean的名稱從容器中獲取bean物件
*
* @param name bean 名稱
* @return bean實體
* @throws Exception 例外
*/
Object getBean(String name) throws Exception;
/**
* 將bean注冊到容器中
*
* @param name bean 名稱
* @param bean bean實體
* @throws Exception 例外
*/
void registerBeanDefinition(String name, BeanDefinition bean) throws Exception;}
根據Bean的名字獲取Bean物件,注冊引數有2個,一個是Bean的名字,一個是 BeanDefinition 物件,
定義完了Bean最基本的容器,還需要一個最簡單 BeanDefinition 介面,我們為了方便,但因為我們這個不必考慮擴展,因此可以直接設計為類,BeanDefinition 需要哪些元素和方法呢?
需要一個 Bean 物件,一個Class物件,一個ClassName字串,還需要一個元素集合 PropertyValues,這些就能組成一個最基本的 BeanDefinition 類了,那么需要哪些方法呢?其實就是這些屬性的get set 方法,
我們看看該類的詳細:
package cn.thinkinjava.myspring;
/**
* bean 的定義
*
* @author stateis0
*/public class BeanDefinition {
/**
* bean
*/
private Object bean;
/**
* bean 的 CLass 物件
*/
private Class beanClass;
/**
* bean 的類全限定名稱
*/
private String ClassName;
/**
* 類的屬性集合
*/
private PropertyValues propertyValues = new PropertyValues();
/**
* 獲取bean物件
*/
public Object getBean() {
return this.bean;
}
/**
* 設定bean的物件
*/
public void setBean(Object bean) {
this.bean = bean;
}
/**
* 獲取bean的Class物件
*/
public Class getBeanclass() {
return this.beanClass;
}
/**
* 通過設定類名稱反射生成Class物件
*/
public void setClassname(String name) {
this.ClassName = name;
try {
this.beanClass = Class.forName(name);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 獲取bean的屬性集合
*/
public PropertyValues getPropertyValues() {
return this.propertyValues;
}
/**
* 設定bean的屬性
*/
public void setPropertyValues(PropertyValues pv) {
this.propertyValues = pv;
}
}
有了基本的 BeanDefinition 資料結構,還需要一個從XML中讀取并決議為 BeanDefinition 的操作類,首先我們定義一個 BeanDefinitionReader 介面,該介面只是一個標識,具體由抽象類去實作一個基本方法和定義一些基本屬性,比如一個讀取時需要存放的注冊容器,還需要一個委托一個資源加載器 ResourceLoader, 用于加載XML檔案,并且我們需要設定該構造器必須含有資源加載器,當然還有一些get set 方法,
package cn.thinkinjava.myspring;
import cn.thinkinjava.myspring.io.ResourceLoader;
import java.util.HashMap;
import java.util.Map;
/**
* 抽象的bean定義讀取類
*
* @author stateis0
*
/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
/**
* 注冊bean容器
*/
private Map<String, BeanDefinition> registry;
/**
* 資源加載器
*/
private ResourceLoader resourceLoader;
/**
* 構造器器必須有一個資源加載器, 默認插件創建一個map容器
*
* @param resourceLoader 資源加載器
*/
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.registry = new HashMap<>();
this.resourceLoader = resourceLoader;
}
/**
* 獲取容器
*/
public Map<String, BeanDefinition> getRegistry() {
return registry;
}
/**
* 獲取資源加載器
*/
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
有了這幾個抽象類和介面,我們基本能形成一個雛形,BeanDefinitionReader 用于從XML中讀取組態檔,生成 BeanDefinition 實體,存放在 BeanFactory 容器中,初始化之后,就可以呼叫 getBean 方法獲取初始化成功的Bean,形成一個完美的倍訓,
3. 如何實作
剛剛我們說了具體的流程:從XML中讀取組態檔, 決議成 BeanDefinition,最終放進容器,說白了就3步,那么我們就先來設計第一步,
1. 從XML中讀取組態檔, 決議成 BeanDefinition
我們剛剛設計了一個讀取BeanDefinition 的介面 BeanDefinitionReader 和一個實作它的抽象類 AbstractBeanDefinitionReader,抽象了定義了一些簡單的方法,其中由一個委托類-----ResourceLoader, 我們還沒有創建, 該類是資源加載器,根據給定的路徑來加載資源,
我們可以使用Java 默認的類別庫 java.net.URL 來實作,定義兩個類,一個是包裝了URL的類 ResourceUrl, 一個是依賴 ResourceUrl 的資源加載類,
ResourceUrl 代碼實作
/**
* 資源URL
*
/
public class ResourceUrl implements Resource {
/**
* 類別庫URL
*/
private final URL url;
/**
* 需要一個類別庫URL
*/
public ResourceUrl(URL url) {
this.url = url;
}
/**
* 從URL中獲取輸入流
*/
@Override
public InputStream getInputstream() throws Exception {
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
return urlConnection.getInputStream();
}
}
ResourceLoader 實作
/**
* 資源URL
*/
public class ResourceUrl implements Resource {
/**
* 類別庫URL
*/
private final URL url;
/**
* 需要一個類別庫URL
*/
public ResourceUrl(URL url) {
this.url = url;
}
/**
* 從URL中獲取輸入流
*/
@Override
public InputStream getInputstream() throws Exception {
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
return urlConnection.getInputStream();
}
}
當然還需要一個介面,只定義了一個抽象方法
package cn.thinkinjava.myspring.io;
import java.io.InputStream;
/**
* 資源定義
*
* @author stateis0
*/
public interface Resource {
/**
* 獲取輸入流
*/
InputStream getInputstream() throws Exception;
}
好了, AbstractBeanDefinitionReader 需要的元素已經有了,但是,很明顯該方法不能實作讀取 BeanDefinition 的任務,那么我們需要一個類去繼承抽象類,去實作具體的方法, 既然我們是XML 組態檔讀取,那么我們就定義一個 XmlBeanDefinitionReader 繼承 AbstractBeanDefinitionReader ,實作一些我們需要的方法, 比如讀取XML 的readrXML, 比如將決議出來的元素注冊到 registry 的 Map 中, 一些決議的細節,我們還是看代碼吧,
XmlBeanDefinitionReader 實作讀取組態檔并決議成Bean
package cn.thinkinjava.myspring.xml;
import cn.thinkinjava.myspring.AbstractBeanDefinitionReader;
import cn.thinkinjava.myspring.BeanDefinition;
import cn.thinkinjava.myspring.BeanReference;
import cn.thinkinjava.myspring.PropertyValue;
import cn.thinkinjava.myspring.io.ResourceLoader;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* 決議XML檔案
*
* @author stateis0
*/
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
/**
* 構造器,必須包含一個資源加載器
*
* @param resourceLoader 資源加載器
*/
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
super(resourceLoader);
}
public void readerXML(String location) throws Exception {
// 創建一個資源加載器
ResourceLoader resourceloader = new ResourceLoader();
// 從資源加載器中獲取輸入流
InputStream inputstream = resourceloader.getResource(location).getInputstream();
// 獲取檔案建造者工廠實體
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 工廠創建檔案建造者
DocumentBuilder docBuilder = factory.newDocumentBuilder();
// 檔案建造者決議流 回傳檔案物件
Document doc = docBuilder.parse(inputstream);
// 根據給定的檔案物件進行決議,并注冊到bean容器中
registerBeanDefinitions(doc);
// 關閉流
inputstream.close();
}
/**
* 根據給定的檔案物件進行決議,并注冊到bean容器中
*
* @param doc 檔案物件
*/
private void registerBeanDefinitions(Document doc) {
// 讀取檔案的根元素
Element root = doc.getDocumentElement();
// 決議元素的根節點及根節點下的所有子節點并添加進注冊容器
parseBeanDefinitions(root);
}
/**
* 決議元素的根節點及根節點下的所有子節點并添加進注冊容器
*
* @param root XML 檔案根節點
*/
private void parseBeanDefinitions(Element root) {
// 讀取根元素的所有子元素
NodeList nl = root.getChildNodes();
// 遍歷子元素
for (int i = 0; i < nl.getLength(); i++) {
// 獲取根元素的給定位置的節點
Node node = nl.item(i);
// 型別判斷
if (node instanceof Element) {
// 強轉為父型別元素
Element ele = (Element) node;
// 決議給給定的節點,包括name,class,property, name, value,ref
processBeanDefinition(ele);
}
}
}
/**
* 決議給給定的節點,包括name,class,property, name, value,ref
*
* @param ele XML 決議元素
*/
private void processBeanDefinition(Element ele) {
// 獲取給定元素的 name 屬性
String name = ele.getAttribute("name");
// 獲取給定元素的 class 屬性
String className = ele.getAttribute("class");
// 創建一個bean定義物件
BeanDefinition beanDefinition = new BeanDefinition();
// 設定bean 定義物件的 全限定類名
beanDefinition.setClassname(className);
// 向 bean 注入組態檔中的成員變數
addPropertyValues(ele, beanDefinition);
// 向注冊容器 添加bean名稱和bean定義
getRegistry().put(name, beanDefinition);
}
/**
* 添加組態檔中的屬性元素到bean定義實體中
*
* @param ele 元素
* @param beandefinition bean定義 物件
*/
private void addPropertyValues(Element ele, BeanDefinition beandefinition) {
// 獲取給定元素的 property 屬性集合
NodeList propertyNode = ele.getElementsByTagName("property");
// 回圈集合
for (int i = 0; i < propertyNode.getLength(); i++) {
// 獲取集合中某個給定位置的節點
Node node = propertyNode.item(i);
// 型別判斷
if (node instanceof Element) {
// 將節點向下強轉為子元素
Element propertyEle = (Element) node;
// 元素物件獲取 name 屬性
String name = propertyEle.getAttribute("name");
// 元素物件獲取 value 屬性值
String value = https://www.cnblogs.com/javastack/archive/2020/11/09/propertyEle.getAttribute("value");
// 判斷value不為空
if (value != null && value.length() > 0) {
// 向給定的 “bean定義” 實體中添加該成員變數
beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
} else {
// 如果為空,則獲取屬性ref
String ref = propertyEle.getAttribute("ref");
if (ref == null || ref.length() == 0) {
// 如果屬性ref為空,則拋出例外
throw new IllegalArgumentException(
"Configuration problem: <property> element for property '"
+ name + "' must specify a ref or value");
}
// 如果不為空,測創建一個 “bean的參考” 實體,構造引數為名稱,實體暫時為空
BeanReference beanRef = new BeanReference(name);
// 向給定的 “bean定義” 中添加成員變數
beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanRef));
}
}
}
}
}
可以說代碼注釋寫的非常詳細,該類方法如下:
-
public void readerXML(String location) 公開的決議XML的方法,給定一個位置的字串引數即可,
-
private void registerBeanDefinitions(Document doc) 給定一個檔案物件,并進行決議,
-
private void parseBeanDefinitions(Element root) 給定一個根元素,回圈決議根元素下所有子元素,
-
private void processBeanDefinition(Element ele) 給定一個子元素,并對元素進行決議,然后拿著決議出來的資料創建一個 BeanDefinition 物件,并注冊到BeanDefinitionReader 的 Map 容器(該容器存放著決議時的所有Bean)中,
-
private void addPropertyValues(Element ele, BeanDefinition beandefinition) 給定一個元素,一個 BeanDefinition 物件,決議元素中的 property 元素, 并注入到 BeanDefinition 實體中,
一共5步,完成了決議XML檔案的所有操作, 最終的目的是將決議出來的檔案放入到 BeanDefinitionReader 的 Map 容器中,
好了,到這里,我們已經完成了從XML檔案讀取并決議的步驟,那么什么時候放進BeanFactory的容器呢? 剛剛我們只是放進了 AbstractBeanDefinitionReader 的注冊容器中,因此我們要根據BeanFactory 的設計來實作如何構建成一個真正能用的Bean呢?因為剛才的哪些Bean只是一些Bean的資訊,沒有我們真正業務需要的Bean,
2. 初始化我們需要的Bean(不是Bean定義)并且實作依賴注入
我們知道Bean定義是不能干活的,只是一些Bean的資訊,就好比一個人,BeanDefinition 就相當你在公安局的檔案,但是你人不在公安局,可只要公安局拿著你的檔案就能找到你,就是這樣一個關系,
那我們就根據BeanFactory的設計來設計一個抽象類 AbstractBeanFactory,
package cn.thinkinjava.myspring.factory;
import cn.thinkinjava.myspring.BeanDefinition;
import java.util.HashMap;
/**
* 一個抽象類, 實作了 bean 的方法,包含一個map,用于存盤bean 的名字和bean的定義
*
* @author stateis0
*/
public abstract class AbstractBeanFactory implements BeanFactory {
/**
* 容器
*/
private HashMap<String, BeanDefinition> map = new HashMap<>();
/**
* 根據bean的名稱獲取bean, 如果沒有,則拋出例外 如果有, 則從bean定義物件獲取bean實體
*/
@Override
public Object getBean(String name) throws Exception {
BeanDefinition beandefinition = map.get(name);
if (beandefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beandefinition.getBean();
if (bean == null) {
bean = doCreate(beandefinition);
}
return bean;
}
/**
* 注冊 bean定義 的抽象方法實作,這是一個模板方法, 呼叫子類方法doCreate,
*/
@Override
public void registerBeanDefinition(String name, BeanDefinition beandefinition) throws Exception {
Object bean = doCreate(beandefinition);
beandefinition.setBean(bean);
map.put(name, beandefinition);
}
/**
* 減少一個bean
*/
abstract Object doCreate(BeanDefinition beandefinition) throws Exception;
}package cn.thinkinjava.myspring.factory;
import cn.thinkinjava.myspring.BeanDefinition;
import cn.thinkinjava.myspring.PropertyValue;
import cn.thinkinjava.myspring.BeanReference;
import java.lang.reflect.Field;
/**
* 實作自動注入和遞回注入(spring 的標準實作類 DefaultListableBeanFactory 有 1810 行)
*
* @author stateis0
*/
public class AutowireBeanFactory extends AbstractBeanFactory {
/**
* 根據bean 定義創建實體, 并將實體作為key, bean定義作為value存放,并呼叫 addPropertyValue 方法 為給定的bean的屬性進行注入
*/
@Override
protected Object doCreate(BeanDefinition beandefinition) throws Exception {
Object bean = beandefinition.getBeanclass().newInstance();
addPropertyValue(bean, beandefinition);
return bean;
}
/**
* 給定一個bean定義和一個bean實體,為給定的bean中的屬性注入實體,
*/
protected void addPropertyValue(Object bean, BeanDefinition beandefinition) throws Exception {
// 回圈給定 bean 的屬性集合
for (PropertyValue pv : beandefinition.getPropertyValues().getPropertyValues()) {
// 根據給定屬性名稱獲取 給定的bean中的屬性物件
Field declaredField = bean.getClass().getDeclaredField(pv.getname());
// 設定屬性的訪問權限
declaredField.setAccessible(true);
// 獲取定義的屬性中的物件
Object value = https://www.cnblogs.com/javastack/archive/2020/11/09/pv.getvalue();
// 判斷這個物件是否是 BeanReference 物件
if (value instanceof BeanReference) {
// 將屬性物件轉為 BeanReference 物件
BeanReference beanReference = (BeanReference) value;
// 呼叫父類的 AbstractBeanFactory 的 getBean 方法,根據bean參考的名稱獲取實體,此處即是遞回
value = getBean(beanReference.getName());
}
// 反射注入bean的屬性
declaredField.set(bean, value);
}
}
}
可以看到 doCreate 方法使用了反射創建了一個物件,并且還需要對該物件進行屬性注入,如果屬性是 ref 型別,那么既是依賴關系,則需要呼叫 getBean 方法遞回的去尋找那個Bean(因為最后一個Bean 的屬性肯定是基本型別),這樣就完成了一次獲取實體化Bean操作,并且也實作類依賴注入,
4. 總結
我們通過這些代碼實作了一個簡單的 IOC 依賴注入的功能,也更加了解了 IOC, 以后遇到Spring初始化的問題再也不會手足無措了,直接看原始碼就能解決,哈哈
good luck !!!
作者:莫那一魯道
鏈接:https://www.jianshu.com/p/6e25dc62e3a1
近期熱文推薦:
1.Java 15 正式發布, 14 個新特性,重繪你的認知!!
2.終于靠開源專案弄到 IntelliJ IDEA 激活碼了,真香!
3.我用 Java 8 寫了一段邏輯,同事直呼看不懂,你試試看,,
4.吊打 Tomcat ,Undertow 性能很炸!!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/207973.html
標籤:其他
上一篇:多執行緒真的比單執行緒快?
