1. 手寫Spring框架
@
- 1. 手寫Spring框架
- 每博一文案
- 2. 反射機制的回顧
- 3. 開始手寫 Spring 框架
- 3.1 第一步:使用 IDE 建立模組myspring
- 3.2 第二步:準備好我們要管理的Bean
- 3.3 第三步:準備myspring.xml配置檔案
- 3.4 第四步:編寫ApplicationContext介面
- 3.5 第五步:編寫ClassPathXmlApplicationContext
- 3.6 第六步:確定採用Map集合儲存Bean
- 3.7 第七步:解析配置檔案,並例項化所有Bean
- 3.8 第八步:測試能否獲取到Bean
- 3.9 第九步:給Bean的屬性賦值
- 3.10 第十步:測試是否能夠正常賦值成功
- 3.11 第十一步:打包釋出
- 3.12 第十二步: 站在程式設計師角度使用我們自己手寫的 spring 的 myspring框架
- 4. 總結:
- 5. 最後:
每博一文案
特別喜歡一種人
他們的知識儲備和資訊密度,都遠遠高於我
但還是願意認真聽我說,那些沒有營養的廢話
我始終覺得,溫柔浪漫跟博學,是人身上最難得的特性
懂得向下相容的人,走到哪裡都是寶藏
太喜歡哪些優秀,卻不帶優越感的人相處了
2. 反射機制的回顧
我們知道框架一般是由 設計模式+註解+反射 來實現,並進行運用開發的。
所以我們要手寫 Spring 框架,就需要先來回顧,回顧Java當中的反射機制,這裡是簡單的回顧反射 。關於反射機制更加詳細的內容,大家可以移步至✏️✏️✏️ Java “框架 = 註解 + 反射 + 設計模式” 之 反射詳解-CSDN部落格 。
我們知道,想要呼叫一個方法,就需要明確知道該方法的四個要素:
- 呼叫的是哪個物件的
- 哪個方法
- 該方法傳什麼引數
- 有無返回值,有返回值的話,又該返回什麼值
為了簡單的回顧我們的反射機制,下面我們進行一個簡單的任務。完成下面的需求
首先你知道以下這幾條資訊
假設你現在以下資訊: 1.有這樣一個類,類名叫做:com.rainbowsea.reflect.User 2.這個類符合javabean規範,屬性私有化,對外提供公開的setter和getter方法。 3.你還知道這個類當中有一個屬性,屬性的名字叫做 age 4.並且你還知道age屬性的型別是 int 型別 請使用反射機制呼叫set()方法,給 User 物件的age 屬性賦值(age 屬性賦值為 20 )
package com.rainbowsea.reflect;
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
下面透過反射機制,獲取到為 age 屬性值進行賦值操作。
package com.rainbowsea.test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test2 {
/*
需求:
假設你現在以下資訊:
1.有這樣一個類,類名叫做:com.powernode.reflect.User
2.這個類符合javabean規範,屬性私有化,對外提供公開的setter和getter方法。
3.你還知道這個類當中有一個屬性,屬性的名字叫做 age
4.並且你還知道age屬性的型別是 int 型別
請使用反射機制呼叫set()方法,給 User 物件的age 屬性賦值
*/
public static void main(String[] args) {
String className = "com.rainbowsea.reflect.User";
String propertyName = "age";
try {
// 透過 反射機制呼叫setAge(int) 方法
// 獲取到對應的類
Class<?> clazz = Class.forName(className);
// 獲取對應的方法
// 方法名: 這裡我們知道set 方法格式為: setAge() set+屬性名的首字母大小+後面屬性名的小寫字母
String setMethodName = "set" + propertyName.toUpperCase().charAt(0)+ propertyName.substring(1);
// 獲取對應的方法名:
// 我們知道的屬性名就可以,根據屬性名獲取屬性型別
// 下面這裡獲取到的是一個完整的:private int com.rainbowsea.reflect.User.age 型別名
Field field = clazz.getDeclaredField(propertyName);
// field.getType() 獲取到屬性的型別,是個簡單的型別 int
Method declaredMethod = clazz.getDeclaredMethod(setMethodName, field.getType());
//System.out.println(field.getType());
// 準備物件
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();
Object obj = declaredConstructor.newInstance();
// 呼叫set()方法賦值, 沒有返回值(呼叫 obj 的物件中的,declaredMethod()方法,引數是 30,沒有返回值)
declaredMethod.invoke(obj,30);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
成功了,哪我們反射機制就算簡單回顧完了,該上正菜了——》手寫 Spring 框架
3. 開始手寫 Spring 框架
Spring IoC容器的實現原理:工廠模式 + 解析XML + 反射機制。
我們給自己的框架起名為:myspring(我的春天)
3.1 第一步:使用 IDE 建立模組myspring
這裡我們採用 IDE 當中的Maven 建立一個 名為 myspring的 Module。
注意:這裡是 org.myspringframework
我們現在的角色是一個spring開發者人員。所以我們的包名的命名格式還是要規定一下的。
打包方式採用 jar,並且還需要引入 dom4j 和 jaxen的依賴,因為要使用它解析XML檔案,還有junit依賴。(進行測試)。有上面三個就可以了,這裡的話,我再引入一個 log4j2 的依賴,用於日誌資訊的顯示記錄。因為我們這裡是,自己手寫 Spring 框架,所以就不要再引入 Spring的 jar 包內容了。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rainbowsea</groupId>
<artifactId>myspring</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 打包方式為 jar -->
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- dom4j是一個能夠解析xml檔案的java元件-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<!-- 單元測試-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- log4j2的依賴-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
</dependencies>
</project>
3.2 第二步:準備好我們要管理的Bean
準備好我們要管理的Bean(這些Bean在將來開發完框架之後是要刪除的)
注意包名,不要用 org.myspringframework
包了,因為這些Bean不是框架內建的,是將來使用我們寫好 Spring 框架方便我們後續測試,基於便於我們手寫 Spring 框架進行思考,所以這裡我就定義為了。com.rainbowsea.myspring.bean
大家可以自行定義
一共定義了 三個 Bean :User,UserService,UserDao
package com.rainbowsea.myspring.bean;
public class User {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.rainbowsea.myspring.bean;
public class UserDao {
public void insert() {
System.out.println("資料庫插入資料");
}
}
package com.rainbowsea.myspring.bean;
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save() {
userDao.insert();
}
}
3.3 第三步:準備myspring.xml配置檔案
將來在框架開發完畢之後,這個檔案也是要刪除的。因為這個配置檔案的提供者應該是使用這個框架的程式設計師。這裡我是同樣也是為了,用於驗證測試,我們手寫額Spring框架的。檔名隨意,我們這裡叫做:myspring.xml。檔案放在類路徑當中即可,我們這裡把檔案放到類的根路徑下。使用value給簡單屬性賦值。使用ref給非簡單屬性賦值。
因為上面我配置了引入一個 log4j2 的依賴,用於日誌資訊的顯示記錄。所以這裡,我們還需配置一個關於 log4j2 的配置檔案。
3.4 第四步:編寫ApplicationContext介面
ApplicationContext 介面中提供一個getBean()方法,透過該方法可以獲取Bean物件。
注意包名:這個介面就是myspring框架中的一員了。
3.5 第五步:編寫ClassPathXmlApplicationContext
ClassPathXmlApplicationContext 是 ApplicationContext 介面的實現類。該類從類路徑當中載入myspring.xml 配置檔案。
package org.myspringframework.core;
public class ClassPathXmlApplicationContext implements ApplicationContext {
@Override
public Object getBean(String beanName) {
return singletonObjects.get(beanName);
}
}
3.6 第六步:確定採用Map集合儲存Bean
確定採用Map集合儲存Bean例項。Map集合的key儲存 beanId,value儲存Bean例項。Map<String,Object>
在ClassPathXmlApplicationContext類中新增 Map<String,Object>屬性。
並且在ClassPathXmlApplicationContext類中新增構造方法,該構造方法的引數接收myspring.xml檔案。
同時實現getBean方法。
package org.myspringframework.core;
import java.util.HashMap;
import java.util.Map;
public class ClassPathXmlApplicationContext implements ApplicationContext {
// 解析myspring.xml檔案,然後例項化Bean,將Bean存放到singletonObjects集合當中
private Map<String, Object> singletonObjects = new HashMap<>();
}
@Override
public Object getBean(String beanName) {
return singletonObjects.get(beanName);
}
}
3.7 第七步:解析配置檔案,並例項化所有Bean
在 ClassPathXmlApplicationContext 的構造方法中解析配置檔案,獲取所有bean的類名,透過反射機制呼叫無引數構造方法建立Bean。並且將Bean物件存放到Map集合中。
補充:
首先我們需要,獲取到一個名為 SAXReader(這是dom4j 解析xml 檔案的核心物件) 的物件以及Document ,用來讀取我們當中的
myspring.xml'
所含的 bean 的配置資訊。這兩個物件可以讀取到xml
配置檔案的資訊。
同時需要:注意的是:在 java 當中一個 ”//
“ 才表示為一個/
。
注意一個向下轉型:向下轉型的目的是為了使用Element介面更加豐富的方法。該 Element 可以獲取到 <bean> 標籤當中的 id 的屬性值,和 <bean>標籤當中的 class 屬性的值
package org.myspringframework.core;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ClassPathXmlApplicationContext implements ApplicationContext {
// 解析myspring.xml檔案,然後例項化Bean,將Bean存放到singletonObjects集合當中
private Map<String, Object> singletonObjects = new HashMap<>();
/**
* 解析myspring的配置檔案,然後初始化所有的Bean物件
*
* @param configLocation spring配置檔案的路徑,注意:使用的是ClassPathXmlApplicationContext ,所以
* 配置檔案應當放到類路徑下
*/
public ClassPathXmlApplicationContext(String configLocation) {
// 解析myspring.xml檔案,然後例項化Bean,將Bean存放到singletonObjects集合當中
// 這是dom4j 解析xml 檔案的核心物件
SAXReader saxReader = new SAXReader();
Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class);
// 獲取一個輸入流,指向配置檔案
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
try {
// 讀檔案
Document document = saxReader.read(in);
// 獲取到所有的 <bean>標籤
// 注意要兩個//,才能表示一個 /
List<Node> nodes = document.selectNodes("//bean");
// 遍歷 bean標籤
nodes.forEach(node -> {
//System.out.println(node);
// 向下轉型的目的是為了使用Element介面更加豐富的方法
Element beanElt = (Element) node;
// 獲取 <bean>標籤當中的id的屬性
String id = beanElt.attributeValue("id");
// 獲取 <bean>標籤當中的 class 屬性
String className = beanElt.attributeValue("class");
//logger.info("beanName" + id);
//logger.info("beanClassName " + className);
try {
// 透過反射機制建立物件,將其放到Map集合中,提前曝光
// 獲取class物件
Class<?> aClass = Class.forName(className);
// 獲取無參構造方法例項化Bean
Constructor<?> declaredCon = aClass.getDeclaredConstructor();
// 呼叫無參構造方法例項化Bean
Object bean = declaredCon.newInstance();
// 將Bean 曝光,加入 Map集合
singletonObjects.put(id, bean);
// 記錄日誌
logger.info(singletonObjects.toString());
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object getBean(String beanName) {
return singletonObjects.get(beanName);
}
}
3.8 第八步:測試能否獲取到Bean
package com.rainbowsea.myspring.test;
import com.rainbowsea.myspring.bean.UserService;
import org.junit.Test;
import org.myspringframework.core.ApplicationContext;
import org.myspringframework.core.ClassPathXmlApplicationContext;
public class MySpringTest {
@Test
public void testMySpring() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("myspring.xml");
Object user = applicationContext.getBean("user");
System.out.println(user);
}
}
我們的 myspring.xml 配置檔案當中,有三個 bean 物件的配置資訊,所以下面應當顯示三個 Bean的例項物件。如下顯示結果,我們可以看出,是成功獲取到了 myspring.xml 配置檔案當中所有的 Bean 物件了。
透過測試Bean已經例項化成功了,屬性的值是null,這是我們能夠想到的,畢竟我們呼叫的是無引數構造方法,所以屬性都是預設值。
下一步就是我們應該如何給Bean的屬性賦值了。
3.9 第九步:給Bean的屬性賦值
透過反射機制呼叫set方法,給Bean的屬性賦值。
繼續在ClassPathXmlApplicationContext構造方法中編寫程式碼。
補充說明:
我們需要,再次重新把所有的 Bean 標籤遍歷一次,這一次主要是給物件屬性賦值的
獲取到 獲取該標籤下的所有的屬性為 <property>標籤,因為 在 <property> 標籤當中存放著,我們對屬性賦值要的確切的值。
賦值,這裡我們是透過 set 方法進行賦值的,這個簡單,我們可以透過反射(物件,方法名,方法引數型別)獲取到方法的物件,又可以透過屬性名,獲取到屬性的型別。
這裡最難的部分就是,在我們的 在 <property> 標籤當中存放著,我們對屬性賦值要的確切的值。但是這個值有兩種:簡單型別 value 的值,複雜型別 ref 的值。複雜型別就是引用型別,這個我們以及在曝光的時候,就儲存到Map當中去了,我們只需要透過對應的beanName 從 Map當前取出來,再透過反射機制呼叫set方法進行一個賦值。
簡單型別的賦值操作:
簡單型別的複雜點,就是一個轉換,我們再 myspring.xml 配置檔案當中的,屬性值都是以字串的形式存在的,而我們的實際屬性的型別,可能是 int,char,double 等等型別的,String 字串型別不可以直接賦值到其他 int,char 型別當中去,所以我們就需要對我們所賦值的屬性進行一個判斷,如果該型別是 int 型別,我們就需要將 String 字串型別轉換為我們所需要的 int 型別。
這裡我們說明一下:
我們myspring框架說明以下,我們只支援這些型別為簡單型別 byte short int long float double boolean char Byte Short Intger Long Float Double Boolean Character String */
完整程式碼:
package org.myspringframework.core;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ClassPathXmlApplicationContext implements ApplicationContext {
// 解析myspring.xml檔案,然後例項化Bean,將Bean存放到singletonObjects集合當中
private Map<String, Object> singletonObjects = new HashMap<>();
/**
* 解析myspring的配置檔案,然後初始化所有的Bean物件
*
* @param configLocation spring配置檔案的路徑,注意:使用的是ClassPathXmlApplicationContext ,所以
* 配置檔案應當放到類路徑下
*/
public ClassPathXmlApplicationContext(String configLocation) {
// 解析myspring.xml檔案,然後例項化Bean,將Bean存放到singletonObjects集合當中
// 這是dom4j 解析xml 檔案的核心物件
SAXReader saxReader = new SAXReader();
Logger logger = LoggerFactory.getLogger(ClassPathXmlApplicationContext.class);
// 獲取一個輸入流,指向配置檔案
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(configLocation);
try {
// 讀檔案
Document document = saxReader.read(in);
// 獲取到所有的 <bean>標籤
// 注意要兩個//,才能表示一個 /
List<Node> nodes = document.selectNodes("//bean");
// 遍歷 bean標籤
nodes.forEach(node -> {
//System.out.println(node);
// 向下轉型的目的是為了使用Element介面更加豐富的方法
Element beanElt = (Element) node;
// 獲取 <bean>標籤當中的id的屬性
String id = beanElt.attributeValue("id");
// 獲取 <bean>標籤當中的 class 屬性
String className = beanElt.attributeValue("class");
//logger.info("beanName" + id);
//logger.info("beanClassName " + className);
try {
// 透過反射機制建立物件,將其放到Map集合中,提前曝光
// 獲取class物件
Class<?> aClass = Class.forName(className);
// 獲取無參構造方法例項化Bean
Constructor<?> declaredCon = aClass.getDeclaredConstructor();
// 呼叫無參構造方法例項化Bean
Object bean = declaredCon.newInstance();
// 將Bean 曝光,加入 Map集合
singletonObjects.put(id, bean);
// 記錄日誌
//logger.info(singletonObjects.toString());
//System.out.println(singletonObjects.toString());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
});
// 再次重新把所有的 Bean 標籤遍歷一次,這一次主要是給物件屬性賦值的
nodes.forEach(node -> {
Element beanElt = (Element) node;
// 獲取 <bean>標籤當中的 id 屬性
String id = beanElt.attributeValue("id");
// 獲取<bean>標籤當中的 class 屬性的值
String className = beanElt.attributeValue("class");
// 獲取Class 物件透過全限定類名+反射機制
try {
Class<?> aClass = Class.forName(className);
// 獲取該<bean>標籤下的所有的屬性為 <property>標籤
List<Element> propertys = beanElt.elements("property");
// 遍歷<property> 的所有的屬性標籤
propertys.forEach(property -> {
// 獲取 <property> 標籤下的name 屬性名下的值
String propertyName = property.attributeValue("name");
//logger.info(" <property> 標籤下的name 屬性名 " + propertyName);
// 獲取 <property> 標籤下的name 屬性名下的值
// 獲取 set 方法名 set+屬性名第一個單詞大寫
String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
try {
//獲取 set 方法
// 獲取屬性型別,透過屬性名,可以獲取到屬性型別
Field declaredField = aClass.getDeclaredField(propertyName);
// 獲取set方法
Method setMethod = aClass.getDeclaredMethod(setMethodName, declaredField.getType());
// 呼叫set方法(set方法沒有返回值)
// 獲取 <property> 標籤當中的 value 或者是 ref 的具體的值
String value = property.attributeValue("value");
String ref = property.attributeValue("ref");
Object actualValue = null; // 真值
if (value != null) {
// 說明在<property> 標籤當中 這個值是簡單型別
/*
我們myspring框架說明以下,我們只支援這些型別為簡單型別
byte short int long float double boolean char
Byte Short Intger Long Float Double Boolean Character
String
*/
// 獲取屬性型別名
String propertyTypeSimpleName = declaredField.getType().getSimpleName();
switch (propertyTypeSimpleName) {
case "byte":
actualValue = Byte.parseByte(value);
break;
case "short":
actualValue = Short.parseShort(value);
break;
case "int":
actualValue = Integer.parseInt(value);
break;
case "long":
actualValue = Long.parseLong(value);
break;
case "float":
actualValue = Float.parseFloat(value);
break;
case "double":
actualValue = Double.parseDouble(value);
break;
case "boolean":
actualValue = Boolean.parseBoolean(value);
break;
case "char":
actualValue = value.charAt(0);
break;
case "Byte":
actualValue = Byte.valueOf(value);
break;
case "Short":
actualValue = Short.valueOf(value);
break;
case "Integer":
actualValue = Integer.valueOf(value);
break;
case "Long":
actualValue = Long.valueOf(value);
break;
case "Float":
actualValue = Float.valueOf(value);
break;
case "Double":
actualValue = Double.valueOf(value);
break;
case "Boolean":
actualValue = Boolean.valueOf(value);
break;
case "Character":
// char 僅僅只是一個字元,所以要獲取第一個字元
actualValue = value.charAt(0);
break;
case "String":
actualValue = value;
break;
}
setMethod.invoke(singletonObjects.get(id), actualValue);
}
if (ref != null) {
// 說明這個值是非簡單型別
// 因為是非簡單型別,那麼它提前曝光的,就存放到了集合當前去了。
setMethod.invoke(singletonObjects.get(id), singletonObjects.get(ref));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
});
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
});
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public Object getBean(String beanName) {
return singletonObjects.get(beanName);
}
}
3.10 第十步:測試是否能夠正常賦值成功
注意: 不要導錯包了,我們這裡匯入的是,我們自己手寫的Spring框架的包。
3.11 第十一步:打包釋出
將多餘的類以及配置檔案刪除,當然,我們這裡並不是真正的開發 Spring 框架,所以不刪除也是沒有關係的(只是需要注意不要導錯jar 包了)。這裡我們使用maven打包釋出。關於 Maven 的使用上的內容,想要了解更多的。大家可以移步:✏️✏️✏️ Maven_ChinaRainbowSea的部落格-CSDN部落格。各種細節內容都是截圖標誌了的,這個大家放心。
3.12 第十二步: 站在程式設計師角度使用我們自己手寫的 spring 的 myspring框架
第一步: 我們新建一個名為: myspring-text 的模組,進行一個我們手寫的Spring 的框架的測試。
第二步: 引入我們自己編寫的Spring 框架,不要引錯了。
第三步: 編寫準備一些Bean 物件用於測試,我們自己編寫的 Spring 框架
package com.rainbowsea.myspring.bean;
public class Vip {
private String name;
private int age;
private double height;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Vip{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
package com.rainbowsea.myspring.bean;
public class OrderDao {
public void insert() {
System.out.println("插入資料");
}
}
package com.rainbowsea.myspring.bean;
public class OrderService {
private OrderDao orderDao;
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
public void generate() {
orderDao.insert();
}
}
第四步: 執行,測試,我們編寫的Spring 的框架是否成功。
注意:匯入的是我們自己編寫的Spring 框架。不要導錯了。
從我們的執行結果上看,我們編寫的Spring 框架是沒有問題的。
4. 總結:
框架一般是由 設計模式+註解+反射 來實現,並進行運用開發的。而這裡我們的Spring IoC容器的實現原理:工廠模式 + 解析XML + 反射機制。
我們知道,想要呼叫一個方法,就需要明確知道該方法的四個要素:
- 呼叫的是哪個物件的
- 哪個方法
- 該方法傳什麼引數
- 有無返回值,有返回值的話,又該返回什麼值
引入 dom4j 和 jaxen的依賴,因為要使用它解析XML檔案, SAXReader(這是dom4j 解析xml 檔案的核心物件) 的物件以及Document ,用來讀取我們當中的
myspring.xml'
所含的 bean 的配置資訊。這兩個物件可以讀取到xml
配置檔案的資訊。編寫Spring 框架比較難的點,就是:簡單型別的複雜點,就是一個轉換,我們再 myspring.xml 配置檔案當中的,屬性值都是以字串的形式存在的,而我們的實際屬性的型別,可能是 int,char,double 等等型別的,String 字串型別不可以直接賦值到其他 int,char 型別當中去,所以我們就需要對我們所賦值的屬性進行一個判斷,如果該型別是 int 型別,我們就需要將 String 字串型別轉換為我們所需要的 int 型別。
注意:我們手寫的Spring 框架不併不是完完全全按照,真正的Spring框架來編寫的,這是大部分思路是一致的,對應不同需要上的處理可能不太一致,這一點希望大家可以明白。
我們手寫Spring 框架是為了,更好的理解Spring 框架的原理,從而不懼怕Spring的使用,在使用上更加的靈活,得心易手。
5. 最後:
“在這個最後的篇章中,我要表達我對每一位讀者的感激之情。你們的關注和回覆是我創作的動力源泉,我從你們身上吸取了無盡的靈感與勇氣。我會將你們的鼓勵留在心底,繼續在其他的領域奮鬥。感謝你們,我們總會在某個時刻再次相遇。”