1. 程式人生 > >依賴注入,控制反轉總結

依賴注入,控制反轉總結

DI(dependency injection)依賴注入模式;依賴注入是指將元件的依賴通過外部以引數或其他形式注入;
再看看
IOC(inversion of control)控制反轉模式;控制反轉是將元件間的依賴關係從程式內部提到外部來管理;
其實兩個說法本質上是一個意思。

不管是依賴注入,還是控制反轉,都說明Spring採用動態、靈活的方式來管理各種物件。物件與物件之間的具體實現互相透明。在理解依賴注入之前,看如下這個問題在各種社會形態裡如何解決:一個人(Java例項,呼叫者)需要一雙鞋子(Java例項,被呼叫者)。

(1)原始社會裡,幾乎沒有社會分工。需要鞋子的人(呼叫者)只能自己去編一雙鞋子(被呼叫者)。對應的情形為:Java程式裡的呼叫者自己來new一個物件

(2)進入工業社會,工廠出現。鞋子不再由普通人完成,而在工廠裡被生產出來,此時需要不同鞋子的人(呼叫者)找到工廠,購買鞋子,無須關心鞋子的製造過程。對應Java程式裡簡單的工廠設計模式但也需要開發人員在工廠類程式碼中new出物件,如在方法中根據引數返回介面的各實現類。

(3)進入“按需分配”社會,需要鞋子的人不需要找到工廠,坐在家裡發出一個簡單指令:需要某種鞋子。鞋子就自然出現在他面前。對應Spring的依賴注入。這裡沒有開發人員主動去new出物件而是通過在Spring的配置檔案中定義相應的呼叫者和被呼叫者的bean,然後測試類通過例項化Spring的上下文物件,再通過物件呼叫getBean(“id名”)來獲取相應的呼叫物件
ApplicationContext ctx = new FileSystemXmlApplicationContext(“applicationcontex.xml”);

依賴注入的三種實現:1.設定注入 2.構造注入 3.註解注入

1.設定注入

設值注入是指通過setter方法傳入被呼叫者的例項。這種注入方式簡單、直觀,因而在Spring的依賴注入裡大量使用。看下面程式碼,是Person的介面
//定義Person介面
public interface Person
{
//Person接口裡定義一個使用鞋子的方法
public void useShoes();
}

然後是Shoes的介面
//定義Shoes介面
public interface Shoes
{
//Shoes接口裡有個walk的方法
public void walk();
}

Person的實現類
//Chinese實現Person介面

public class Chinese implements Person
{
//面向Shoes介面程式設計,而不是具體的實現類
private Shoes shoes;
//預設的構造器
public Chinese()
{}
//設值注入所需的setter方法
public void setShoes(Shoes shoes)
{
this.shoes= shoes;
}
//實現Person介面的useShoes方法
public void useShoes()
{
System.out.println(shoes.walk());
}
}

Shoes的第一個實現類
//Shoes的第一個實現類拖鞋 slipper

public class Slipper implements Shoes
{
//預設構造器
public Slipper()
{}
//實現Shoes介面的walk方法
public String walk()
{
return “拖鞋走路好慢”;
}
}

Shoes的第二個實現類
//Shoes的第二個實現類運動鞋 sneaker

public class Sneaker implements Shoes
{
//預設構造器
public Sneaker()
{}
//實現Shoes介面的walk方法
public String walk()
{
return “運動鞋走路好快”;
}
}

下面採用Spring的配置檔案將Person例項和Shoes例項組織在一起。配置檔案如下所示:
<!-- 下面是標準的XML檔案頭 -->
<?xml version=“1.0” encoding=“gb2312”?>
<!-- 下面一行定義Spring的XML配置檔案的dtd -->
"http://www.springframework.org/dtd/spring-beans.dtd">
<!-- 以上三行對所有的Spring配置檔案都是相同的 -->
<!-- Spring配置檔案的根元素 -->
<BEANS>
<!—定義第一bean,該bean的id是chinese, class指定該bean例項的實現類 -->
<BEAN class=lee.Chinese id=chinese>
<!-- property元素用來指定需要容器注入的屬性,shoes屬性需要容器注入此處是設值注入,因此Chinese類必須擁有setShoes方法 -->
<property name="shoes">
<!-- 此處將另一個bean的引用注入給chinese bean -->
<REF local=“slipper”/>
</property>
</BEAN>
<!-- 定義slipper bean -->
<BEAN class=lee.Slipper id=slipper />
</BEANS>

