接上一篇《手寫Spring---IOC容器(1)》繼續更新
一、DI分析
Q1:那些地方會有依賴?
1.構造引數依賴,上一篇中的幾種構建物件都不涉及傳入引數的問題
2.屬性依賴,構建出來的物件屬性會存在依賴
複製程式碼
Q2:依賴注入的本質是什麼?
賦值,給入構造引數值,還有對屬性的賦值
複製程式碼
Q3:引數值,屬性值,可能是什麼值?
直接賦予的值和bean依賴(使用另外的bean)
複製程式碼
Q4:直接賦予的值會有哪幾種型別?
1.基本資料型別,String
2.陣列,集合
3.Properties
4.map
5.物件
複製程式碼
結論:無論是引數值還是屬性值,bean工廠在進行DI依賴注入時的本質就是進行賦值
Q1:如何告訴bean工廠該給入什麼構造引數值?即如何來定義引數依賴?
Q2:如何來定義屬性依賴?
二、DI實現
① DI依賴注入-構造引數依賴定義分析:
以一個Girl類為例
public class Girl {
public Girl(String name,int age,char cup,Boy boyfriend){}
}
複製程式碼
Q1:我們要建立一個Girl類是如何建立的?
Boy Mike = new Boy();
Girl beauty = new Girl("WT",24,'A',Mike);
這種時候直接賦值,非常簡單
複製程式碼
Q2:這種時候的構造引數依賴是怎樣的?
1.第一個引數值"WT"
2.第二個引數值"24"
3.第三個引數值是'A'
4.第四個引數值是一個Boy的bean
複製程式碼
② 此時我們可以進行一個DI的構造引數依賴設計了
Q1:引數可以多個,使用什麼來進行儲存?
集合或者陣列
複製程式碼
Q2:引數有順序,如何處理順序?
按引數順序放入List即可
複製程式碼
Q3:引數值可以為直接的賦值,也可以為bean依賴,如何去表示?
只能使用Object型別:List<Object> constructorArgumentValues
複製程式碼
Q4:如果使用了Object來表示值,如何區分是否為bean依賴?
此時我們需要為bean依賴定義一個資料型別BeanReference,
bean工廠在構造bean例項時需要進行遍歷引數是否為
BeanReference型別,如果是,替換成依賴的bean例項
複製程式碼
Q5:如果直接賦值中存在陣列集合,它們中的某元素存在bean依賴,如何處理?
元素值還是使用BeanReference,
bean工廠在使用時應該遍歷此陣列/集合,存在即替換
複製程式碼
Q6:這個BeanReference該是怎樣的?
/**
* 用於依賴注入中描述bean依賴
*/
public class BeanReference {
private String beanName;
public BeanReference(String beanName){
super();
this.beanName = beanName;
}
/**
* 獲得引用的beanName
* @return
*/
public String getBeanName(){
return this.beanName;
}
}
複製程式碼
這個類僅僅是作為說明bean依賴的,需要提供一個beanName引數和一個getBeanName方法即可
③ DI實現-構造引數依賴定義(可對照上一篇參考)
1.在BeanDefinition中增加獲取構造引數值的介面,讓使用者在定義bean時能指定構造引數值
BeanDefinition---List<?> getConstructorArgumentValues();
複製程式碼
2.在實現了BeanDefinition介面的GeneralBeanDefinition中新增實現,因為使用了lombok外掛不再需要手寫getter和setter,所以我們新增表示構造引數值的欄位即可
private List<?> constructorArgumentValues;
複製程式碼
④ DI實現-BeanFactory中實現構造引數依賴注入1
(1)首先需要把bean定義中的構造引數引用轉化為真實的值,在DefaultBeanFactory中增加一個方法getConstructorArgumentValues來完成這件事
private Object[] getConstructorArgumentValues(BeanDefinition beanDefinition) throws Exception{
return this.getRealValues(beanDefinition.getConstructorArgumentValues());
}
複製程式碼
此時我們把這個獲取值的過程提取出來單獨為一個方法
private Object[] getRealValues(List<?> defs) throws Exception{
if (CollectionUtils.isEmpty(defs)){return null;}
Object[] values = new Object[defs.size()];
int i = 0;
//values陣列的元素
Object value = null;
for (Object realValue : defs){
if (realValue == null){
value = null;
}else if (realValue instanceof BeanReference){
value = this.doGetBean(((BeanReference) realValue).getBeanName());
···
}else {value = realValue;}
values[i++] = value;
}
return values;
}
複製程式碼
tips:這裡的CollectionUtils使用的是apache的,else if那裡省略了一部分的方法,具體做法就是建立一個工具類,然後去處理不同資料型別的資料中的bean引用,分支有大概4個,陣列Object[],集合Collection,properties,和Map
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
複製程式碼
⑤ DI實現-BeanFactory中實現構造引數依賴注入2
Q1:有引數了,如何斷定是哪個構造方法,哪個工廠方法?
1.方法是可以過載的(引數數量和引數型別的判斷)
2.形參定義時可能是介面或者父類,實參是具體的子實現
3.反射提供的獲取的構造方法,方法的API(以下的Class指的都是**引數**的型別)
(getConstrutors()和getConstructor(Class<?>...))
(getMethods()和getMethod(String,Class<?>...))
複製程式碼
判斷邏輯:
1.先根據引數的型別進行精確匹配查詢,如果沒有找到,則進行第二步查詢
2.獲取所有的構造方法遍歷,通過引數數量過濾,再比對形參型別和實參型別
(為什麼第一步無法找到,也就是上面提及到的 “形參定義時可能是介面或者父類,實參是具體的子實現” 這個原因)
複製程式碼
Q2:當我們判斷出構造方法或者工廠方法後,對於原型bean(prototype),下次獲取Bean是否可以省略判斷過程(設定多例時每次獲取需要重新構造)
對於prototype,我們可以快取這個構造方法或工廠方法
我們在BeanDefinition中增加快取的方法
//以下4個方法僅供BeanFactory使用
public Constructor<?> getConstructor();
public void setConstructor(Constructor<?> constructor);
public Method getFactoryMethod();
public void setFactoryMethod(Method method);
複製程式碼
由於在BeanDefinition介面中增加了上面4個方法,所以在GeneralBeanDefinition新增實現
private Constructor<?> constructor;
private Method factoryMethod;
@Override
public Constructor<?> getConstructor() {
return this.constructor;
}
@Override
public void setConstructor(Constructor<?> constructor) {
this.constructor = constructor;
}
@Override
public Method getFactoryMethod() {
return this.factoryMethod;
}
@Override
public void setFactoryMethod(Method method) {
this.factoryMethod = factoryMethod;
}
複製程式碼
接下來就可以去實現構造方法和工廠方法的程式碼
⑥ DI實現-BeanFactory中實現構造引數依賴注入3
(1) 在DefaultBeanFactory中增加查詢構造方法的方法
private Constructor determineConstructor(BeanDefinition beanDefinition,Object[] args) throws Exception{
Constructor constructor = null;
//當沒有任何一個引數時直接獲取無參構造方法
if (args == null){
return beanDefinition.getBeanClass().getConstructor(null);
}
//對於原型bean,第二次開始獲取Bean例項時,可直接獲取第一次快取的構造方法
constructor = beanDefinition.getConstructor();
if (constructor != null){
return constructor;
}
//根據引數型別獲取精確匹配的構造方法
Class<?>[] paramTypes = new Class[args.length];
int j = 0;
for (Object paramType : args){
paramTypes[j++] = paramType.getClass();
}
try {
constructor = beanDefinition.getConstructor();
}catch (Exception e){
//此異常不需要進行處理
}
if (constructor == null){
//把所有的構造器全部遍歷出來一一比對
Outer: for (Constructor<?> allConstructor : beanDefinition.getBeanClass().getConstructors()){
Class<?>[] pTypes = allConstructor.getParameterTypes();
//此構造方法的引數長度等於提供引數長度
if (pTypes.length == args.length){
for (int i = 0;i<pTypes.length;i++){
//如果第一個引數的型別就已經不匹配了,就直接不再繼續比對了,直接跳轉到外迴圈
if (!pTypes[i].isAssignableFrom(args[i].getClass())){
continue Outer;
}
}
//如果以上皆匹配的話,就直接獲取到這個構造器,然後直接讓迴圈終止
constructor = allConstructor;
break Outer;
}
}
}
if (constructor != null){
if (beanDefinition.isPrototype()){
//對原型bean構造器進行快取方便下次查詢
beanDefinition.setConstructor(constructor);
}
return constructor;
}else {
throw new Exception("不存在對應的構造方法!"+beanDefinition);
}
}
複製程式碼
修改構造方法建立物件的實現
前一篇的構造方法
private Object createInstanceByConstructor(BeanDefinition beanDefinition) throws IllegalAccessException, InstantiationException {
try{
return beanDefinition.getBeanClass().newInstance();
} catch (SecurityException e){
logger.error("建立bean的例項異常,beanDefinition"+beanDefinition,e);
throw e;
}
}
複製程式碼
修改後版本
private Object createInstanceByConstructor(BeanDefinition beanDefinition) throws Exception {
try {
//獲取真正的引數值
Object[] args = this.getConstructorArgumentValues(beanDefinition);
if (args == null) {
return beanDefinition.getBeanClass().newInstance();
} else {
// 決定構造方法
return this.determineConstructor(beanDefinition, args).newInstance(args);
}
} catch (SecurityException e1) {
logger.error("建立bean的例項異常,beanDefinition:" + beanDefinition, e1);
throw e1;
}
}
複製程式碼
自然工廠方法也要進行同樣的編寫查詢方法和決定方法的邏輯實現
工廠查詢方法
private Method determineFactoryMethod(BeanDefinition bd, Object[] args, Class<?> type) throws Exception {
if (type == null) {
type = bd.getBeanClass();
}
String methodName = bd.getFactoryMethodName();
if (args == null) {
return type.getMethod(methodName, null);
}
Method m = null;
// 對於原型bean,從第二次開始獲取bean例項時,可直接獲得第一次快取的構造方法。
m = bd.getFactoryMethod();
if (m != null) {
return m;
}
// 根據引數型別獲取精確匹配的方法
Class[] paramTypes = new Class[args.length];
int j = 0;
for (Object p : args) {
paramTypes[j++] = p.getClass();
}
try {
m = type.getMethod(methodName, paramTypes);
} catch (Exception e) {
// 這個異常不需要處理
}
if (m == null) {
// 沒有精確引數型別匹配的,則遍歷匹配所有的方法
// 判斷邏輯:先判斷引數數量,再依次比對形參型別與實參型別
outer: for (Method m0 : type.getMethods()) {
if (!m0.getName().equals(methodName)) {
continue;
}
Class<?>[] paramterTypes = m.getParameterTypes();
if (paramterTypes.length == args.length) {
for (int i = 0; i < paramterTypes.length; i++) {
if (!paramterTypes[i].isAssignableFrom(args[i].getClass())) {
continue outer;
}
}
m = m0;
break outer;
}
}
}
if (m != null) {
// 對於原型bean,可以快取找到的方法,方便下次構造例項物件。在BeanDefinfition中獲取設定所用方法的方法。
if (bd.isPrototype()) {
bd.setFactoryMethod(m);
}
return m;
} else {
throw new Exception("不存在對應的構造方法!" + bd);
}
}
複製程式碼
前一篇的靜態工廠方法:
private Object createInstanceByStaticFactoryMethod(BeanDefinition beanDefinition) throws Exception{
Class<?> type = beanDefinition.getBeanClass();
Method method = type.getMethod(beanDefinition.getFactoryMethodName(),null);
return method.invoke(type,null);
}
複製程式碼
修改後的靜態工廠方法:
private Object createInstanceByStaticFactoryMethod(BeanDefinition beanDefinition) throws Exception{
Class<?> type = beanDefinition.getBeanClass();
Object[] realArgs = this.getRealValues(beanDefinition.getConstructorArgumentValues());
Method m = this.determineFactoryMethod(beanDefinition, realArgs, null);
return m.invoke(type, realArgs);
}
複製程式碼
前一篇的工廠bean方法:
private Object createInstanceByFactoryBean(BeanDefinition beanDefinition) throws Exception{
Object factoryBean = this.doGetBean(beanDefinition.getFactoryBeanName());
Method method = factoryBean.getClass().getMethod(beanDefinition.getFactoryMethodName(),null);
return method.invoke(factoryBean,null);
}
複製程式碼
修改後的工廠bean方法:
private Object createInstanceByFactoryBean(BeanDefinition beanDefinition) throws Exception{
Object factoryBean = this.doGetBean(beanDefinition.getFactoryBeanName());
Object[] realArgs = this.getRealValues(beanDefinition.getConstructorArgumentValues());
Method m = this.determineFactoryMethod(beanDefinition, realArgs, factoryBean.getClass());
return m.invoke(factoryBean, realArgs);
}
複製程式碼
修改完構造,靜態工廠,工廠bean的方法後進行一下測試看能否正常工作了
⑦ DI實現-BeanFactory中實現構造引數依賴注入4
Q:迴圈依賴如何進行處理?
1.構造物件時可以迴圈依賴嗎?
A:不可以在構造例項物件時的迴圈依賴
2.如何發現迴圈依賴?
A:加入一個正在構造的bean的記錄,每個bean開始構造時加入到記錄中,構造完成就移走,
如果有依賴,先看依賴的bean是否在構造中,如果是,就構成了迴圈,丟擲異常
複製程式碼
程式碼實現:
DefaultBeanFactory.java---新增一個欄位:
private ThreadLocal<Set<String>> buildingBeans = new ThreadLocal<>();
放進ThreadLocal的一個集合,是為了執行緒安全
複製程式碼
doGetBean方法內補充:
// 記錄正在建立的Bean
Set<String> ingBeans = this.buildingBeans.get();
if (ingBeans == null) {
ingBeans = new HashSet<>();
this.buildingBeans.set(ingBeans);
}
// 檢測迴圈依賴
if (ingBeans.contains(beanName)) {
throw new Exception(beanName + " 迴圈依賴!" + ingBeans);
}
// 記錄正在建立的Bean
ingBeans.add(beanName);
複製程式碼
還有建立完成後···
// 建立好例項後,移除建立中記錄
ingBeans.remove(beanName);
複製程式碼
DI實現-屬性依賴設計&實現1
Q1:屬性依賴是什麼?
某個屬性依賴某個值
複製程式碼
Q2:該如何來描述一個屬性依賴?
屬性名,值,定義一個類,表示這倆個值
複製程式碼
Q3:會有多個依賴該如何存放?
List
複製程式碼
Q4:屬性值的情況和構造引數值一樣嗎?
一樣
複製程式碼
定義屬性依賴描述實體類PropertyValue
import lombok.Data;
@Data
public class PropertyValue {
private String name;
private Object value;
}
複製程式碼
DI實現-屬性依賴設計&實現2
在BeanDefinition中新增屬性依賴定義的介面
List<PropertyValue> getPropertyValues();
複製程式碼
在GeneralBeanDefinition中新增實現
private List<PropertyValue> propertyValues;
public List<PropertyValue> getPropertyValues() {
return propertyValues;
}
public void setPropertyValues(List<PropertyValue> propertyValues) {
this.propertyValues = propertyValues;
}
複製程式碼
在DefaultBeanFactory的doGetBean方法中增加對屬性依賴的呼叫
// 建立好例項後,移除建立中記錄
ingBeans.remove(beanName);
// 給入屬性依賴
this.setPropertyDIValues(bd, instance);
// 執行初始化方法
this.doInit(bd, instance);
複製程式碼
setPropertyDIValues方法的實現(和getRealValues方法類似)
private void setPropertyDIValues(BeanDefinition bd, Object instance) throws Exception {
if (CollectionUtils.isEmpty(bd.getPropertyValues())) {
return;
}
for (PropertyValue pv : bd.getPropertyValues()) {
if (StringUtils.isBlank(pv.getName())) {
continue;
}
Class<?> clazz = instance.getClass();
Field p = clazz.getDeclaredField(pv.getName());
p.setAccessible(true);
Object rv = pv.getValue();
Object v = null;
if (rv == null) {
v = null;
} else if (rv instanceof BeanReference) {
v = this.doGetBean(((BeanReference) rv).getBeanName());
} else if (rv instanceof Object[]) {
// TODO 處理集合中的bean引用
} else if (rv instanceof Collection) {
// TODO 處理集合中的bean引用
} else if (rv instanceof Properties) {
// TODO 處理properties中的bean引用
} else if (rv instanceof Map) {
// TODO 處理Map中的bean引用
} else {
v = rv;
}
p.set(instance, v);
}
}
複製程式碼
以下是測試篇(可不看)
A,ABeanFactory,C,CC,D~F的bean設計(反正就是一堆測試用的類把,分別滿足測試另外bean的引用,父類子類,迴圈引用和屬性依賴)
ABean
package MySpring.DITestUtils;
public class ABean {
private String name;
private CBean cb;
public ABean(String name, CBean cb) {
super();
this.name = name;
this.cb = cb;
System.out.println("呼叫了含有CBean引數的構造方法");
}
public ABean(String name, CCBean cb) {
super();
this.name = name;
this.cb = cb;
System.out.println("呼叫了含有CCBean引數的構造方法");
}
public ABean(CBean cb) {
super();
this.cb = cb;
}
public void doSomthing() {
System.out.println(System.currentTimeMillis() + " " + this.name + " cb.name=" + this.cb.getName());
}
public void init() {
System.out.println("ABean.init() 執行了");
}
public void destroy() {
System.out.println("ABean.destroy() 執行了");
}
}
複製程式碼
ABeanFactory
public class ABeanFactory {
public static ABean getABean(String name, CBean cb) {
return new ABean(name, cb);
}
public ABean getABean2(String name, CBean cb) {
return new ABean(name, cb);
}
}
複製程式碼
CBean
public class CBean {
private String name;
public CBean(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
複製程式碼
}
CCBean
public class CCBean extends CBean {
public CCBean(String name) {
super(name);
}
複製程式碼
}
DBean
public class DBean {
private EBean ebean;
public DBean(EBean ebean) {
super();
this.ebean = ebean;
}
複製程式碼
}
EBean
public class EBean {
private DBean dbean;
public EBean(DBean dbean) {
super();
this.dbean = dbean;
}
複製程式碼
}
FBean(為了省事用了@Data)
@Data
public class FBean {
private String name;
private int age;
private ABean aBean;
複製程式碼
}
測試類1---DITest
public class DITest {
static PreBuildBeanFactory bf = new PreBuildBeanFactory();
@Test
public void testConstructorDI() throws Exception {
GeneralBeanDefinition bd = new GeneralBeanDefinition();
bd.setBeanClass(ABean.class);
List<Object> args = new ArrayList<>();
args.add("abean");
args.add(new BeanReference("cbean"));
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("abean", bd);
bd = new GeneralBeanDefinition();
bd.setBeanClass(CBean.class);
args = new ArrayList<>();
args.add("cbean");
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("cbean", bd);
bf.preInstantiateSingletons();
ABean abean = (ABean) bf.getBean("abean");
abean.doSomthing();
}
@Test
public void testStaticFactoryMethodDI() throws Exception {
GeneralBeanDefinition bd = new GeneralBeanDefinition();
bd.setBeanClass(ABeanFactory.class);
bd.setFactoryMethodName("getABean");
List<Object> args = new ArrayList<>();
args.add("abean02");
args.add(new BeanReference("cbean02"));
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("abean02", bd);
bd = new GeneralBeanDefinition();
bd.setBeanClass(CBean.class);
args = new ArrayList<>();
args.add("cbean02");
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("cbean02", bd);
bf.preInstantiateSingletons();
ABean abean = (ABean) bf.getBean("abean02");
abean.doSomthing();
}
@Test
public void testFactoryMethodDI() throws Exception {
GeneralBeanDefinition bd = new GeneralBeanDefinition();
bd.setFactoryBeanName("abeanFactory");
bd.setFactoryMethodName("getABean2");
List<Object> args = new ArrayList<>();
args.add("abean03");
args.add(new BeanReference("cbean02"));
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("abean03", bd);
bd = new GeneralBeanDefinition();
bd.setBeanClass(ABeanFactory.class);
bf.registerBeanDefinition("abeanFactory", bd);
bf.preInstantiateSingletons();
ABean abean = (ABean) bf.getBean("abean03");
abean.doSomthing();
}
@Test
public void testChildTypeDI() throws Exception {
GeneralBeanDefinition bd = new GeneralBeanDefinition();
bd.setBeanClass(ABean.class);
List<Object> args = new ArrayList<>();
args.add("abean04");
args.add(new BeanReference("ccbean01"));
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("abean04", bd);
bd = new GeneralBeanDefinition();
bd.setBeanClass(CCBean.class);
args = new ArrayList<>();
args.add("Ccbean01");
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("ccbean01", bd);
bf.preInstantiateSingletons();
ABean abean = (ABean) bf.getBean("abean04");
abean.doSomthing();
}
}
複製程式碼
DITest的測試結果
呼叫了含有CCBean引數的構造方法
1555720164146 abean04 cb.name=Ccbean01
呼叫了含有CBean引數的構造方法
1555720164162 abean cb.name=cbean
呼叫了含有CBean引數的構造方法
1555720164162 abean02 cb.name=cbean02
呼叫了含有CBean引數的構造方法
1555720164162 abean03 cb.name=cbean02
複製程式碼
CirculationDITest的測試結果
java.lang.Exception: dbean 迴圈依賴![ebean, dbean]
複製程式碼
PropertyDITest的測試結果
呼叫了含有CBean引數的構造方法
FFBean01 18
1555720275503 abean01 cb.name=cbean01
複製程式碼