本篇介紹Spring IOC容器,通過具體的實例詳細地講解IOC概念,徹底理解Spring反轉控制的思想。通過本篇的學習,可以達成如下目標。
● 運用工廠模式設計程序
● 理解JavaBean和POJO對象
● 理解控制反轉思想
● 理解IOC容器
1、一個簡單的項目需求
在一個鄉村小學校,一天只上三節課,有三名老師和一個校長。張老師負責教學生語文,王老師教學生數學,李老師教音樂,校長負責安排三位老師每天的上課時間,並提前通知各位老師上課時間,通知方式包括郵件、電話,後續可能會有更多方式。
現在需要編寫一個Java程序實現校長安排老師老師上課時間,並通知到老師,要考慮程序的可擴展性。
2、用工廠模式設計程序
項目中通知老師上課的方式包括郵件、電話,後續可能還有所擴展。雖然通知方式不同,但通知功能是一致的,適合用工廠模式來設計通知功能,後續增加通知方式時,再增加一個通知實現類和修改工廠類代碼就可以了,無需修改其它實現類的代碼。
工廠模式主要用於對功能相似的類進行抽象,抽象出的功能通過接口方式由實現類來實現,然後由工廠類裝配不同的實現類,實現一個工廠生產不同產品的功能。
圖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、項目技術架構存在的問題
圖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之間的依賴關係以及屬性的注入。