📢 本文由 gemini-2.5-flash 翻譯
範例引入
取得員工資料,回傳統一回應結果,並在頁面呈現顯示
首先需要匯入 dom4j 依賴,用於解析 XML 檔案
1
2
3
4
5
| <dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
|
匯入解析 XML 的工具類別 XMLParserUtils、對應的實體類別 Emp、XML 檔案 emp.xml
匯入靜態網頁檔案,放在 resources/static
SpringBoot 專案的靜態資源 (H5 + CSS + JS 等前端資源) 預設存放目錄為 clsspath:/static, classpath:/public, clsspath:/resources
對應 Maven 來說 classpath 為 src/main/resources
編寫 Controller 程式,處理請求,回應資料 (此範例程式碼省略,使用下列三層架構)
三層架構
上例 Emp 程式將資料存取、處理邏輯和接收回應請求放在一個 Controller 裡,使得復用性差、難以維護,為此需要將其分開以滿足單一職責原則,三層架構使得程式碼復用性強、便於維護、有利於擴展
三層架構分為 Controller、Service 與 Dao
- Controller:控制層,接收前端送出的請求,對請求進行處理,並回應資料
- Service:業務邏輯層,處理具體的業務邏輯
- Dao:資料存取層 (Data Access Object) 或持久層,負責資料存取操作,CRUD
瀏覽器發出請求 -> Controller 接收請求、回應資料 -> Service 邏輯處理 -> Dao 資料存取
上例 Emp 程式碼可以優化成
- Controller
1
2
3
4
5
6
7
8
9
| @RestController
public class EmpController {
@RequestMapping("/listEmp")
public Result listEmp(){
EmpService empServiceA = new EmpServiceA();
List<Emp> empList = empServiceA.listEmp();
return Result.success(empList);
}
}
|
- Service
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
| // interface
public interface EmpService {
public List<Emp> listEmp();
}
public class EmpServiceA implements EmpService {
private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("講師");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就業指導");
}
});
return empList;
}
}
|
- Dao
1
2
3
4
5
6
7
8
9
10
11
12
| // interface
public interface EmpDao {
public List<Emp> listEmp();
}
public class EmpDaoA implements EmpDao {
public List<Emp> listEmp(){
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
|
分層解耦
內聚:軟體中各個功能模組內部的功能聯繫
耦合:衡量軟體中各個層/模組之間的依賴、關聯的程度
軟體設計原則:高內聚低耦合
具體請見
https://blog.yexca.net/archives/145
例如上例三層架構,Controller 與 Service 耦合,Service 與 Dao 耦合
在 Controller 中直接建立 Service 物件 EmpService empServiceA = new EmpServiceA(); 使用 A,若變更為 B 則需要修改 Controller,為了更換 Service 而不修改 Controller,可以建立一個容器,使得 Controller 從容器取得物件 (依賴注入) ,Service 把服務注入容器 (控制反轉)
- 控制反轉:Inverse Of Control,簡稱 IOC。物件的建立控制權由程式自身轉移到外部 (容器)
- 依賴注入:Dependency Injection,簡稱 DI。容器為應用程式提供執行時所需的資源
- Bean 物件:IOC 容器中建立、管理的物件
IOC
要把某個物件交給 IOC 容器管理,需要在對應類別上加上下列其中一個註解
| 註解 | 說明 | 位置 |
|---|
@Component | 宣告 Bean 的基礎註解 | 不屬於下列三種類別時使用此註解 (工具類別) |
@Controller | @Component 的衍生註解 | 標註在控制器類別上 |
@Service | @Component 的衍生註解 | 標註在業務類別上 |
@Repository | @Component 的衍生註解 | 標註在資料存取類別上 (MyBatis 整合時,較少用) |
- 宣告 Bean 時,可以透過 value 屬性指定名稱,若無,預設類別名稱首字母小寫
- 在 SpringBoot 整合網頁開發中,宣告控制器 Bean 只能用
@Controller
Bean 元件掃描
上面宣告 Bean 的四個註解,若要生效,還需被元件掃描註解 @ComponentScan 掃描
此註解實際上已包含在啟動類別宣告註解 @SpringBootApplication 中,預設的掃描範圍是啟動類別所在的套件及其子套件
透過 value 或 basePackage 屬性指定掃描範圍
1
| @ComponentScan({"dao","net.yexca"})
|
範例
上例中
- Controller
由於 @RestController 註解已包含 @Controller,無須修改
- Service
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
| // interface
public interface EmpService {
public List<Emp> listEmp();
}
// 在實作類別新增
@Service // 將當前類別交給IOC容器管理,成為IOC容器中的Bean
public class EmpServiceA implements EmpService {
private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("講師");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就業指導");
}
});
return empList;
}
}
|
- Dao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // interface
public interface EmpDao {
public List<Emp> listEmp();
}
// 在實作類別新增
@Repository // 將當前類別交給IOC容器管理,成為IOC容器中的Bean
public class EmpDaoA implements EmpDao {
public List<Emp> listEmp(){
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
|
DI
使用 @Autowired 註解可以注入依賴,不過預設是按照型別進行,如果存在多個相同型別的 Bean 會報錯
將以上範例加上 DI (修改的程式碼將被註解)
- Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| @RestController
public class EmpController {
@Autowired // 執行時,IOC容器會提供該型別的Bean物件,並指派給該變數 -- 依賴注入
private EmpService empService;
@RequestMapping("/listEmp")
public Result listEmp(){
//EmpService empServiceA = new EmpServiceA();
//List<Emp> empList = empServiceA.listEmp();
List<Emp> empList = empService.listEmp();
return Result.success(empList);
}
}
|
- Service
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
| // interface
public interface EmpService {
public List<Emp> listEmp();
}
// 在實作類別新增
@Service // 將當前類別交給IOC容器管理,成為IOC容器中的Bean
public class EmpServiceA implements EmpService {
@Autowired // 執行時,IOC容器會提供該型別的Bean物件,並指派給該變數 -- 依賴注入
private EmpDao empDao;
// private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("講師");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就業指導");
}
});
return empList;
}
}
|
- Dao
沒有資料注入,無須修改
@Primary
若將 Service 新增一個實作類別 EmpServiceB 且也使用了 @Autowired,程式會報錯,可以透過 @Primary 註解指定使用哪一個
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
63
64
| // interface
public interface EmpService {
public List<Emp> listEmp();
}
// 在實作類別新增
@Service // 將當前類別交給IOC容器管理,成為IOC容器中的Bean
public class EmpServiceA implements EmpService {
@Autowired // 執行時,IOC容器會提供該型別的Bean物件,並指派給該變數 -- 依賴注入
private EmpDao empDao;
// private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("講師");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就業指導");
}
});
return empList;
}
}
@Primary // 使用此 Bean
@Service // 將當前類別交給IOC容器管理,成為IOC容器中的Bean
public class EmpServiceB implements EmpService {
@Autowired // 執行時,IOC容器會提供該型別的Bean物件,並指派給該變數 -- 依賴注入
private EmpDao empDao;
// private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("講師");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就業指導");
}
});
return empList;
}
}
|
@Qualifier
指定使用哪個 Bean,在 Controller 使用,將上方 @Primary 註解取消
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| @RestController
public class EmpController {
@Qualifier("empServiceA") // 使用 Bean empServiceA
@Autowired // 執行時,IOC容器會提供該型別的Bean物件,並指派給該變數 -- 依賴注入
private EmpService empService;
@RequestMapping("/listEmp")
public Result listEmp(){
//EmpService empServiceA = new EmpServiceA();
//List<Emp> empList = empServiceA.listEmp();
List<Emp> empList = empService.listEmp();
return Result.success(empList);
}
}
|
@Resource
與 @Qualifier 類似,不過不使用 @Autowired
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| @RestController
public class EmpController {
// @Qualifier("empServiceA") // 使用 Bean empServiceA
// @Autowired
@Resource(name = "empServiceB") // 使用 Bean empServiceB
private EmpService empService;
@RequestMapping("/listEmp")
public Result listEmp(){
//EmpService empServiceA = new EmpServiceA();
//List<Emp> empList = empServiceA.listEmp();
List<Emp> empList = empService.listEmp();
return Result.success(empList);
}
}
|
@Autowired 與 @Resource 差異
@Autowired 是 Spring 框架提供的註解,而 @Resource 是 JDK 提供的註解
@Autowired 預設是按照型別注入,而 @Resource 預設是按照名稱注入