用項目案例解讀Spring IOC容器的控制反轉思想

本篇介紹Spring IOC容器,通過具體的實例詳細地講解IOC概念,徹底理解Spring反轉控制的思想。通過本篇的學習,可以達成如下目標。

● 運用工廠模式設計程序

● 理解JavaBean和POJO對象

● 理解控制反轉思想

● 理解IOC容器

 

1、一個簡單的項目需求

在一個鄉村小學校,一天只上三節課,有三名老師和一個校長。張老師負責教學生語文,王老師教學生數學,李老師教音樂,校長負責安排三位老師每天的上課時間,並提前通知各位老師上課時間,通知方式包括郵件、電話,後續可能會有更多方式。

現在需要編寫一個Java程序實現校長安排老師老師上課時間,並通知到老師,要考慮程序的可擴展性。

2、用工廠模式設計程序

項目中通知老師上課的方式包括郵件、電話,後續可能還有所擴展。雖然通知方式不同,但通知功能是一致的,適合用工廠模式來設計通知功能,後續增加通知方式時,再增加一個通知實現類和修改工廠類代碼就可以了,無需修改其它實現類的代碼。

工廠模式主要用於對功能相似的類進行抽象,抽象出的功能通過接口方式由實現類來實現,然後由工廠類裝配不同的實現類,實現一個工廠生產不同產品的功能。

blob.png

圖1 Notice工廠模式

代碼實現步驟:

(1)定義通知類接口

1

2

3

4

package com.milihua.springprogram.notice;

public interface NoticeInterface {

    void  sendMessage();

}

(2)定義EmailNotice類,實現NoticeInterface接口

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

package com.milihua.springprogram.notice;

import com.milihua.springprogram.entity.Teacher;

public class EmailNotice implements NoticeInterface {

  

    private Teacher teacher;

    String  message;

    

    public String getMessage() {

        return message;

    }

    public void setMessage(String message) {

        this.message = message;

    }

    @Override

    public void sendMessage() {

        // TODO Auto-generated method stub

        teacher.setClasstime(message + "_郵件發送");

    }

    public Teacher getTeacher() {

        return teacher;

    }

    public void setTeacher(Teacher teacher) {

        this.teacher = teacher;

    }

  

}

(3)定義PhoneNotice類,實現NoticeInterface接口

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

package com.milihua.springprogram.notice;

import com.milihua.springprogram.entity.Teacher;

public class PhoneNotice implements NoticeInterface {

  

    private Teacher teacher;

    String  message;

    

    public String getMessage() {

        return message;

    }

  

    public void setMessage(String message) {

        this.message = message;

    }

  

    @Override

    public void sendMessage() {

        // TODO Auto-generated method stub

        teacher.setClasstime(message + "_電話通知");

    }

  

    public Teacher getTeacher() {

        return teacher;

    }

  

    public void setTeacher(Teacher teacher) {

        this.teacher = teacher;

    }

}

(4)定義NoticeFactory類,負責裝配不同實現方式的通知類

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

package com.milihua.springprogram.factory;

import com.milihua.springprogram.notice.EmailNotice;

import com.milihua.springprogram.notice.NoticeInterface;

import com.milihua.springprogram.notice.PhoneNotice;

public class NoticeFactory {

    //使用 getNotic方法獲取通知對象

    public static NoticeInterface getNotic(String noticeType) {

        if (noticeType == null) {

            return null;

        }

        if (noticeType.equalsIgnoreCase("email")) {

            return new EmailNotice();

        else if (noticeType.equalsIgnoreCase("phone")) {

            return new PhoneNotice();

        }

        return null;

    }

}

3、項目的實體類——老師

項目的唯一實體類是老師類,實體類也是POJO類(簡單的Java對象),實體類僅有屬性以及獲取和設置屬性的get和set方法,沒有事務處理方法,這是和Javabean不同的地方。

哪些類適合作爲POJO類呢?項目中用於描述事物本身以及需要數據傳遞和序列化的類。例如,項目中的數據庫表、實體對象、序列化對象等。在本項目案例中,老師類屬於實體對象類。

定義老師類的代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

package com.milihua.springprogram.entity;

public class Teacher {

    private String name;

    private String classtime;

     public String getNotify() {

        return name + "在" + classtime;

    }

  