從配置檔案中,可以看到Spring管理bean的靈巧性。bean與bean之間的依賴關係放在配置檔案裡組織,而不是寫在程式碼裡。通過配置檔案的 指定,Spring能精確地為每個bean注入屬性。因此,配置檔案裡的bean的class元素,不能僅僅是介面,而必須是真正的實現類。

Spring會自動接管每個bean定義裡的property元素定義。Spring會在執行無引數的構造器後、建立預設的bean例項後,呼叫對應 的setter方法為程式注入屬性值。property定義的屬性值將不再由該bean來主動建立、管理,而改為被動接收Spring的注入。

每個bean的id屬性是該bean的惟一標識,程式通過id屬性訪問bean,bean與bean的依賴關係也通過id屬性完成。

下面看主程式部分:
public class BeanTest
{
//主方法,程式的入口
public static void main(String[] args)throws Exception
{
//因為是獨立的應用程式,顯式地例項化Spring的上下文。
ApplicationContext ctx = new FileSystemXmlApplicationContext(“applicationcontex.xml”);
//通過Person bean的id來獲取bean例項,面向介面程式設計,因此
//此處強制型別轉換為介面型別
Person p = (Person)ctx.getBean(“chinese”);
//直接執行Person的useShoes()方法。
p.useShoes(); //這裡方法執行控制檯會輸出 “拖鞋走路好慢”
}
}

程式的執行結果如下:

拖鞋走路好慢

主程式呼叫Person的useShoes()方法時,該方法的方法體內需要使用Shoes的例項,但程式裡沒有任何地方將特定的Person例項和Shoes實 例耦合在一起。或者說,程式裡沒有為Person例項傳入Shoes的例項,Shoes例項由Spring在執行期間動態注入。

Person例項不僅不需要了解Shoes例項的具體實現,甚至無須瞭解Shoes的建立過程。程式在執行到需要Shoes例項的時候,Spring建立了Shoes例項,然後注入給需要Shoes例項的呼叫者。Person例項執行到需要Shoes例項的地方,自然就產生了Shoes例項,用來供Person例項使用。

這裡如果想把鞋子換成運動鞋(注意這裡的運動鞋類以上程式碼已經寫出,如果換其他鞋可以新增類)只需要:
修改原來的Spring配置檔案,在其中增加如下一行:
<!-- 定義一個Sneaker bean–>
<BEAN class=lee.Sneaker id=sneaker />

該行重新定義了一個Shoes的實現:Sneaker。然後修改chinese bean的配置,將原來傳入slipper的地方改為傳入sneaker。也就是將
<REF local=“sneaker”/>

改成
<REF local=“sneaker”/>

此時再次執行程式,將得到如下結果:

運動鞋走路好快

Person與Shoes之間沒有任何程式碼耦合關係,bean與bean之間的依賴關係由Spring管理。採用setter方法為目標bean注入屬性的方式,稱為***設值注入***。
業務物件的更換變得相當簡單,物件與物件之間的依賴關係從程式碼裡分離出來,通過配置檔案動態管理。

2.構造注入

所謂構造注入,指通過建構函式來完成依賴關係的設定,而不是通過setter方法。對前面程式碼Chinese類做簡單的修改,修改後的程式碼如下:
Person的實現類
//Chinese實現Person介面

public class Chinese implements Person
{
//面向Shoes介面程式設計,而不是具體的實現類
private Shoes shoes;
//預設的構造器
public Chinese()
{}
//構造注入所需的帶引數的構造器
public Chinse(Shoes shoes)
{
this.shoes = shoes;
}

//實現Person介面的useShoes方法
public void useShoes()
{
System.out.println(shoes.walk());
}
}

此時無須Chinese類裡的setShoes方法,構造Person例項時,Spring為Person例項注入所依賴的Shoes例項。構造注入的配置檔案也需做簡單的修改,修改後的配置檔案如下:
<!-- 下面是標準的XML檔案頭 -->
<xml version=“1.0” encoding=“gb2312”?>
<!-- 下面一行定義Spring的XML配置檔案的dtd -->
"http://www.springframework.org/dtd/spring-beans.dtd">
<!-- 以上三行對所有的Spring配置檔案都是相同的 -->
<!-- Spring配置檔案的根元素 -->
<BEANS>
<!—定義第一個bean,該bean的id是chinese, class指定該bean例項的實現類 -->
<BEAN class=lee.Chinese id=chinese>
</BEAN>
<!-- 定義sneaker bean -->
<BEAN class=lee.Sneaker id=sneaker />
</BEANS>

