Spring IOC
(Inversion of Control,控制反轉
)是Spring的一個核心思想,其包括兩層含義:html
控制
:控制是指當前對象對內部依賴對象的控制權,好比建立依賴對象。反轉
:這種控制權不禁當前對象來管理,由第三方(好比容器)來管理。(反轉也能夠理解爲獲取依賴對象的方式從主動變成了被動)在Spring中,控制反轉
也能夠簡單地理解爲將對象的建立和管理交由IOC容器來完成。java
IOC將依賴對象的控制權轉移到外部,但當前對象仍是須要依賴對象的,這時候就須要使用依賴注入
將所須要的依賴對象從外部注入進來。spring
控制反轉是一種思想,而依賴注入是一種實現方式。segmentfault
爲何須要使用Spring IOC?從IOC思想來看,主要有兩大優勢:數組
從Spring IOC的實際實現來看,還有以下好處:bash
沒有IOC的狀況下,使用依賴的對象須要手動new一個對象出來,根據構造器是否須要參數,能夠分爲有參對象和無參對象。而Spring只須要在xml文件中集中配置一次,或者使用註解就能夠實現依賴注入,不須要手動new對象出來。app
接下來考慮幾種須要修改具體對象實現的狀況下代碼重構的成本,來理解Spring IOC如何實現鬆耦合。工具
好比須要將類名從Message修改成News,那麼此時能夠藉助開發工具的重構功能實現代碼重構。
重構難度:1星。post
好比須要將對象類型名從Student修改成Teacher,那麼此時能夠藉助查找替換功能實現代碼重構。 無重寫方法狀況下,重構難度:2星; 有重寫方法狀況下,重構難度:3星;單元測試
好比須要在建立Student對象時,增長年齡參數,此時藉助查找功能也須要一個個手動修改代碼,增長參數。
重構難度:5星。
若是使用Spring IOC,即使對難度最大的第三種狀況,也只須要在xml文件中修改下注入的參數,或者在對應對象中增長一個屬性,並使用註解自動注入便可。
重構難度:1星。
IOC容器
是Spring提供的一個工廠
,用於管理全部的bean、以及bean之間的關係。
Java反射機制
,經過反射機制獲取類的全部信息(屬性、類名等);Spring中主要提供兩類IOC容器,分別是BeanFactory
和ApplicationContext
。
二者間繼承關係以下圖,可見ApplicationContext間接繼承自BeanFactory。
BeanFactory是Spring提供的最基礎的IOC容器,用於幫助完成bean的註冊和bean之間關係的綁定。
特色:
延加載
策略。當容器中bean被訪問時,才進行bean的初始化和依賴注入工做。BeanFactory最基礎的實現類是DefaultListableBeanFactory
。
舊版本中BeanFactory最多見實現類是XmlBeanFactory(現已被廢棄),新版中使用XmlBeanDefinitionReader
+ DefaultListableBeanFactory
替代XmlBeanFactory
。
出於方便,可直接創建了一個SpringBoot工程。
<?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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
</beans>
複製代碼
這裏使用@Data註解省略了getter和setter方法。
@Data
public class User {
private Integer age;
private Nation nation;
public User() {
}
public User(Integer age) {
this.age = age;
}
public User(Nation nation) {
this.nation = nation;
}
}
複製代碼
<bean id="user" class="com.example.springdemo.xml.model.User">
<constructor-arg name="age" type="java.lang.Integer" value="26"></constructor-arg>
</bean>
複製代碼
public static void main(String[] args) {
// 1. XmlBeanFactory方式,已廢棄
// XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
// 2. XmlBeanDefinitionReader + DefaultListableBeanFactory 替代XmlBeanFactory
Resource resource = new ClassPathResource("application.xml");
BeanFactory factory = new DefaultListableBeanFactory();
BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory);
bdr.loadBeanDefinitions(resource);
// 根據bean名從容器中獲取bean
User user1 = (User) factory.getBean("user");
System.out.println(user1.getAge());
}
複製代碼
ApplicationContext
是在BeanFactory的基礎上構建的,是相對比較高級的容器實現,除了擁有 BeanFactory的全部支持,ApplicationContext還提供了其餘高級特性,好比:
ApplicationContext常見實習類有:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext和XmlWebApplicationContext。
仍然使用BeanFactory示例中的配置文件,同時使用ClassPathXmlApplicationContext
來讀取xml文件。
// 3. ApplicationContext擁有比BeanFactory更高級的特性
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
User user3 = (User) applicationContext.getBean("user");
System.out.println(user3.getAge());
複製代碼
依賴注入方式
主要包括:
構造器注入
屬性注入
(setter方法注入)其中經常使用的兩種方式是構造器注入
和屬性注入
。雖然官方文檔建議能用構造器注入就用構造器注入,由於這樣可使得依賴關係明確,而且若是缺乏依賴的話在初始化階段就能夠發現問題。但屬性注入
更加靈活
,而且構造器注入
方式沒法解決循環依賴問題,因此通常使用屬性注入
,強制依賴
建議使用構造器方式
注入。
基於構造器的依賴注入是指,在bean的構造器中指明依賴的bean,並在初始化時完成注入。
User類見2.2.1.2中,存在以下構造器:
public User(Integer age) {
this.age = age;
}
複製代碼
<!-- 構造器方式注入 -->
<bean id="user" class="com.example.springdemo.xml.model.User">
<constructor-arg name="age" type="java.lang.Integer" value="26"></constructor-arg>
</bean>
複製代碼
基於屬性的依賴注入是指,容器經過無參構造器初始化bean,再經過調用setter方法來完成注入。
User類如上,有屬性age(經過@Data註解省略了getter/setter方法):
private Integer age;
複製代碼
<!-- 屬性注入 -->
<bean id="user2" class="com.example.springdemo.xml.model.User">
<property name="age" value="27"></property>
</bean>
複製代碼
// user使用了構造器注入
User user1 = (User) factory.getBean("user");
System.out.println("DI by constructor:" + user1.getAge());
// user2使用了屬性注入
User user2 = (User) factory.getBean("user2");
System.out.println("DI by setter:" + user2.getAge());
複製代碼
輸出:
在3.1小節中,User bean注入了int型的age屬性,一樣能夠注入其它由IOC容器管理的bean。
User類還存在以下構造器,表示User bean依賴Nation bean。
public User(Nation nation) {
this.nation = nation;
}
複製代碼
Nation類:
public class Nation {
public String name;
public Nation(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
複製代碼
註冊Nation bean,同時註冊User bean,並使用構造器方式對User bean注入Nation bean。
<bean id="nation" class="com.example.springdemo.xml.model.Nation">
<constructor-arg name="name" type="java.lang.String" value="China"></constructor-arg>
</bean>
<!-- 手動注入依賴bean -->
<bean id="user3" class="com.example.springdemo.xml.model.User">
<constructor-arg name="nation" type="com.example.springdemo.xml.model.Nation" ref="nation"></constructor-arg>
</bean>
複製代碼
// 注入IOC容器管理的bean,user注入時須要nation。
// 這時候也能明顯體現Spring Ioc解耦的優勢,若是不使用依賴注入,將對象的建立交由bean來作的話,代碼以下。
// 若是須要修改傳入參數,項目中全部地方都須要修改
Nation nation = new Nation("China");
User user4 = new User(nation);
System.out.println("Name of nation in user3 by code: " + user4.getNation().getName());
// 而使用依賴注入的話,只須要在xml中一個地方集中配置管理,在配置文件中注入取值或者內部bean,而不須要每一個建立對象的地方都要修改
User user5 = (User) factory.getBean("user3");
System.out.println("Name of nation in user3 by IOC: " + user5.getNation().getName());
複製代碼
輸出:
在實際項目中,通常對於Dao、Service都須要單例,不會建立過多的bean,那麼不少時候這些bean並不存在name、type的衝突,這時候是否是能夠根據特定的規則,來簡化bean的裝配。
Spring IOC容器還提供了自動裝配
,能夠根據bean的type、name進行自動裝配,而不須要顯示地聲明bean間依賴關係。
<!-- 自動裝配bean,這裏是byType方式。自動裝配時會將知足條件的bean注入到構造器和setter方法中 -->
<!-- 自動裝配缺點:(1)不適用構造器重寫的狀況;(2)不能裝配基本類型和字符串 -->
<bean id="user4" class="com.example.springdemo.xml.model.User" autowire="byType">
<constructor-arg name="age" type="java.lang.Integer" value="28"></constructor-arg>
</bean>
複製代碼
// 自動裝配bean
User user6 = (User) factory.getBean("user4");
System.out.println("自動裝配:" + user6.getAge());
System.out.println("自動裝配:" + user6.getNation().getName());
複製代碼
輸出:
Spring IOC容器讀取依賴關係方式
有:
xml配置文件
方式註解
方式相比較而言,xml配置文件
方式擁有集中配置
的優勢,而註解
方式則擁有簡化配置
的優勢。
Spring IOC容器能夠經過加載xml配置文件,來讀取依賴關係。
因爲xml配置文件擁有繁雜、不易配置的缺點,Spring IOC容器還同時支持註解
來簡化配置。
使用註解來讀取依賴關係的步驟以下:
在bean類上使用註解@Component聲明bean,使用註解@Autowired來聲明依賴關係:
@Component
@Data
public class User {
private Integer age;
@Autowired
private Nation nation;
}
複製代碼
建立ApplicationContext類型的IOC容器(BeanFactory實測不支持),並設置要掃描註解的包路徑。
// 能夠傳入要掃描的包路徑動態數組,也能夠傳入Class動態數組
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.example.springdemo.annation");
User user1 = (User) applicationContext.getBean("user");
System.out.println(user1.getNation().getName());
複製代碼
Spring IOC容器其實還支持xml配置文件和註解混用的方式來讀取bean間依賴關係。
@Autowired
private Nation nation;
複製代碼
使用構造器方式注入age屬性,但沒有注入nation字段
<!-- 註解與xml混用方式,這裏並無注入nation,也沒有使用自動裝配 -->
<bean id="user5" class="com.example.springdemo.xml.model.User">
<constructor-arg name="age" type="java.lang.Integer" value="30"></constructor-arg>
</bean>
複製代碼
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
// xml與註解混用,經過@Autowired注入nation bean(實際測試BeanFactory不支持註解,須要使用ApplicationContext)
User user7 = (User) applicationContext.getBean("user5");
System.out.println(user7.getAge());
System.out.println(user7.getNation().getName());
複製代碼
從輸出能夠發現age和nation屬性都被注入了。
在3.3.2中介紹了註解的簡單使用,因爲註解已經成爲如今主流的使用方式,接下來詳細介紹些經常使用註解知識。
@Bean
、@Component
等註解用於將對象註冊爲bean,交由Spring容器管理。
@Component
註解在類
上使用,將類的定義與bean的聲明綁定,@Bean則將二者分離。通常@Component更適合聲明service、dao等單例對象,而@Bean更適合用於在不一樣場景下建立不一樣對象的狀況。@Bean
註解註冊同一類的多個bean,該註解使用在方法上。@Componet
、@Configuration
註解聲明的類裏面使用聲明裏面使用;@Bean
// @Primary表示默認bean,當有多個相同類型的bean時,可使用@Primary來表示默認bean,其它地方注入依賴時就不須要都指明具體bean名。
@Primary
public Nation nation1() {
return new Nation("China");
}
@Bean
public Nation nation2() {
return new Nation("England");
}
// 使用name屬性爲bean命名
@Bean(name = "nation3")
public Nation nation() {
return new Nation("America");
}
複製代碼
當有多個相同類型的bean時,可使用@Primary
來表示默認bean,其它地方注入默認bean時就不須要指明具體bean名。
可使用@Value
註解來注入外部屬性,而@ConfigurationProperties
則可用來注入一整個對象的全部屬性,或者說批量注入外部屬性。
@Configuration
@PropertySource("classpath:application.properties")
public class JavaConfiguration {}
複製代碼
@Bean
// 使用@Value來注入外部屬性,須要使用@PropertySource引入資源文件。還可使用@ConfigurationProperties來批量注入外部屬性
public Nation nation4(@Value("${nation4.name:#{null}}") String nation4name) {
return new Nation(nation4name);
}
複製代碼
@Autowired
和@Required
用來注入依賴的bean。
@Autowired
註解能夠在字段
上使用,也能夠在setter方法
上使用,同時能夠在構造器
上使用。@Autowired
默認採用了自動裝配
,且優先根據類型
自動裝配,同類型則優先注入primary bean,而後再依據bean名(與屬性同名)進行注入。@Autowired
還能夠結合@Qualifier
來指定要注入的bean名。例如:@Qualifier("nation2")。@Required
註解也能夠用來注入依賴,但已經被廢棄。
Spring同時支持JSR-250的註解,一樣可使用@Resource
來注入依賴(字段或者setter方法上),並使用name字段來指定bean名。例如:@Resource(name = "nation2")。
Spring3.0還支持JSR-330註解,可使用@Inject
來注入依賴。
@Configuration
註解代表該類是一個Java配置類,用於聲明bean,在該類中可使用@Bean註解來聲明bean。通常狀況下會將同一功能或用途的bean在一個配置累中聲明。
Spring IOC中Bean有多種不一樣做用域,主要有:
接下來主要介紹下singleton和prototype做用域。
singleton
模式(單例模式)是默認模式,該模式的bean有如下特色:
(1)註冊bean 註冊一個bean名爲nation1的單例bean。
@Bean
public Nation nation1() {
return new Nation("China");
}
複製代碼
(2)從容器獲取bean 兩次從容器中獲取同一個bean,是同一對象地址相同。
// 建立的nation1和nation2對象是相同bean
Nation nation1 = (Nation) applicationContext.getBean("nation1");
Nation nation2 = (Nation) applicationContext.getBean("nation1");
System.out.println("nation1 == nation2: " + (nation1 == nation2));
複製代碼
輸出:
prototype
模式(原型模式、多例模式)的bean有如下特色:
(1)註冊bean 註冊一個名爲nation7的多例bean。
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Nation nation7() {
return new Nation();
}
複製代碼
(2)從容器獲取bean 兩次從容器獲取同一個bean,是不一樣的對象,地址不一樣。
// nation3和nation4是不一樣bean。
// 此處須要注意,若是bean類上使用了lombok註解,不要被輸出所迷惑,由於lombok重寫了toString(),使得二者看起來像是一個對象,實際應該比較對象地址
Nation nation3 = (Nation) applicationContext.getBean("nation7");
Nation nation4 = (Nation) applicationContext.getBean("nation7");
System.out.println("nation3 == nation4: " + (nation3 == nation4));
System.out.println("nation3:" + nation3);
System.out.println("nation4:" + nation4);
複製代碼
輸出:
未完待續。。。。。
Spring IOC能夠說是Spring全家桶的基石,AOP等都依賴IOC的實現,要理解學習Spring應該先掌握Spring IOC的知識。
本文先是簡單介紹了Spring IOC的思想和一些基本概念,而後針對Spring IOC容器實現依賴注入相關的知識點,而且分別給出了xml形式和註解形式依賴注入代碼實現。
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/index.html
https://juejin.im/post/5bf51d4c5188256d9832b0d3
https://segmentfault.com/a/1190000013700859
https://segmentfault.com/a/1190000014979704
https://juejin.im/post/5b399eb1e51d4553156c0525
複製代碼