Spring 框架模擬實現(xiàn)
1. 前言
通過幾個章節(jié)的學習,大家對于 Spring 已經(jīng)有了初步的認知,我們通過案例練習,或者源碼追蹤,可以粗略的看到 Spring 框架初始化 bean 對象的過程,那么這個章節(jié),我們模擬 Spring 框架的思路,來寫一個類似 Spring 加載對象的案例,加深大家的印象。
2. 案例實現(xiàn)思路
2.1 步驟介紹
思路分析:
我們通過寫過的案例可以知道:
- Spring 框架的容器 是一個接口
ApplicationContext
和接口的實現(xiàn)類ClassPathXmlApplicationContext
來初始化的; - 在初始化容器對象的時候需要傳遞 xml 配置文件的位置;
- xml 的配置文件中主要是通過 bean 標簽可以對 Java 的類進行描述:類的路徑 類的標識 類的構(gòu)造參數(shù)等等;
- 容器初始化以后需要解析 xml 配置文件的各個 bean 標簽;
- 實例化的對象如果有參數(shù)或者構(gòu)造方法,那么也需要給參數(shù)賦值;
開發(fā)準備:
為了方便理解測試 ,我們來自定義容器的接口和實現(xiàn)類。
名稱改為 SpringContext
和 XmlSpringContext
區(qū)別于框架的接口和實現(xiàn)類。
接口定義方法 getBean 用于獲取容器內(nèi)的示例,實現(xiàn)類定義有參構(gòu)造用于接受初始化時候的配置文件路徑。
接口代碼如下:
public interface SpringContext {
public Object getBean(String beanName);
}
實現(xiàn)類代碼如下:
public class XmlSpringContext implements SpringContext {
Map<String,Object> map = new HashMap<String,Object>();
public XmlSpringContext (String filename){
}
public Object getBean(String beanName){
return map.get(beanName);
}
}
代碼解釋:
- map 用于存儲實例化的 bean 對象 ;
- 有參構(gòu)造方法邏輯暫時為空,下面會做實現(xiàn),加載文件實例化對象在方法內(nèi)部;
- getBean 的方法用于通過 key 獲取 map 中存儲的實例。
為了測試對象的實例化,我們自定義 UserService
和 UserServiceImpl
作為測試的接口對象和實現(xiàn)類。
接口代碼如下:
public interface UserService {
public void deleteById(Integer id);
}
接口的實現(xiàn)類代碼如下:
public class UserServiceImpl implements UserService {
//持久層的dao屬性
private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
//實現(xiàn)接口的方法
public void deleteById(Integer id) {
System.out.println("刪除的方法執(zhí)行");
}
}
代碼解釋:dao 的屬性其實是為了模擬屬性賦值,后面依賴注入章節(jié)會詳細講解。
自定義一個 xml 文件 作為模擬框架的配置文件 :
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean name="userDao" class="com.wyan.dao.UserDaoImpl"></bean>
<bean name="userService" class="com.wyan.service.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
代碼解釋:userDao 的 bean 需要實例化 是因為 service 用到了它的引用,所以這里多個屬性 property。
編寫測試類加載文件測試:
public class TestSpring {
@Test
public void test() {
//初始化容器(讀取配置文件 構(gòu)建工廠)
SpringContext context =
new XmlSpringContext("applicationContext.xml");
UserServiceImpl userService = (UserServiceImpl) context.getBean("userService");
userService.deleteById(1);
System.out.println(userService.getUserDao());
}
}
代碼解釋:這里的目的只是測試能否獲取對象調(diào)用方法,如果控制臺打印證明案例成功
2.2 容器對象的實現(xiàn)類構(gòu)造函數(shù)具體代碼
思路分析:
1. 讀取初始化時候傳遞的文件路徑;
2. 通過 SAXReader 解析 xml 文件的節(jié)點得到 beans 節(jié)點下對應多個 bean 節(jié)點集合;
3. 每一個 bean 表示一個對象,都需要被初始化,所以需要循環(huán)遍歷集合;
4. 在循環(huán)遍歷的過程中獲取 id 屬性和 class 屬性,id 屬性作為存入 map 的 key,class 屬性用于反射實例化對象,并存儲 map 的 value;
5. 繼續(xù)解析子節(jié)點,如果有參數(shù),反射獲取 method 執(zhí)行參數(shù)賦值。
完整代碼:
public XmlSpringContext(String filename){
// xml文件的解析器
SAXReader sr = new SAXReader();
try {
//構(gòu)建一個直接通向我們配置文件路徑 的輸入流
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(filename);
//文檔模型對象
Document doc = sr.read(inputStream);
//獲取根標簽
Element root = doc.getRootElement();
//獲取當前根標簽的子標簽
List<Element> beans = root.elements("bean");
for(Element bean:beans){
String key = bean.attributeValue("name");
String value = bean.attributeValue("class");
Class<?> myclass = Class.forName(value);
//當前對象
Object obj = myclass.newInstance();
map.put(key, obj);
List<Element> elements = bean.elements("property");
if(elements.size()>0){
for(Element pro: elements){
String av = pro.attributeValue("name");//dao--->setDao
//方法名
String methodName="set"+(av.charAt(0)+"").toUpperCase()+av.substring(1,av.length());
//方法參數(shù)
String refvalue = pro.attributeValue("ref");
Object refobj = map.get(refvalue);
//根據(jù)方法名稱獲取方法對象Method
Method method = myclass.getMethod(methodName,refobj.getClass().getInterfaces()[0]);
method.invoke(obj, refobj);
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
測試結(jié)果:
3. 小結(jié)
本章節(jié)帶著大家模擬一下 Spirng 加載文件的過程和實例化對象的過程,當然這個過程只是模擬 Spring 的框架的思路,而并不是真正的 Spring 框架源碼,實際源碼遠比這個要復雜的多,
那么通過本章節(jié)我們收獲哪些知識呢?
- Spring 容器類的使用
- xml 配置文件的作用
- 反射技術(shù)的應用
我不相信不勞而獲,如果有誰告訴你,他可以做到 XX 速成,請遠離他,他連基本的誠信都沒有…