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