分析完與Spring IoC功能相關的介面之后,接下來我們就要來自定義Spring IoC功能了,
首先,我們先來看一下需求:現要對下面的組態檔進行決議,并自定義Spring框架的IoC功能對涉及到的物件進行管理,
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl">
<property name="username" value="zhangsan"></property>
<property name="password" value="123456"></property>
</bean>
<bean id="userService" class="com.meimeixia.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
要想完成這一需求,可不是件容易的事情,我們得分如下幾部分來進行定義,
- 定義bean相關的pojo類,
- 定義注冊表相關的類,
- 定義決議器相關的類,
- 定義IoC容器相關的類,
下面,我們先來定義bean相關的pojo類,
定義bean相關的pojo類
首先,我們先使用IDEA來創建一個Maven工程,工程名字你可以取為liayun_spring,然后再來創建相應的包,這里為了讓大家清楚地看到我都創建了哪些包,干脆我把最終Maven工程的結構給大家展示出來吧!

PropertyValue類
在這一部分,我們會創建不同的類,第一個類就是PropertyValue,該類的作用就是用來封裝bean的屬性的,看一下一開始的組態檔,最終我們是要決議該組態檔的,所以嚴格來說,PropertyValue這個類是用來封裝<bean>標簽的<property>子標簽中的屬性的,由于<property>子標簽中有name、ref、value等屬性,因此PropertyValue類里面至少得有name、ref、value這三個屬性,
package com.meimeixia.framework.beans;
/**
* 用來封裝bean標簽下的property標簽的屬性,屬性有這些:
* name屬性
* ref屬性
* value屬性:給基本資料型別及String型別的資料賦值
* @author liayun
* @create 2021-09-20 9:41
*/
public class PropertyValue {
private String name;
private String ref;
private String value;
public PropertyValue() {
}
public PropertyValue(String name, String ref, String value) {
this.name = name;
this.ref = ref;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
MutablePropertyValues類
創建完PropertyValue類之后,接下來我們再來創建第二個類,那就是MutablePropertyValues,為什么要創建這個類呢?因為一個<bean>標簽可以有多個<property>子標簽,而每一個<property>子標簽都會被封裝成一個PropertyValue物件,對于多個PropertyValue物件我們就要進行存盤以及管理了,所以在這里我們就要創建一個MutablePropertyValues類,用來存盤并管理多個PropertyValue物件了,
注意,在創建該類時,我們需要用到迭代器模式,所以該類得去實作Iterable介面,至于為什么這兒要用到迭代器模式,我不說,相信大家也知道,因為MutablePropertyValues類是用來存盤并管理多個PropertyValue物件的,所以它必須是可以迭代的,
package com.meimeixia.framework.beans;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 用于存盤和管理多個PropertyValue物件
* @author liayun
* @create 2021-09-20 10:15
*/
public class MutablePropertyValues implements Iterable<PropertyValue> {
// 定義List集合物件,用來存盤PropertyValue物件
private final List<PropertyValue> propertyValueList; // 如果用final修飾的話,那么就意味著它只能被賦值一次
// 以下構造方法是用來為以上成員變數賦值的
public MutablePropertyValues() {
this.propertyValueList = new ArrayList<PropertyValue>();
}
public MutablePropertyValues(List<PropertyValue> propertyValueList) {
if (propertyValueList == null) {
this.propertyValueList = new ArrayList<PropertyValue>();
} else {
this.propertyValueList = propertyValueList;
}
}
// 獲取所有的PropertyValue物件,當然,是以陣列的形式回傳
public PropertyValue[] getPropertyValues() {
// 將集合轉換為陣列并回傳
return propertyValueList.toArray(new PropertyValue[0]);
}
// 根據name屬性值獲取PropertyValue物件
public PropertyValue getPropertyValue(String propertyName) {
// 遍歷集合物件
for (PropertyValue propertyValue : propertyValueList) {
if (propertyValue.getName().equals(propertyName)) {
return propertyValue;
}
}
return null;
}
// 判斷集合是否為空
public boolean isEmpty() {
return propertyValueList.isEmpty();
}
// 往集合里面添加PropertyValue物件,注意,該方法的回傳值型別是MutablePropertyValues,目的是為了能實作鏈式編程
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
// 判斷集合中存盤的PropertyValue物件是否和傳遞進來的重復了,如果重復了,那么就進行覆寫
for (int i = 0; i < propertyValueList.size(); i++) {
// 獲取集合中每一個PropertyValue物件
PropertyValue currentPv = propertyValueList.get(i);
if (currentPv.getName().equals(pv.getName())) {
propertyValueList.set(i, pv); // 如果重復了,那么就進行覆寫
return this; // 回傳當然物件(即MutablePropertyValues型別的物件),目的就是實作鏈式編程
}
}
this.propertyValueList.add(pv); // 如果沒有重復的,那么就直接添加進集合里面去
return this; // 回傳當然物件(即MutablePropertyValues型別的物件),目的就是實作鏈式編程
}
// 判斷是否有指定name屬性值的PropertyValue物件,有的話回傳true,沒有的話回傳false
public boolean contains(String propertyName) {
return getPropertyValue(propertyName) != null;
}
// 獲取迭代器物件
@Override
public Iterator<PropertyValue> iterator() {
/*
* 獲取迭代器物件的這個方法應該如何來實作呢?
*
* 由于PropertyValue物件是存盤在一開始定義的List集合里面的,所以這里我們直接呼叫其獲取迭代器的方法(即iterator)即可,
*/
return propertyValueList.iterator();
}
}
相信大家可以看到,我們在以上MutablePropertyValues類中定義了很多方法,這些方法我也寫了一些比較詳細的注釋,相信大家都能看懂,
BeanDefinition類
接下來,我們再來創建bean相關的pojo類里面的最后一個類,也是最重要的一個類,叫BeanDefinition,其實,之前我們在分析Spring IoC功能的相關介面時,就見過類似這玩意,只不過它是介面,而在這里我們是直接將其定義成類了,主要是為了簡單,圖省事,
BeanDefinition類主要是用來封裝bean資訊的,主要包含id(即bean物件的名稱)、class(需要交由Spring管理的類的全類名)及子標簽<property>中的資料,
package com.meimeixia.framework.beans;
/**
* 用來封裝bean標簽資料,包含:
* id屬性
* class屬性
* property子標簽中的資料
* @author liayun
* @create 2021-09-20 11:06
*/
public class BeanDefinition {
private String id;
private String className;
private MutablePropertyValues propertyValues;
public BeanDefinition() {
this.propertyValues = new MutablePropertyValues();
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public MutablePropertyValues getPropertyValues() {
return propertyValues;
}
public void setPropertyValues(MutablePropertyValues propertyValues) {
this.propertyValues = propertyValues;
}
}
可以看到,雖然這個類特別重要,但是創建起來還是比較簡單的,
定義注冊表相關的類
BeanDefinitionRegistry介面
定義完bean相關的pojo類之后,接下來我們就要來定義注冊表相關的類了,當然,這一部分就不僅僅是有類了,還有介面,說白了,在這一部分,我們會創建一個介面及其子實作類,至于介面的話,我們就命名為BeanDefinitionRegistry了,下面我們就來看一下該介面需要定義哪些功能?
BeanDefinitionRegistry介面應定義注冊表的相關操作,所以需要定義如下功能:
- 注冊BeanDefinition物件到注冊表中,
- 從注冊表中洗掉指定名稱的BeanDefinition物件,
- 根據名稱從注冊表中獲取BeanDefinition物件,
- 判斷注冊表中是否包含指定名稱的BeanDefinition物件,
- 獲取注冊表中BeanDefinition物件的個數,
- 獲取注冊表中所有的BeanDefinition物件的名稱,
以上這些功能,相信大家應該很熟悉,因為之前我們在分析Spring原始碼里面的BeanDefinitionRegistry介面時就看到過,BeanDefinitionRegistry介面也定義了以上這些功能,
根據以上分析,我們創建出來的BeanDefinitionRegistry介面就應該是下面這個樣子的,
package com.meimeixia.framework.beans.factory.support;
import com.meimeixia.framework.beans.BeanDefinition;
/**
* 注冊表物件所屬介面
* @author liayun
* @create 2021-09-20 11:16
*/
public interface BeanDefinitionRegistry {
// 注冊BeanDefinition物件到注冊表中
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
// 從注冊表中洗掉指定名稱的BeanDefinition物件
void removeBeanDefinition(String beanName) throws Exception;
// 根據名稱從注冊表中獲取BeanDefinition物件
BeanDefinition getBeanDefinition(String beanName) throws Exception;
boolean containsBeanDefinition(String beanName);
int getBeanDefinitionCount();
String[] getBeanDefinitionNames();
}
BeanDefinitionRegistry介面創建完畢之后,接下來我們就要來創建它的子實作類了,注意,這里我們只創建一個子實作類,
SimpleBeanDefinitionRegistry類
我們在創建該類時,要讓該類去實作BeanDefinitionRegistry介面,并去重寫它里面所有的抽象方法,注意,在該類里面我們還得定義一個Map集合,讓其作為注冊表容器,
package com.meimeixia.framework.beans.factory.support;
import com.meimeixia.framework.beans.BeanDefinition;
import java.util.HashMap;
import java.util.Map;
/**
* 注冊表介面的子實作類
* @author liayun
* @create 2021-09-20 11:23
*/
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
// 定義一個Map集合,用來存盤BeanDefinition物件
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>(); // 注意,在這里我們選擇創建的是雙列集合,因為我們不僅要存盤BeanDefinition物件,還要存盤其名稱
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
beanDefinitionMap.put(beanName, beanDefinition);
}
@Override
public void removeBeanDefinition(String beanName) throws Exception {
beanDefinitionMap.remove(beanName);
}
@Override
public BeanDefinition getBeanDefinition(String beanName) throws Exception {
return beanDefinitionMap.get(beanName);
}
@Override
public boolean containsBeanDefinition(String beanName) {
return beanDefinitionMap.containsKey(beanName);
}
@Override
public int getBeanDefinitionCount() {
return beanDefinitionMap.size();
}
@Override
public String[] getBeanDefinitionNames() {
return beanDefinitionMap.keySet().toArray(new String[0]);
}
}
定義決議器相關的類
接下來,我們就來定義決議器相關的介面和類,相信大家也都知道了,我們均是參照Spring里面的介面和類來定義的,所以在這一部分我們就將介面命名為BeanDefinitionReader,既然它是一個介面的話,那么它里面定義的便是最基本的功能規范了,還有,我們還得為該介面創建一個子實作類,名字不妨就叫做XmlBeanDefinitionReader,
為什么我們還要創建BeanDefinitionReader介面呢?之前我帶著大家分析Spring IoC功能相關的介面時,你也看到了,針對于不同的組態檔,Spring會提供不同的子類來進行決議,例如,決議properties格式的組態檔用的是PropertiesBeanDefinitionReader類,決議XML格式的組態檔用的是XmlBeanDefinitionReader,大家要是能夠去看一下BeanDefinitionReader介面的繼承體系的話,你會發現這倆類都是其子實作類,當然了,這里我們在自定義Spring IoC功能時,只會針對XML格式的組態檔來創建決議類,
BeanDefinitionReader介面
由于BeanDefinitionReader是用來決議組態檔并在注冊表中注冊bean的資訊的,所以我們應在它里面定義如下兩個規范,
- 獲取注冊表的功能,讓外界可以通過該物件獲取注冊表物件,
- 加載組態檔,并注冊bean資料,
根據以上分析,我們創建出來的eanDefinitionReader介面就應該是下面這個樣子的,
package com.meimeixia.framework.beans.factory.support;
/**
* 用來決議組態檔的,而且該介面只是定義了規范,具體的應由子類來實作
* @author liayun
* @create 2021-09-20 11:49
*/
public interface BeanDefinitionReader {
// 獲取注冊表物件
BeanDefinitionRegistry getRegistry();
// 加載組態檔,并在注冊表中進行注冊
void loadBeanDefinitions(String configLocation) throws Exception;
}
XmlBeanDefinitionReader類
BeanDefinitionReader介面創建完畢之后,接下來我們就來創建其子實作類,名字上面我也說了,就叫XmlBeanDefinitionReader,
相信大家也知道了,XmlBeanDefinitionReader類是專門用來解析XML格式的組態檔的,而且創建該類時,不用我說,大家都應該知道該類得實作BeanDefinitionReader介面并去重寫它里面的兩個功能,如下所示,
package com.meimeixia.framework.beans.factory.xml;
import com.meimeixia.framework.beans.BeanDefinition;
import com.meimeixia.framework.beans.MutablePropertyValues;
import com.meimeixia.framework.beans.PropertyValue;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionReader;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionRegistry;
import com.meimeixia.framework.beans.factory.support.SimpleBeanDefinitionRegistry;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/**
* 針對XML格式的組態檔進行決議的類
* @author liayun
* @create 2021-09-20 11:58
*/
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
/*
* 宣告注冊表物件
*
* 為什么要在成員變數位置處宣告注冊表物件呢?大家不妨來想一下,XmlBeanDefinitionReader物件(即決議器)是
* 專門用來決議XML格式的組態檔的,決議完之后,自然是會將組態檔里面的<bean>標簽封裝成BeanDefinition對
* 象,那么這些BeanDefinition物件是存放在哪呢?是不是就是注冊到了注冊表物件里面呀?所以,我們就在這個位置聲
* 明了一個注冊表物件,
*/
private BeanDefinitionRegistry registry;
public XmlBeanDefinitionReader() {
registry = new SimpleBeanDefinitionRegistry();
}
/**
* 獲取注冊表物件
* @return 直接回傳成員注冊表物件
*/
@Override
public BeanDefinitionRegistry getRegistry() {
return registry;
}
/**
* 加載組態檔,并在注冊表中進行注冊
* @param configLocation 類路徑下組態檔的路徑
* @throws Exception
*/
@Override
public void loadBeanDefinitions(String configLocation) throws Exception {
// 使用dom4j進行XML組態檔的決議
SAXReader reader = new SAXReader();
// 獲取類路徑下的組態檔,注意,這里我們只實作類路徑下的組態檔的加載
InputStream is = XmlBeanDefinitionReader.class.getClassLoader().getResourceAsStream(configLocation);
Document document = reader.read(is);
// 根據Document物件獲取根標簽物件(根標簽很明顯就是<beans>標簽)
Element rootElement = document.getRootElement();
// 獲取根標簽下所有的<bean>子標簽物件
List<Element> beanElements = rootElement.elements("bean");
// 遍歷集合
for (Element beanElement : beanElements) {
// 獲取id屬性
String id = beanElement.attributeValue("id");
// 獲取class屬性
String className = beanElement.attributeValue("class");
// 將id屬性和class屬性封裝到BeanDefinition物件中
// 1. 創建BeanDefinition物件
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setId(id);
beanDefinition.setClassName(className);
// 創建MutablePropertyValues物件
MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
// 獲取<bean>標簽下所有的<property>子標簽物件
List<Element> propertyElements = beanElement.elements("property");
for (Element propertyElement : propertyElements) {
String name = propertyElement.attributeValue("name");
String ref = propertyElement.attributeValue("ref");
String value = propertyElement.attributeValue("value");
PropertyValue propertyValue = new PropertyValue(name, ref, value);
mutablePropertyValues.addPropertyValue(propertyValue);
}
// 將MutablePropertyValues物件封裝到BeanDefinition物件中
beanDefinition.setPropertyValues(mutablePropertyValues);
// 將BeanDefinition物件注冊到注冊表中
registry.registerBeanDefinition(id, beanDefinition);
}
}
}
注意,由于我們要在XmlBeanDefinitionReader類的loadBeanDefinitions方法中使用dom4j進行XML組態檔的決議,所以我們應在工程的pom.xml檔案里面匯入對應的jar包的坐標,如下所示,
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
定義IoC容器相關的類
定義完決議器相關的介面和類之后,接下來我們來定義IoC容器相關的介面和類,大家要注意了,這一部分是我們自定義Spring IoC功能最核心的部分,而且在這一部分我們需要定義如下這些介面和類,
BeanFactory介面
在該介面中我們需要定義IoC容器的統一規范,即獲取bean物件的方法,
package com.meimeixia.framework.beans.factory;
/**
* IoC容器父介面
* @author liayun
* @create 2021-09-20 17:34
*/
public interface BeanFactory {
// 根據bean物件的名稱獲取bean物件
Object getBean(String name) throws Exception;
// 根據bean物件的名稱獲取bean物件,并進行型別轉換
<T> T getBean(String name, Class<? extends T> clazz) throws Exception;
}
ApplicationContext介面
BeanFactory介面創建完畢之后,接下來我們來創建它的一個子介面,這里我們就取名為ApplicationContext了,
之前我帶領著大家分析Spring IoC功能相關的介面時,相信大家也知道了ApplicationContext屬于非延時加載,也就是說(使用者)在創建容器物件的時候,就會去加載組態檔,并實體化bean物件,最終將其存盤在容器里面,
這里,我也不廢話了,直接給出ApplicationContext介面的代碼,如下所示,
package com.meimeixia.framework.context;
import com.meimeixia.framework.beans.factory.BeanFactory;
/**
* 定義非延時加載功能
* @author liayun
* @create 2021-09-20 17:39
*/
public interface ApplicationContext extends BeanFactory {
// 進行組態檔加載并進行物件創建
void refresh() throws Exception;
}
可以看到,我們在創建ApplicationContext介面時,在它里面只定義了一個refresh方法,該方法主要完成以下兩個功能,
- 加載組態檔,
- 根據注冊表中的BeanDefinition物件封裝的資料進行bean物件的創建,
AbstractApplicationContext類
ApplicationContext介面創建完畢之后,接下來我們來創建它的一個子實作類,這里我們就取名為AbstractApplicationContext了,
那么,AbstractApplicationContext類有什么特點以及作用呢?
-
作為ApplicationContext介面的子類,所以該類也是非延時加載,也就是立即加載,這樣,我們就需要在該類中定義一個Map集合,以作為bean物件存盤的容器,
-
宣告BeanDefinitionReader型別的變數,用來進行XML組態檔的決議,這是符合單一職責原則的,也就是說,如果你要去決議組態檔的話,那么就不要在該類里面去自己實作了,而是直接呼叫決議器的方法進行決議就行了,
當然了,BeanDefinitionReader型別的物件創建應交由子類去實作,因為只有子類明確到底會創建BeanDefinitionReader哪個子實作類物件,
明確了AbstractApplicationContext類具有的以上特點之后,相信大家不難創建出該類,如下所示,
package com.meimeixia.framework.context.support;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionReader;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionRegistry;
import com.meimeixia.framework.context.ApplicationContext;
import java.util.HashMap;
import java.util.Map;
/**
* ApplicationContext介面的子實作類,用于立即加載
* @author liayun
* @create 2021-09-20 18:07
*/
public abstract class AbstractApplicationContext implements ApplicationContext {
// 宣告決議器變數
protected BeanDefinitionReader beanDefinitionReader; // 注意,這里我們只是宣告決議器變數而已,具體的物件應交由子類去創建,
// 而且,為了讓子類更好的去訪問,我們將會使用protected來修飾!
// 定義用于存盤bean物件的Map容器,也是為了讓子類更好的去訪問,我們同樣會使用protected來修飾!
protected Map<String, Object> singletonObjects = new HashMap<String, Object>(); // 注意,這里我們不考慮執行緒安全問題,直接創建一個HashMap物件就可以了
// 宣告組態檔類路徑的變數,也是為了讓子類更好的去訪問,我們同樣會使用protected來修飾!
protected String configLocation;
@Override
public void refresh() throws Exception {
// 加載BeanDefinition物件,加載BeanDefinition物件,我們只需要去呼叫決議器里面的方法即可
beanDefinitionReader.loadBeanDefinitions(configLocation);
// 初始化bean物件,也就是創建bean物件
finishBeanInitialization();
}
/**
* bean物件的初始化
*
* 不妨我們來思考一個問題,就是如果我們要進行bean物件的初始化,那么應該獲取哪個東東呢?
* 很顯然,就是BeanDefinition物件,因為BeanDefinition物件里面記錄了bean的相關信
* 息,只有拿到這些資訊,你才能去創建物件,所以我們要去獲取BeanDefinition物件,而
* BeanDefinition物件又是被注冊在注冊表里面的,所以首先我們還得先去獲取對應的注冊
* 表物件!
*/
private void finishBeanInitialization() throws Exception {
// 獲取注冊表物件
BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
// 獲取BeanDefinition物件
String[] beanNames = registry.getBeanDefinitionNames();
for (String beanName : beanNames) {
// 進行bean的初始化
getBean(beanName);
}
}
}
注意:該類finishBeanInitialization方法中呼叫getBean方法使用到了模板方法模式,
ClassPathXmlApplicationContext類
接下來,我們來定義IoC容器介面的具體子實作類,這個子實作類的名字不妨就取名為ClassPathXmlApplicationContext,通過該類的類名我們就能知道該類主要是加載類路徑下的XML格式的組態檔,并進行bean物件的創建的,
接下來,我們就來看一下該類具體要完成的功能主要有哪些?
- 在構造方法中,創建BeanDefinitionReader物件,因為我們只是在父類中宣告了一個BeanDefinitionReader型別的變數而已,而該BeanDefinitionReader型別的物件的創建應交由具體的子類來實作,
- 在構造方法中,呼叫refresh方法,用于進行組態檔加載、創建bean物件并存盤到容器中,
- 重寫父介面中的getBean方法,并實作依賴注入(DI)操作,
根據以上分析,我們創建出來的ClassPathXmlApplicationContext類就應該是下面這個樣子的,
package com.meimeixia.framework.context.support;
import com.meimeixia.framework.beans.BeanDefinition;
import com.meimeixia.framework.beans.MutablePropertyValues;
import com.meimeixia.framework.beans.PropertyValue;
import com.meimeixia.framework.beans.factory.support.BeanDefinitionRegistry;
import com.meimeixia.framework.beans.factory.xml.XmlBeanDefinitionReader;
import com.meimeixia.framework.utils.StringUtils;
import java.lang.reflect.Method;
/**
* IoC容器具體的子實作類:用于加載類路徑下的XML格式的組態檔
* @author liayun
* @create 2021-09-20 19:00
*/
public class ClassPathXmlApplicationContext extends AbstractApplicationContext {
/**
* 提供一個有參構造,該有參構造需要傳入組態檔的類路徑
* @param configLocation
*/
public ClassPathXmlApplicationContext(String configLocation) {
this.configLocation = configLocation;
// 構建決議器物件
this.beanDefinitionReader = new XmlBeanDefinitionReader(); // 注意,我們現在是已經規定了要加載的就是XML格式的組態檔,所以這里我們創建的是XmlBeanDefinitionReader物件
try {
this.refresh();
} catch (Exception e) {
// 這兒我們不做任何處理啊!
}
}
// 根據bean物件的名稱獲取bean物件
@Override
public Object getBean(String name) throws Exception {
// 判斷物件容器中是否包含指定名稱的bean物件,若包含,則直接回傳即可,若不包含,則還需要自行創建
Object obj = singletonObjects.get(name);
if (obj != null) { // 物件容器中確實包含指定名稱的bean物件,所以直接獲取到之后直接進行回傳
return obj;
}
// 物件容器中并沒有包含指定名稱的bean物件,所以我們還需要自行創建
// 獲取BeanDefinition物件,因為它里面記錄了bean的相關資訊
BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
BeanDefinition beanDefinition = registry.getBeanDefinition(name);
// 獲取bean資訊中的className,也就是全類名
String className = beanDefinition.getClassName();
// 通過反射創建物件
Class<?> clazz = Class.forName(className);
Object beanObj = clazz.newInstance(); // 我們的目的就是為了獲取這個bean物件
// 進行依賴注入操作
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
// 由于MutablePropertyValues類使用到了迭代器模式,所以我們就可以使用迭代器去遍歷了
for (PropertyValue propertyValue : propertyValues) { // 如果你能使用迭代器去遍歷的話,那么就意味著你也可以使用增強for回圈去遍歷
// 獲取name屬性的值
String propertyName = propertyValue.getName();
// 獲取value屬性的值
String value = propertyValue.getValue();
// 獲取ref屬性的值
String ref = propertyValue.getRef();
// 這里大家一定要注意,<property>標簽里面的value屬性和ref屬性只能存在一個!
if (ref != null && !"".equals(ref)) {
// 獲取依賴的bean物件
Object bean = getBean(ref); // 這里涉及到遞回操作
/*
* 拿到name屬性的值之后,我們就要去拼接對應的set方法名了,
*
* 為什么要去拼接對應的set方法呢?因為<property>標簽里面的name
* 屬性值是要和類中的set方法相對應的屬性名保持一致的!我相信使用過
* Spring框架的童鞋應該都知道這一點,
*/
String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
// 獲取所有的方法物件
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (methodName.equals(method.getName())) {
// 通過反射執行該set方法
method.invoke(beanObj, bean);
}
}
}
if (value != null && !"".equals(value)) {
// 拼接set方法名
String methodName = StringUtils.getSetterMethodByFieldName(propertyName);
// 獲取Method物件
Method method = clazz.getMethod(methodName, String.class);
method.invoke(beanObj, value);
}
}
/*
* 在回傳beanObj物件之前,將該物件存盤到Map容器中,
*
* 為什么要存放在Map容器里面呢?因為如果你不存放到Map容器里面的話,
* 那么下一次你從Map容器里面去獲取bean物件時,肯定是獲取不到的,獲
* 取不到的話,就意味著你還需要再去重新創建一遍,顯然這就很愚蠢了!
*/
singletonObjects.put(name, beanObj);
return beanObj;
}
@Override
public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
Object bean = getBean(name);
if (bean == null) {
return null;
}
return clazz.cast(bean); // 該cast方法就是用來進行強制型別轉換的
}
}
由于以上類的getBean方法中需要根據<property>標簽的name屬性值拼接set方法名,所以在這里我們就專門創建了一個工具類,即StringUtils,如下所示,
package com.meimeixia.framework.utils;
/**
* 工具類
* @author liayun
* @create 2021-09-20 19:31
*/
public class StringUtils {
private StringUtils() {
}
// 拼接set方法名,例如userDao ---> setUserDao
public static String getSetterMethodByFieldName(String fieldName) {
String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
return methodName;
}
}
測驗
,,,
總結
,,,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/317901.html
標籤:java