執行效果與使用sneaker設值注入時的執行效果完全一樣。區別在於:建立Person例項中Axe屬性的時機不同——設值注入是現建立一個預設的bean例項,然後呼叫對應的構造方法注入依賴關係。而構造注入則在建立bean例項時,已經完成了依賴關係

3.註解注入

在進行Spring開發時所需要的基礎jar包有:

這裡寫圖片描述
在這裡插入圖片描述
當需要在Spring中使用註解的時候,還需要匯入
這裡寫圖片描述
在這裡插入圖片描述

在配置檔案中引入新的約束:

  xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> 

在配置檔案中進行配置:

在需要使用註解進行的時候,需要在配置檔案中進行 開啟註解掃描的配置

<context:component-scan base-package=“com.某某包名” ></context:component-scan>

context:annotation-config</context:annotation-config>

使用註解的方式來建立物件
Computer.java(需要被建立的類)

/**

  • Created by **
    */
    @Component(value = “computer”) //相當於在配置檔案中 
    @Scope(value = “prototype”) //配置建立物件是否是以單列模式進行建立
    public class Computer {
    public void printBrand(){
    System.out.println(“小米筆記本”);

    }
    }

在需要例項化的類的類名上面加上@Component 註解來進行標識,value的值就相當於在配置檔案中進行配置時bean標籤的id屬性的值,用於物件的建立。

建立物件的四個註解:
@Component
@Controller //web層中使用
@Service   //業務層
@Repository //持久層

這四個註解目前的功能都是一樣的,註解名的不同為了能夠讓標記類本身的用途更加清晰,Spring在後續的版本中會對其加強。

建立物件的測試程式碼:

@Test
public void Test(){

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext02.xml");

Computer computer = (Computer) context.getBean("computer");

computer.printBrand();

}

執行結果:
小米筆記本

使用註解注入物件屬性
在開發中我們經常會遇到在一個類中持有另外一個類的引用,在前面的部落格中記錄了使用配置檔案中注入物件屬性的方式,下面我們接著來看一下使用註解進行注入的方式:

下面的例子,在UserService中持有UserDao這個物件的引用

UserDao.java

/**

  • Created by **
    */
    @Component(value = “userDao”)
    public class UserDao {

    public void printInfo(){

     System.out.println("UserDao中的例項化物件裡的方法執行了");
    

    }
    }

兩種物件屬性注入的註解:
1.使用 @Autowired 註解進行自動裝配,不需要指定要注入的物件的value值,自動的根據類名去尋找對應的類來建立物件並進行物件屬性的注入。
2. 使用 @Resource(name=”userDao”),需要指定需要建立的物件的名字,這裡的name對應@Component註解中的value的值,使用這個註解能夠根據我們所指定的物件名準確創建出我們所需要的物件。

UserService.java(示例中使用@Resource註解進行物件屬性的注入)

/**

  • Created by **
    */
    @Service(value = “userService”)
    public class UserService {

    private List list;

    //@Autowired //自動裝配,根據類名稱去找相應的類來建立物件
    @Resource(name=“userDao”) //name屬性值寫使用註解建立物件時的value中的值
    private UserDao userDao;

    public void print(){
    userDao.printInfo();
    }
    }

MyTest.java 測試程式碼:

@Test
public void Test(){

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) context.getBean("userService");
service.print();

}

執行結果:
UserDao中的例項化物件裡的方法執行了

配置檔案和註解混合使用
1.建立物件的操作使用配置檔案的方式實現

2.注入屬性的操作使用註解的方式實現

下面看一下混合使用的例子:

在StudentService中持有BookDao,ComputerDao物件的引用,呼叫StudentService例項化物件中的printDo()方法測試物件是否建立成功。

BookDao.java

/**

  • Created by **
    */
    public class BookDao {

    public void doSomething(){

     System.out.println("讀書");
    

    }

}

ComputerDao.java

/**

  • Created by **
    */
    public class ComputerDao {

    public void doSomething(){

     System.out.println("敲程式碼……");
    

    }

}

StudentService.java

/**

  • Created by **
    */
    public class StudentService {
    @Resource(name = “bookDao”)
    private BookDao bookDao;
    @Resource(name = “computerDao”)
    private ComputerDao computerDao;

    public void printDo(){
    bookDao.doSomething();
    computerDao.doSomething();
    }
    }

applicationContext.xml(Spring 配置檔案)

<context:component-scan base-package=“com.某某包”></context:component-scan>



MyTest.java(測試程式碼)

@Test
public void Test(){

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService service = (StudentService) context.getBean("studentService");
service.printDo();

}

執行結果:
讀書
敲程式碼……