    public String getName() {

        return name;

    }

  

    public void setName(String name) {

        this.name = name;

    }

  

    public String getClasstime() {

        return classtime;

    }

  

    public void setClasstime(String classtime) {

        this.classtime = classtime;

    }

  

}

4、項目的業務類——校長

業務類也稱爲BO(業務對象),用於處理項目中的業務邏輯。業務邏輯主要用於項目涉及的各類業務操作。例如,在本項目案例中,校長需要安排上課時間,併發送上課時間給老師。在業務對象中,需要組織和協調實體類、組件類、DAO(數據訪問對象)完成整個業務邏輯的處理操作。其中,組件類是JavaBean,是用於處理具體事務的類。例如,在本項目案例中,PhoneNotice、EmailNotice類就是組件類,用於處理髮送通知事務。

定義校長類的代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

package com.milihua.springprogram.business;

import com.milihua.springprogram.entity.Teacher;

import com.milihua.springprogram.factory.NoticeFactory;

import com.milihua.springprogram.notice.EmailNotice;

import com.milihua.springprogram.notice.PhoneNotice;

public class Principal {

    /*

      校長類,負責裝配產品,關聯老師類和通知類

    **/

    public String  notifyTeacher()

    {

        StringBuilder  notifyReturn = new StringBuilder();

        //獲取郵件通知類

        EmailNotice  eamilNotice = (EmailNotice)NoticeFactory.getNotic("email");

        //獲取電話通知類

        PhoneNotice  phoneNotice = (PhoneNotice)NoticeFactory.getNotic("phone");

        /*

          發送通知給張老師

        **/

        //

        Teacher teacherZhang = new Teacher();

        teacherZhang.setName("張老師");

        //注入老師類到郵件通知類

        eamilNotice.setTeacher(teacherZhang);

        //注入消息到郵件通知類

        eamilNotice.setMessage("8:45上語文課");

        //發送消息給張老師

        eamilNotice.sendMessage();

        notifyReturn.append(teacherZhang.getNotify() + "\n");

        

        /*

          發送通知給王老師

        **/

        //

        Teacher teacherWang = new Teacher();

        teacherWang.setName("王老師");

        //注入老師類到電話通知類

        phoneNotice.setTeacher(teacherWang);

        //注入消息到電話通知類

        phoneNotice.setMessage("9:50上數學課");

        //發送消息給張老師

        phoneNotice.sendMessage();

        notifyReturn.append(teacherWang.getNotify() + "\n");

        

        /*

          發送通知給李老師

        **/

        //

        Teacher teacherLi = new Teacher();

        teacherLi.setName("李老師");

        //注入老師類到電話通知類

        phoneNotice.setTeacher(teacherLi);

        //注入消息到電話通知類

        phoneNotice.setMessage("13:50上課");

        //發送消息給張老師

        phoneNotice.sendMessage();

        notifyReturn.append(teacherLi.getNotify() + "\n");

  

        return  notifyReturn.toString();

        

    }

}

5、項目技術架構存在的問題

blob.png

 

圖2 項目技術架構圖

項目技術架構主要由javaBean組件、業務邏輯處理、POJO(實體)、前端四部分組成。JavaBean組件實現通知發送,應用工廠模式便於組件擴展。業務邏輯處理部分調用NoticeFactory創建通知組件和Teacher類,並將Teacher類實例和消息注入到組件,最後調用組件發送消息。

從技術架構圖可以看出,NoticeFactory(組件工廠)負責通知組件的創建,Principal(業務類)調用NoticeFactory獲取組件,並將Teacher類實例和消息注入到組件。Principal是主要控制類,控制了組件的創建和組件屬性的注入。

Principal類對組件的較強控制,對程序的擴展性和易維護性顯然是不利的。例如,當程序需要增加微信通知方式,且老師都希望用微信通知時,麻煩就來了,需要修改大量程序代碼。再如,老師的上課時間可能每週或每天都有變化,把時間安排寫在程序代碼中顯然是不妥的,應該寫在程序外面,由外面對通知組件的屬性進行注入。

要解決上面的問題,就需要弱化Principal類對組件的控制權,將組件的創建和屬性的注入(圖2紅色點劃線指示的功能)交給第三方託管,這個第三方就是Spring框架的IOC容器,控制反轉就是將Principal類對組件的控制權移交給IOC容器。

6、Spring IOC容器的控制反轉

Spring IOC容器是框架的核心,IOC是控制反轉的意思,可以用來降低程序代碼之間的耦合度。把強耦合的代碼依賴從代碼中移出去,放到統一的XML配置文件中,將程序對組件的主要控制權交給IOC,由IOC統一加載和管理。例如,可以把本案例中的JavaBean組件的創建、實體類的創建、以及JavaBean組件的屬性注入等代碼從Principal類移出,放入到Spring的XML配置文件中。這樣就實現了Principal類與JavaBean組件的代碼解耦,也解決了項目案例技術架構所存在的問題。

Spring配置文件代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

<?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"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:p="http://www.springframework.org/schema/p"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

                           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd

