「Don’t call us, we’ll call you(不要聯繫我,我會主動聯繫你)」
這是好萊塢很經典的一句話,應用在ioc(控制反轉)領域,發現理解起來相得益彰——你做爲用戶不須要控制業務實體的生成,交給我容器來控制,這就是控制反轉。不過,這樣理解起來也有點麻煩,套用面向對象大師Martin Fowler的說法更爲貼切: java
「Dependency Inversion(依賴注入)」
當容器中全部的實體bean 都被管理起來的時候,他們之間的依賴關係不須要你手動的寫死在程序中,能夠寫進配置文件,由容器幫你將這些依賴對象主動注入到你定義的業務bean中,也叫依賴注入(DI)。 git
package org.loda.ioc.demo.controller; import org.loda.ioc.annotation.Component; import org.loda.ioc.annotation.Inject; import org.loda.ioc.demo.model.User; import org.loda.ioc.demo.service.UserService; @Component public class UserController { @Inject private UserService userService; public void add(User user){ userService.add(user); } }
以上不是spring提供的依賴注入的Demo,這是我本身實現的,可是原理一致。@Component表示該UserController被spring容器管理起來了,@Inject表示這個UserService類型的屬性的具體實現將有spring幫我注入。 github
由上面簡單的程序,咱們立刻就意識到了spring在這裏作了層隔離,隔離了接口(UserService)和實現(new UserService())。 web
在傳統的三層架構中,有controller,service,dao三層。這三層之間controller中擁有service實例,service擁有dao實例,這樣才能經過controller,直接進行一連串調用,訪問到最後的dao,並由dao來向數據庫發起訪問。這樣,整個web後臺開發的流程就串了起來。 spring
在咱們當前程序中,也是如此,這裏我定義了@Component註解表示該類是要被個人spring管理的,定義了@Inject表示該屬性是要被spring注入的。如圖所示 數據庫
若是完成了ioc/di,通過一層層的調用,個人controller就能訪問到dao,執行dao中的內容,看看dao中的代碼: api
package org.loda.ioc.demo.dao; import org.loda.ioc.annotation.Component; import org.loda.ioc.demo.model.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component("userDao") public class UserDaoImpl implements UserDao { private static final Logger logger = LoggerFactory.getLogger(UserDao.class); @Override public void add(User user) { logger.info("添加了用戶{}", user); } }
能不能打印這句話呢?首先讓咱們看看咱們測試Demo的入口: 數據結構
@Test public void testIoc() throws Exception { // 建立工廠對象,並將配置信息DemoConfig設置進去,工廠對象會自動在後臺完成對象裝載 BeanFactory bf = new AnnotationContext(new DemoConfig()); // 從bean工廠中根據類型(也能夠根據名字)取出對象 UserController uc = bf.getBean(UserController.class); // 斷言不能爲空 Assert.assertNotNull(uc); // 完成添加動做(若是成功依賴注入,那麼這個流程確定能夠走完) uc.add(new User(1, "jack", "123456")); }
BeanFactory做爲對象工廠,將控制着整個bean對象的訪問。這裏的工廠實際上是個藉口,他有個實現類,咱們在他們實現類中完成對象容器的填充和從容器中獲取對象自己的功能。完成bean對象的填充,咱們須要讀取「填充數據源」,即咱們從哪裏獲取填充對象或者填充對象的路徑。主流的實現方式是讀取xml配置文件,根據配置文件中定義的bean配置來初始化這些bean對象到工廠中去。這裏AnnotationContext表示咱們使用掃描註解的方式,掃描指定包中的全部擁有@Component配置的類,實例化這些類,並放到bean工廠中。(這裏new DemoConfig()中指定了從哪裏開始掃描),這裏是從org.loda.ioc包開始掃描,其下全部包都會被掃描到。 架構
package org.loda.ioc.demo; import org.loda.config.SpringConfig; public class DemoConfig implements SpringConfig{ @Override public String[] basePath() { return new String[]{"org.loda.ioc"}; } }
存儲容器須要使用合理的數據結構,基於散列表的hashMap很好的知足了快讀快取的功能,而後咱們定義了接口getBean(String beanName)和getBean(Class<?> targetClass)兩種方法來獲取咱們的bean對象。 ide
這是BeanFactory實現類中的對象獲取的實現:
/** * 使用map做爲容器存儲bean對象 */ protected Map<String, Object> map = new HashMap<String, Object>(); /** * 如下兩個方法都是獲取bean容器中的bean對象 */ @Override public Object getBean(String beanName) { return map.get(beanName); } @SuppressWarnings("unchecked") @Override public <T> T getBean(Class<T> requireType) { return (T) map .get(StringUtils.uncapitalize(requireType.getSimpleName())); }
還記得上面咱們進行
BeanFactory bf = new AnnotationContext(new DemoConfig());
步驟完成了項目的啓動麼,這裏咱們會刷新容器,而後將指定的路徑做爲根路徑,查找須要管理的bean對象,並註冊到map中。
public AnnotationContext(SpringConfig config) { // 刷新spring存儲對象的bean容器(map對象) refresh(); // 註冊bean文件(ioc流程) register(config.basePath()); }
在註冊過程當中,咱們會先掃描根路徑下被@Component註解了的實體類,將這些實體類放到map裏面去。完成這個步驟後,咱們再將全部的屬性依賴一一注入進來。
// 刷新spring存儲對象的bean容器(map對象) refresh(); // 註冊bean文件(ioc流程) register(config.basePath());
其中會碰到以下幾個問題:
1. 根據jdk獲取包對象的api在這裏沒法使用,須要使用自定義的類加載器加載這個包路徑。
2. 加載進來的都是以key,value的形式,這裏key該如何取呢?我採起的形式是,若是@Component中的value屬性沒有配置,就將key定位類名小寫開頭+剩餘字母:如UserController的key就是userController。若是
@Component中value配置了值,就以這個值做爲key。
3. 當屬性上擁有@Inject註解時,表示這個屬性須要依賴注入,他會從已經裝滿了bean對象的map中取。這裏依賴從map中取時,key值能夠是@Inject中註解配置的name、type(名稱、類型)來獲取。
4. 注入屬性在spring中採用兩種方式,set注入和構造函數注入。這裏爲了書寫方便,拋棄了set和get方法,也沒有利用構造函數注入,而
是採起的簡單粗暴、喜聞樂見的破壞封裝性的暴力注入給private屬性。
完成加載bean對象和依賴注入後,咱們的ioc基本功能就此完成,按照測試demo進行測試,發現會輸出dao中的打印信息。很簡單是吧,確實,沒什麼複雜的思想,只要熟悉 反射,人人都能寫一個ioc!
以後,附上源碼下載地址。
https://github.com/mjaow/my_spring
https://git.oschina.net/mjaow/my_spring