依賴注入DI指的是Spring IoC容器對對象的依賴對象的處理過程,對象的依賴對象,説起來比較拗口,其實指的就是:如果一個對象A的某一屬性為對象B,則對象B就是對象A的依賴對象,對象A創建的過程中也要創建對象B並注入到對象A,之後對象A才能正常工作。
Spring IoC可通過如下三種方式注入依賴對象:
- 構造器參數
- 工廠方法參數
- Setter方法
Spring DI因此也分為兩種:基於構造器的DI和基於Setter的DI。
我們需要用代碼來説明Spring兩種方式DI的區別。
代碼準備
創建一個Spring項目,Spring的任何版本都可以,在pom中加入依賴即可:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
創建兩個基礎的接口,以及他們的實現類,不需要包含任何業務邏輯,我們只是為了説明依賴注入的概念。
public interface IDependencyA {
public void test();
}
public interface IDependencyB {
public void testB();
}
@Service
public class DependencyA implements IDependencyA {
@Override
public void test(){
System.out.println("I am DependencyA test...");
}
}
@Service
public class DependencyB implements IDependencyB{
private IDependencyA dependencyA;
public static void main(String[] args) {
System.out.println("I am a new comer...hello world ");
}
@Override
public void testB(){
System.out.print("This is DependencyB's test and ...");
dependencyA.test();
}
public DependencyB(){
//this.dependencyA =springTest;
System.out.println("create DependencyB...");
}
}
其中類DependencyB包含了一個類型為IDependencyA的屬性dependencyA,因此,DependencyB依賴於對象dependencyA。
在添加一個配置類,配置類啥也不幹,僅指定包掃描路徑,包掃描路徑就是為了告訴Spring從什麼地方加載bean:
@Configuration
@ComponentScan(value={"SpringTest"})
public class MyConfiguration {
}
還要簡單約定一下,目前來看Spring項目使用註解的情況應該會多於使用xml配置的情況,因此我們的文章主要基於註解而非基於xml配置文件。除非某些特殊案例只能使用xml、無法使用註解替代的情況下,才給出xml文件的配置。
最後增添加一個啓動類:
public class App {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
System.out.println("Now start to getbean...");
DependencyB dependencyB = applicationContext.getBean(DependencyB.class);
dependencyB.testB();
}
}
下面我們就用這個簡單的例子來説明Spring不同的DI方式。
基於構造器的DI
第一步,我們啥也不幹,直接運行啓動類:
create DependencyB...
Now start to getbean...
This is DependencyB's test and ...Exception in thread "main" java.lang.NullPointerException
at SpringTest.DependencyB.testB(DependencyB.java:14)
at SpringTest.App.main(App.java:11)
DependencyB的依賴對象dependencyA沒有創建,所以報了空指針異常。
我們並沒有給Spring IoC任何DI的指示,所以DI沒有工作,dependencyA沒有注入到dependencyB中。
基於構造器的DI包含兩種方式:
- 構造器參數
- 工廠方法參數
先來看第一種,通過構造方法參數注入,修改DependencyB的構造方法,增加一個IDependencyA的參數:
public DependencyB(IDependencyA dependencyA){
this.dependencyA =dependencyA;
System.out.println("create DependencyB...");
}
再次運行啓動類:
create DependencyB...
Now start to getbean...
This is DependencyB's test and ...I am DependencyA test...
發現dependencyA已經通過構造器參數成功注入到dependencyB中。
另外一種基於構造器參數的DI是通過工廠方法,這種情況下使用xml配置會更加方便:
<!--工廠方法注入-->
<bean id="dependencyB" class="springTest.DependencyB" factory-method="factoryMethod"></bean>
指定factory-method,Spring IoC會調用該方法創建bean,可以通過構造器參數constructor-arg指定依賴對象完成注入。
DependencyB需要增加靜態工廠方法factoryMethod:
public static DependencyB factoryMethod(IDependencyA dependencyA){
DependencyB dependencyB = new DependencyB();
dependencyB.dependencyA =dependencyA;
System.out.println("create DependencyB...");
return dependencyB;
}
基於Setter的DI
Spring IoC通過調用對象的setter方法注入依賴對象的方式。
我們去掉DepedencyB的構造方法的參數,添加setDepencyB方法:
@Component
public class DependencyB implements IDependencyB{
private IDependencyA dependencyA;
public static void main(String[] args) {
System.out.println("I am a new comer...hello world ");
}
@Override
public void testB(){
System.out.print("This is DependencyB's test and ...");
dependencyA.test();
}
public void setDependencyA(IDependencyA dependencyA){
System.out.println("here you call set method...");
this.dependencyA=dependencyA;
}
public DependencyB(){
}
}
然後直接運行啓動類:
Now start to getbean...
This is DependencyB's test and ...Exception in thread "main" java.lang.NullPointerException
at springTest.DependencyB.testB(DependencyB.java:14)
at springTest.App.main(App.java:11)
顯然是不行的,因為我們並沒有指定DependencyB的屬性dependencyA需要注入,此時Spring IoC並不知道需要為DependencyB注入什麼屬性,所以我們需要顯示指定一下(後面我們會知道Spring其實有自動裝配的概念,不指定也可以)。
一種方式是通過註解指定,setDependencyA方法添加@Autowired註解:
@Autowired
public void setDependencyA(IDependencyA dependencyA){
System.out.println("here you call set method...");
this.dependencyA=dependencyA;
}
再次運行啓動類,就OK了。@Autowired註解我們暫不分析,後面學習自動裝配的時候會詳細説。
另外一種,當然可以通過xml指定,所以我們需要在項目的resource目錄下創建一個xml文件mySpring.xml:
<?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.xsd">
<bean id="dependencyB" class="springTest.DependencyB">
<property name="dependencyA" ref="dependencyA"/>
</bean>
<bean id="dependencyA" class="springTest.DependencyA">
</bean>
</beans>
在xml文件中通過property標籤,以及ref指定了類之間的依賴關係。
我們還需要重新創建一個基於xml的啓動類:
public class AppByXML {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("mySpring.xml");
System.out.println("Now start to getbean...");
DependencyB dependencyB = applicationContext.getBean(DependencyB.class);
dependencyB.testB();
}
}
運行啓動類:
here you call set method...
Now start to getbean...
This is DependencyB's test and ...I am DependencyA test...
dependencyA被成功注入了dependencyB中了,從log中可以清晰的看到Spring是通過調用setter方法實現注入的。
這種情況下,setter方法必須是public的,否則Spring執行注入的時候會因為找不到setter方法而拋異常。
基於構造器的 or 基於Setter的 DI?
既然有基於構造器的和基於Setter的DI,那我們在項目中應該怎麼選?
Spring官網説,Spring Team一般推薦使用基於構造器的DI,因為基於構造器的DI有個好處是當你創建對象後,依賴對象也一同準備好了,可以避免後續業務處理的錯誤。
但是對於一個相對複雜的類來説,依賴對象一大堆,構造器就會很醜陋,所以,其實項目中還是基於Setter、或者自動裝配更好用。
另外,基於構造器和基於Setter的DI是可以結合使用的,對於強制要求的依賴對象可以放在構造器中,可以在編譯階段就發現依賴對象沒有準備好的問題,避免運行期錯誤。
另外一種依賴 depend on
除了我們上面所説的,一個對象作為另外一個對象的屬性這種類型的依賴之外,還有另外一種依賴:一個對象的運行是依賴於另外一個對象的但是另外一個對象並不是當前對象的屬性。
這種情況可以使用@DependsOn註解或xml的 depend on標籤:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
配置很麻煩
你不覺得DI的配置、尤其是xml方式的配置,很麻煩嗎?一個一個的類、其中的一個一個依賴都需要通過xml進行配置,尤其是當你需要修改一個類、引入新的依賴對象或者去掉一個依賴對象的時候,你不得不同時打開xml配置文件做相應的修改。
確實非常麻煩,但是Spring為你想到了一個很好的處理辦法:自動裝配。
下一篇文章學習。
上一篇 Spring FrameWork從入門到NB - Ioc
下一篇 Spring FrameWork從入門到NB - Bean