什麼是IOC容器?爲何須要IOC容器?

什麼是IOC容器?爲何須要IOC容器?

假設咱們如今正在使用三層架構開發一個項目,其中有一個用戶模塊,包含登陸、註冊等功能。如今已經寫好了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,看起來很方便,也能夠正常工做,可是它存在一些問題:服務器

  1. 如今UserServiceImpl依賴於UserDaoImpl,若是這兩個類是由兩個不一樣的人開發的,則他們沒法同時工做,由於在UserDaoImpl完成以前,UserServiceImpl沒法經過編譯
  2. UserServiceImpl沒法被測試,由於它與某個特定的UserDao實現類綁定在了一塊兒,咱們不能把它替換成一個用於單元測試的MockUserDao
  3. 若是咱們有多套數據庫實現(即多個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容器的基本邏輯。

相關文章
相關標籤/搜索