假設咱們如今正在使用三層架構開發一個項目,其中有一個用戶模塊,包含登陸、註冊等功能。如今已經寫好了User
實體類和UserDao
數據訪問層:java
public class User { private Integer id; private String username; private String password; // 如下是getter和setter方法 } public interface UserDao { // 查找用戶 User get(String username, String password); // 插入用戶 void insert(User user); } public class UserDaoImpl implements UserDao { @Override public User getByUsername(String username, String password) { ... } @Override public void insert(User user) { ... } }
UserDao
封裝了對數據庫中的用戶表進行操做。如今,須要一個UserService
來封裝登陸、註冊這兩個業務邏輯:程序員
public interface UserService { // 登陸 User login(String username, String password); // 註冊 void register(User user); } public class UserServiceImpl implements UserService { @Override public User login(String username, String password) { User user = userDao.get(username, password); // userDao從哪裏來? if (user == null) { // 用戶名或密碼錯誤 } return user; } @Override public void register(User user) { userDao.insert(user); // userDao從哪裏來? } }
顯然,UserServiceImpl
須要一個UserDao
的實例userDao
來訪問數據庫,那麼問題來了:這個userDao
如何該獲取呢?
不少人都會用以下代碼來獲取userDao
:數據庫
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); ... }
直接在UserServiceImpl
內部new一個UserDaoImpl
,看起來很方便,也能夠正常工做,可是它存在一些問題:服務器
UserServiceImpl
依賴於UserDaoImpl
,若是這兩個類是由兩個不一樣的人開發的,則他們沒法同時工做,由於在UserDaoImpl
完成以前,UserServiceImpl
沒法經過編譯UserServiceImpl
沒法被測試,由於它與某個特定的UserDao
實現類綁定在了一塊兒,咱們不能把它替換成一個用於單元測試的MockUserDao
UserDao
實現類),那麼不能很方便地切換爲了解決上面幾個問題,可使用一種被稱爲依賴注入的技巧:架構
public class UserServiceImpl implements UserService { private UserDao userDao; // 構造函數注入 public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } ... } // 外部程序 UserService userService = new UserServiceImpl(new UserDaoImpl());
如今,userDao
不是由UserServiceImpl
自己構造,而是讓外部程序經過UserServiceImpl
的構造函數傳入進來,這種操做稱爲構造函數注入。
還可使用另外一種注入方式——setter方法注入:ide
public class UserServiceImpl implements UserService { private UserDao userDao; // setter方法注入 public void setUserDao(UserDao userDao) { this.userDao = userDao; } ... } // 外部程序 UserService userService = new UserServiceImpl(); userService.setUserDao(new UserDaoImpl());
不論哪一種注入方式,其基本邏輯都是同樣的:組件不負責建立本身依賴的組件,而是讓外部程序建立依賴組件,而後經過構造函數或setter函數注入進來。其實,這裏也蘊含着控制反轉的思想,由於建立依賴組件的任務從組件內部轉移到了外部程序。
使用了依賴注入,前面的幾個問題就迎刃而解了,由於UserServiceImpl
再也不依賴UserDao
的具體實現類,咱們能夠輕鬆地替換UserDao
的實現。
可是問題又來了:該由誰負責對象的組裝呢?
答案是:應該由應用的最外層負責對象的組裝。例如,在三層架構中,能夠在controller層負責service類的組裝;若是咱們的程序有main函數,也能夠在main函數中進行相關組件的組裝。函數
public class UserController { private UserService userService = new UserServiceImpl(new UserDaoImpl()); public void handleLoginRequest(...) { userService.login(...); ... } }
按照這種方式寫程序,項目中的全部組件都按照依賴注入的方式管理本身的依賴,全部組件都由最外層統一組裝,若是想替換掉某個組件的實現也很方便,看起來很美好。可是,當項目逐漸變得龐大,組件之間的依賴變多的時候,某個組件可能須要依賴於幾十個大大小小的其它組件,建立這樣的組件就成了一種折磨:單元測試
// 建立一個複雜的組件 Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7());
若是這個組件只須要被使用一次,看起來仍是能夠接受,可是若是這個組件在不少地方都要使用,那麼在每一個使用的地方都須要寫一遍上面建立的代碼,這將會產生大量的代碼冗餘:測試
public class A { Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7()); public void f1() { // 使用c1 ... } } public class B { Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7()); public void f2() { // 使用c1 ... } } public class C { Component1 c1 = new Component1(new Component2(new Component3()), new Component4(new Component5(), new Component6()), new Component7()); public void f3() { // 使用c1 ... } }
更糟糕的是,若是組件c1
依賴的其中一個組件將要被替換,那麼上面全部建立c1
的代碼都要修改,這簡直是維護的噩夢!
爲了不這個問題,能夠把系統中全部的組件放進一個「容器」中統一管理:this
public class Container { public static Component1 getComponent1() { ... } public static Component2 getComponent2() { ... } public static Component3 getComponent3() { ... } ... }
而後,系統中全部須要使用組件的地方都經過Container
類來獲取:
public class A { Component1 c1 = Container.getComponent1(); public void f1() { // 使用c1 ... } } public class B { Component1 c1 = Container.getComponent1(); public void f2() { // 使用c1 ... } } public class C { Component1 c1 = Container.getComponent1(); public void f3() { // 使用c1 ... } }
使用這種方法,不管是獲取組件仍是替換組件都很是方便。可是,如今Container
類是經過Java代碼來實現的,若是系統中的組件有任何變更,就須要修改代碼,而後從新編譯項目。在某些場景下,咱們可能須要在項目運行時動態地添加、移除或者替換組件。
爲了實現組件的動態管理,能夠將如何建立組件以及組件之間的依賴關係等信息寫入配置文件中,而後項目啓動時經過讀取配置文件來動態建立全部組件,再放到Container
中。這樣就能夠在項目運行時修改配置文件中的組件信息,而無需從新編譯,甚至無需重啓服務器:
// 建立Container Container container = new ContainerFactory("container.xml").create(); // 獲取Component1 Component1 c1 = (Component1) container.create("c1");
其實,上面的Container就是一個簡單的IOC容器。IOC表示控制反轉,意思是建立組件的工做再也不由程序員控制,而是由IOC容器控制,程序員只負責告訴IOC容器如何建立某個組件,若是想要這個組件,直接從容器中取就是了,這就是IOC容器的基本邏輯。