什么是IOC容器?為什么需要IOC容器?
假設我們現在正在使用三層架構開發一個專案,其中有一個用戶模塊,包含登錄、注冊等功能,現在已經寫好了User物體類和UserDao資料訪問層:
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方法注入:
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的代碼都要修改,這簡直是維護的噩夢!
為了避免這個問題,可以把系統中所有的組件放進一個“容器”中統一管理:
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容器的基本邏輯,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/255842.html
標籤:其他