                           http://www.springframework.org/schema/context

                           http://www.springframework.org/schema/context/spring-context-3.2.xsd

                           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

    <bean id="teacherZhang" class="com.milihua.springprogram.entity.Teacher">

        <property name="name" value="張老師"></property>

    </bean>

    <bean id="teacherWang" class="com.milihua.springprogram.entity.Teacher">

        <property name="name" value="王老師"></property>

    </bean>

    <bean id="teacherLi" class="com.milihua.springprogram.entity.Teacher">

        <property name="name" value="李老師"></property>

    </bean>

    <bean id="eamilNoticeZhang" class="com.milihua.springprogram.notice.EmailNotice" p:teacher-ref="teacherZhang">

        <property name="message" value="8:45上語文課"></property>

    </bean>

    <bean id="phoneNoticeWang" class="com.milihua.springprogram.notice.PhoneNotice" p:teacher-ref="teacherWang">

        <property name="message" value="9:50上數學課"></property>

    </bean>

    <bean id="phoneNoticeLi" class="com.milihua.springprogram.notice.PhoneNotice" p:teacher-ref="teacherLi">

        <property name="message" value="13:50上音樂課"></property>

    </bean>

</beans>

定義新的業務類,用於從IOC上下文環境中讀取組件和POJO實例,代碼如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

package com.milihua.springprogram.business;

import org.springframework.context.ApplicationContext;

import com.milihua.springprogram.entity.Teacher;

import com.milihua.springprogram.notice.EmailNotice;

import com.milihua.springprogram.notice.PhoneNotice;

public class IocPrincipal {

    /*

      基於Spring IOC容器的校長類,從配置文件獲取通知組件

    **/

    public String  notifyTeacher(ApplicationContext ctx)

    {

        StringBuilder  notifyReturn = new StringBuilder();

  

        //從容器中獲取通知張老師的組件

        EmailNotice  noticeZhang = ctx.getBean("eamilNoticeZhang",EmailNotice.class);

        noticeZhang.sendMessage();

        //從容器中獲取張老師的實例

        Teacher  teacherZhang = ctx.getBean("teacherZhang",Teacher.class);

        notifyReturn.append(teacherZhang.getNotify() + "<p>");

  

        //從容器中獲取通知王老師的組件

        PhoneNotice  phoneNoticWang = ctx.getBean("phoneNoticeWang",PhoneNotice.class);

        phoneNoticWang.sendMessage();

        //從容器中獲取王老師的實例

        Teacher  teacherWang = ctx.getBean("teacherWang",Teacher.class);

        notifyReturn.append(teacherWang.getNotify() + "<p>");

  

        //從容器中獲取通知李老師的組件

        PhoneNotice  phoneNoticLi = ctx.getBean("phoneNoticeLi",PhoneNotice.class);

        phoneNoticLi.sendMessage();

        //從容器中獲取王老師的實例

        Teacher  teacherLi = ctx.getBean("teacherLi",Teacher.class);

        notifyReturn.append(teacherLi.getNotify() + "<p>");

  

        return  notifyReturn.toString();

    }

}

課程小結

Spring IOC容器的核心是把程序業務代碼與事物(組件、POJO類)代碼進行分離,程序有關事物的創建、屬性和依賴對象的注入、以及生命週期交由容器進行加載和管理。業務代碼只需從容器中獲取組件或POJO實例對象即可,無需再考慮組件之間、組件與POJO之間的依賴關係以及屬性的注入。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章