我們回顧一下計算機的發展史,從最初第一台計算機的佔地面積達170平方米,重達30噸,到現如今的個人筆記本,事物更加輕量級功能卻更加豐富,這是事物發展過程中的一個趨勢,在技術領域中同樣也是如此,企業級JavaBean(Enterprise JavaBean ,EJB)在創建之初是非常成功,但是時間一久人們便開始追逐更加方便更加簡易和輕量級的技術框架實現,於是Spring就應運而生,Spring一直開始不斷地涉及到其他領域(這裡就不再多詳談了),而Spring的精髓當中就包括控制反轉和依賴注入。
淺談控制反轉(Inversion of Control,IOC)
我們首先先來了解一下控制二字,也就是在控制「正」轉的情況下,在任何一個有請求作用的系統當中,至少需要有兩個類別互相配合工作,在一個入口類下使用new關鍵字創建另一個類的對象實例,這就好比在面向對象編程的思想下,“我“充當一個入口類,在這個入口類中,我每次吃飯的時候都要買一雙一次性筷子(每次使用都要new一次),在這樣的關係下,是」我「(即呼叫者)每次都要」主動「去買一次性筷子(另一個類),我對筷子說你老老實實的過來我的手上,是我控制了筷子,那好,在這種控制正轉的關係下,放在現實生活當中,肯定是不現實的,而且人是懶惰的,他總會去創造出更加方便自己生活的想法,更確切的做法是,買一雙普通的筷子(非一次性),把他放在一個容器當中(在Spring中叫做IOC容器),你需要使用的時候就對容器說:IOC我想要用筷子(向容器發出請求),接著筷子就會」注入「到的手上,而在這個過程當中,你不再是控制方,反而演變成請求者(雖然本身還是呼叫者),依賴容器給予你資源,控制權坐落到了容器身上,於是這就是人們俗稱的控制反轉。
初涉依賴注入(Dependency Injection)
同樣接著上面的例子,在控制反轉的統一下,筷子是怎麼來到我的手上(即我們是如何獲得請求的類),這就是一個依賴注入的過程。
再談IOC與DI
設計原則中好萊塢原則描述到,“別找我們,我們找你”,百度百科上對這點描述是“不要給我們打電話,我們會給你打電話(don't call us, we'll call you)」這是著名的好萊塢原則。在好萊塢,把履歷遞交給演藝公司後就只有回家等待。由演藝公司對整個娛樂項目的完全控制,演員只能被動式的接受公司的差使,在需要的環節中,完成自己的演出。這一點完美的提現了在IOC身上,IOC所注重的是設計思想上,從一個常規的創建對象的做法,即new一個對象,轉變成向IOC容器遞交”簡歷“,被動的等待IOC容器返回資源給你。控制反轉即指的是”演藝公司控制演員“,而說到依賴,則是“演員需要公司混飯”,我們所需求的對象,需要依賴容器來獲得,這個過程即是依賴注入。本質上IOC和DI是同一思想下不同維度的表現。
程式碼實作
既然說在控制反轉中取得資源的過程叫做依賴注入,那麼這裡程式碼實作也是專注於依賴注入。依賴注入有3種方式,分別為建構注入,設定注入,介面注入。
1.介面注入:在介面中定義要注入的訊息,並透過介面來完成注入。 (Spring不支援這種注入方式--不支援的原因是--Spring聲稱其是非入侵式的《離開這個框架也能活》,如果使用介面注入的話,就違背了這一原則),這裡不做程式碼實現講解。
2.setter注入
我們先脫離Spring來實現setter注入
public interface UserDao{ addUser(String username); }
public class UserDaoImpl implements UserDao{ @Override public void addUser(String username) { System.out.println("添加用户:"+username); } }
public class UserMessage{ private UserDao userDao; //使用设值方式赋值 public void setUserDao(UserDaoImpl userDao) { this.userDao = userDao; } @Override public void addUser(String userName, String password) { userDao.addUser(userName, password); } }
我們仔細觀察,其實這裡的做法跟UserDao userDao=new UserDaoImpl()做法本質是一樣的,這裡就不得不提到了多態性,即父類可以引用子類的方法,在這裡形成的一個效果就是降低了User Message和UserDao的耦合度。再想想,讀者可能會說不對啊,你說的控制反轉和依賴注入需要向容器請求資源,這個容器並沒有在上面提現出來啊,下面我們就講解一下Spring 中是如何做到注入的。
<!-- 使用spring管理对象的创建,还有对象的依赖关系 --> <bean id="userManager" class="scau.zzf.service.UserMessage"> <!-- (1)UserMessageImpl使用了userDao,Ioc是自动创建相应的UserDao实现,都是由容器管理--> <!-- (2)在UserMessageImpl中提供构造函数,让spring将UserDao实现注入(DI)过来 --> <!-- (3)让spring管理我们对象的创建和依赖关系,必须将依赖关系配置到spring的核心配置文件中 --> <property name="userDao" ref="UserDao"></property> <!-- 构造注入 --> <!-- <constructor-arg ref="userDao"/> --> </bean> <bean id="UserDao" class="scau.zzf.Dao.UserDao"> </bean>
首先我們需要組裝Bean,即在Spring容器中將Bean進行配置後,然後返回Bean物件實例。我們可以透過XmlBeanFactory讀取我們xml文件,從而取得相關的Bean資訊。
public class test { public static void main(String[] args) throws Exception { BeanFactory factory=new XmlBeanFactory(new FileSystemResource("src/appllication.xml")); UserMessage userMessage=(UserMessage)factory.getBean("UserMessage"); userMessage.add("德玛西亚"); } }
在實際應用當中,我們並不會手動去讀取Xml中的信息或者加載配置文件,Spring底層已經幫我做好了這些,也就是在實際應用當中,我們就只是需要發送一個請求而已,當然了解這麼一個過程還是有必要的。
下面再簡單講解一下如何透過註解來實現注入。
@Configuration public class UserConfig { @Bean public UserDao getUserDao(){ return new UserDao(); } @Bean public UserMessage getUserMessage(){ return new UserMesssgae(getUserDao); } }
@Configuration的作用是使整個類別成為一個配置類,@Bean註解會告訴Spring這個註解下的方法將會傳回一個對象,這個物件要註冊維Spring應用上下文的Bean。在預設情況下,Spring的Bean都是單例的,也就是再上面的例子當中,無論我們使用多少次getUserDao(),結果傳回的物件至始至終都是相同的。關於JavaConfig的配置進一步相關說明,讀者可以前往筆者的另一篇文章《更優雅的配置SSH》中進行參考。