IOC(Inversion Of Control)
1.概念:
- 控制反轉(Inversion of Control,縮寫爲IoC),是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。
- IoC是一種設計思想。
2.一句話闡述控制反轉:
-
將原本在程序中手動創建對象的控制權,交由Spring框架來管理。
-
控制指的是創建對象的控制權。
-
反轉指的是將對象控制權完全交由IoC容器,並由IoC來管理對象間的相互依賴關係。
-
Spring IOC 負責創建對象,管理對象(通過依賴注入(DI),裝配對象,配置對象,並且管理這些對象的整個生命週期。
-
3.簡單的生活例子(工廠模式):
假如你現在很想喝可樂,然後你試着去製作一瓶可樂:
那麼你需要做些什麼呢?
- 購買原料:可口可樂的糖漿(可口可樂的靈魂),碳酸水,糖,焦糖,磷酸,咖啡因。
- 按一定比例進行混合,攪拌。
- 特殊工藝對可樂進行成品製作。
- 裝瓶,壓蓋。
- 最後得到一瓶圖中的可樂。
但是你的目的僅僅是想喝一瓶可樂,所以對你而言,最簡單的辦法就是直接買一瓶,從哪來,工廠。
於是,所有這些複雜的操作都交給了工廠,你只需要向工廠要,工廠就給你一瓶成品。
在上述過程中,發生了這樣的改變:
- 由你自己生產一瓶可樂,到工廠爲你生產一瓶可樂,你直接拿來使用就可以了。
這個過程其實就是設計模式中的工廠模式:
- 工廠模式(Factory Pattern)是 Java 中最常用的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。
在工廠模式中,我們在創建對象時不會對客戶端暴露創建邏輯,並且是通過使用一個共同的接口來指向新創建的對象。
爲什麼要了解工廠模式,因爲它就是IOC的核心思想。
4.IOC的由來
假設你是一名Java開發者,你參與了一個非常巨大的項目,比如,某某大型交易網站,在你負責的代碼中,有這樣一個模塊:
很複雜是吧,其實還可以更加複雜。
這裏麪包含了許多調用,控制,繼承的關係,那麼,這樣的程序有什麼樣的缺點呢?
- 可維護性極差。
爲什麼這樣說呢,舉個例子,如果在哪一天,由於業務需求,你需要更改其中某一段代碼,那可真是一件有趣的事情,你需要更改所有與這個類有關的代碼,一句話描述就是:牽一髮而動全身。
- 用標準語言來解釋的話,這叫:耦合性高,造成可維護性差。
對於企業來說,最好的依賴關係是什麼呢,在企業中,項目一般由許多人合作完成,如果A程序員在寫B類時,需要C程序員負責的D類,而C一直沒有開始寫D類,怎麼辦,A程序員也不寫嘛,這肯定是不行的,對於企業而說,最好的依賴關係是:
- 編譯時不依賴,運行時才依賴
那麼這個問題怎麼解決呢,這個時候,你會想起工廠模式,把他們都交給工廠,由工廠來處理它們之間的依賴關係,需要對象的時候,向工廠索要就可以了。這也是IOC最初的由來。
- 爲了解決對象之間的耦合度過高的問題,軟件專家Michael Mattson提出了IOC理論,用來實現對象之間的“解耦”,目前這個理論已經被成功地應用到實踐當中,很多的J2EE項目均採用了IOC框架產品Spring。
5.IOC容器:
IOC的核心在IOC容器:
通過IOC容器,成功的將上述的高耦合的代碼解耦,對象間的依賴關係,全部由IOC容器來處理。這個容器,也可以看做是是一個工廠。
Spring-IOC容器:
- 在Spring中,核心IOC容器是一個Map結構。
key
是bean的id。value
是Object
類型的,是bean對象。
6.Spring IOC的簡單配置:
通過xml文件配置的方式,將對象交給Spring容器來管理:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 將對象交給Spring容器來管理 -->
<bean id="ATService" class="service.impl.ATserviceImpl"></bean>
<bean id="ATDao" class="dao.impl.ATDaoImpl"></bean>
</beans>
-
<bean>
標籤:用於讓Spring創建對象,並存入IOC容器中。id
:對象唯一標識符。class
:對象的全限定類名。
從ioc容器中獲取對象的例子:
//獲取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根據id獲取Bean
ATService obj1 = (ATService)ac.getBean("ATService");
ATDao obj2 = (ATDao)ac.getBean("ATDao");
7.Spring IOC有關的結構:
接口結構圖:
-
BeanFactory
是 Spring容器中的頂層接口。 -
ApplicationContext
是BeanFactory
的子接口。 -
二者區別:
ApplicationContext
:只要一讀取配置文件,默認情況下就會創建對象(即時加載)。單例對象適用(一個應用只有一個對象的實例)。BeanFactory
:什麼使用什麼時候創建對象(延遲加載)。多例對象適用(每次訪問對象時,都會重新創建新的對象實例)
-
ApplicationContext
常用實現類:ClassPathXmlApplicationContext
:從類的根路徑下加載配置文件。每次訪問對象時,都會重新創建新的對象實例。AnnotationConfigApplicationContext
:在有訪問權限的條件下可以加載磁盤任意路徑下的配置文件。AnnotationConfigApplicationContext
:用於讀取註解創建容器。
8.Spring對Bean的管理:
實例Bean的方式:
- 方式一:Spring默認會根據無參構造函數來創建類對象。如果 bean中沒有默認無參構造函數,將會創建失敗。
- 方式二:創建一個實例工廠,讓Spring來管理,調用裏面的方法來創建對象。
工廠類:
public class Factory{
public ATService createATService{
return new ATServiceImpl();
}
}
配置bean:
<bean id="Factory" class="factory.Factory"></bean>
<bean id="ATService" factory-bean="Factory" factory-method="createATService"></bean>
-
factory-bean
屬性:用於指定實例工廠 bean的 id。 -
factory-method
屬性:用於指定實例工廠中創建對象的方法。 -
方式三:創建一個靜態工廠,讓Spring來管理這個工廠,調用這個工廠的方法來創建對象。
工廠類:
public class Factory{
public static ATService createATService{
return new ATServiceImpl();
}
}
配置bean:
<bean id="ATService" class="factory.Factory" factory-method="createATService"></bean>
class
屬性:指定靜態工廠的全限定類名。factory-method
屬性:指定生產對象的靜態方法。
管理Bean的作用範圍:
<bean>
標籤的scope
標籤用於指定對象的作用範圍。
singleton
:該屬性的默認值,是單例的 。prototype
:是多例的。request
,session
:WEB項目中,將創建的對象存入指定的中。global session
:WEB項目中,應用於 Portlet環境,沒有Portlet環境,就應用於session
。
管理Bean的生命週期:
<bean>
標籤的init-method
屬性:指定類中的初始化方法名稱。<bean>
標籤的destroy-method
屬性:指定類中銷燬方法名稱。
對於單例對象:
- 一個應用只有一個對象的實例。它的作用範圍是整個引用。
- 創建容器時就創建了,銷燬容器時才銷燬。
- 單例對象的生命週期等同於容器。
對於多例對象:
- 每次訪問對象時,都會重新創建對象實例。
- 當使用時才創建新的對象實例。
- 當對象長時間不使用時,被JVM垃圾回收。
9.Spring-IOC依賴注入(DI)
- DI—Dependency Injection,即 依賴注入。
- DI是IOC的另一個說法,主要由反射實現。
- Spring通過DI來實現對對象間各種依賴關係的管理。
構造函數注入:
適用於不常變化的數據。
使用構造函數注入,需要在<bean>
標籤內使用<constructor-arg>
。
屬性:
type
:指定注入的數據類型index
:指定要注入數據在構造函數參數列表的索引位置。name
:參數在構造函數中的名稱。value
:對指定參數賦值。(僅可以是基本數據類型和String型)ref
:賦值。(在該配置文件中已配置的其它bean類型)
實例:
<bean id="ATService" class="service.impl.ATserviceImpl">
<constructor-arg name="name" value="ATFWUS"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="date"></constructor-arg>
</bean>
<bean id="date" class="java.util.Date"></bean>
優劣勢分析:
- 優勢:對象在構造完後就進入了準備的狀態,可以立即使用。
- 劣勢:當依賴對象過多時,構造方法的參數會比較的長,而通過反射構造對象的時候,對相同類型的參數處理會比較困難。
set方法注入:
需要原類提供set方法。
使用此種注入方式,需要使用<property>
標籤。
屬性:
name
:set方法名稱後面部分,如setName
,這裏是name
。value
:給屬性賦值。(只可以是基本數據類型和String)ref
:賦值,其它bean類型。
對於注入集合,哈希表等數據類型,可以使用子標籤:
<set><value>ATFWUS</value></set>
set集合數據的注入。<array><value>ATFWUS</value></array>
數組數據注入。<list><value>ATFWUS</value></list>
list集合數據注入。- property數據注入:
<property name='mypro'>
<pros>
<prop key='A'>a</prop>
<prop key='B'>b</prop>
</prop>
</property>
- map數據注入:
<property name='mymap'>
<map>
<entry key='A' value='a'></entry>
<entry key='B'>
<value>b</value>
</entry>
</map>
</property>
優劣勢分析:
- 優勢:創建對象沒有明確限制,可以使用默認構造函數。
- 劣勢:如果有成員必須有值,那麼獲取的對象set方法可能沒有執行。
10.Spring基於註解的IOC
創建對象註解–@Component
:
先需要在xml文件中告知Spring創建容器時需要掃描的包:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知Spring創建容器時需要掃描的包 -->
<context:component-scan base-package="com" ></context:component-scan>
</beans>
- 作用:把當前類對象存入Spring容器。
- 相當於XML中的
<bean id="" class="">
。 - 屬性
value
:指定bean的id,不指定則默認時當前類名(首字母改小寫)。
創建對象註解–@Controller
,@Service
,@Respository
:
- 這三個註解是
@Component
的衍生註解,不過賦予了語義化。 @Controller
:一般用於表現層的註解。@Service
:一般用於業務層的註解。@Repository
:一般用於持久層的註解。
注入數據註解–@Autowired
:
- 自動按照類型注入。只要容器中有唯一bean對象與之匹配,就可以注入成功。
- set方法可以省略。
- 它只能注入其他 bean類型。當有多個類型匹配時,使用要注入的對象變量名稱作爲 bean的 id,在 spring容器查找,找到了也可以注入成功。找不到就報錯。
- 屬性
value
:指定 bean的 id。
注入數據註解–@Qualifier
,@Resource
,@Value
:
@Qualifier
在自動按照類型注入的基礎之上,再按照 Bean 的 id 注入。它在給字段注入時不能獨立使用,必須和@Autowire
一起使用;但是給方法參數注入時,可以獨立使用。屬性value
:指定 bean的 id。@Resource
:直接按照 Bean的 id注入。只能注入其他 bean類型。屬性name
:指定bean的id。@Value
:注入基本數據類型和 String類型數據,屬性value
:指定注入的值。
改變作用範圍註解–@Scope
- 用於指定 bean的作用範圍。
- 相當於:
<bean id="" class="" scope="">
。 - 屬性value:指定範圍的值。
生命週期註解–@PostConstruct
,@PreDestroy
:
@PostConstruct
指定初始化方法。@PreDestroy
指定銷燬方法。
配置註解–@Configuration
,@ComponentScan
:
- 用於指定當前類是一個 spring 配置類,當創建容器時會從該類上加載註解。
- 使用
@Configuration
在獲取容器時需要使用AnnotationApplicationContext
(有@Configuration
註解的類.class)。 @ComponentScan
用於指定 spring在初始化容器時要掃描的包。作用和在 spring的 xml配置文件中的:<context:component-scan base-package="com"/>
是一樣的。@ComponentScan
屬性basePackages
:用於指定要掃描的包。和該註解中的value
屬性作用一樣。
獲取容器:
ApplicationContext ac =
new AnnotationConfigApplicationContext(SpringConfiguration.class);
配置註解–@Import
:
- 用於導入其他配置類,在引入其他配置類時,可以不用再寫@Configuration 註解。
- 屬性
value[]
:用於指定其他配置類的字節碼。
創建對象註解–@Bean
:
- 該註解只能寫在方法上,表明使用此方法創建一個對象,並且放入 spring容器。
- 屬性
name
:給當前@Bean註解方法創建的對象指定一個名稱(與 bean的 id一樣)。 - 默認是當前方法名稱。
加載配置文件註解–@PropertySource
:
- 此註解用於加載
.properties
文件中的配置。 - 屬性
value[]
:指定 properties文件位置。如果是在類路徑下,需要寫上類路徑classpath
。
11.XML配置與註解的權衡分析
-
XML文件配置的優勢:不涉及任何源碼,修改後,無需重新編譯與部署。
-
註解配置的優勢:配置方便簡單,維護方便,直接找到類就可以改變相應的配置。
-
使用場景分析:
- 如果項目所需的Bean大都來源於第三方,基於XML配置更好。
- 如果項目所需的Bean大都用戶自我開發,基於註釋配置更好。
12.IOC優劣勢分析
- IOC的優勢大大降低了程序間的耦合性,將對象交給Spring容器,簡化了開發,增強了可維護性。
- IOC的一些不足:IOC的核心是反射,反射創建對象一定程度上,還是對效率有一定的影響,但目前影響已經不大,大概1-2倍的差距。
本文暫不涉及Spring IOC實現的源碼分析,僅解釋了Spring IOC,和怎樣去使用Spring IOC,後期會對Spring的IOC相關源碼進行分析。
參考書籍:
- 《Spring實戰》第四版
- 《SSM項目實戰》
感謝耐心的你看到了這,再次感謝!
ATFWUS --Writing By 2020–04-20