分層開發 Web 應用程序
- 1.應用程序分層開發模式:MVC
- 1.1 了解 MVC 模式
- 1.2 MVC 和三層架構的關系
- 2.視圖技術 Thymeleaf
- 3.使用控制器
- 3.1 常用注解
- 3.1.1 @Controller
- 3.1.2 @RestController
- 3.1.3 @RequestMapping
- 3.1.4 @PathVariable
- 3.2 將 URL 映射到方法
- 3.3 在方法中使用參數
- 3.3.1 獲取路徑中的值
- 3.3.2 獲取路徑中的參數
- 3.3.3 通過 Bean 接收 HTTP 提交的對象
- 3.3.4 用注解 @ModelAttribute 獲取參數
- 3.3.5 通過 HttpServletRequest 接收參數
- 3.3.6 用 @RequestParam 綁定入參
- 3.3.7 用 @RequestBody 接收 JSON 數據
- 3.3.8 上傳文件 MultipartFile
- 3.3.9 上傳圖片
- 4.理解模型
- 5.實現 MVC 模式的 Web 應用程序(實戰)
- 5.1 添加依賴
- 5.2 創建實體模型
- 5.3 創建控制器
- 5.4 創建用于展示的視圖
1.應用程序分層開發模式:MVC
1.1 了解 MVC 模式
Spring Boot 開發 Web 應用程序主要使用 MVC 模式。MVC 是 Model(模型)、View(視圖)、Controller(控制器)的簡寫。
Model
:是 Java 的實體 Bean,代表存取數據的對象或 POJO(Plain Ordinary JavaObiects
,簡單的 Java 對象),也可以帶有邏輯。其作用是在內存中暫時存儲數據,并在數據變化時更新控制器(如果要持久化,則需要把它寫入數據庫或者磁盤文件中)。View
:主要用來解析、處理、顯示內容,并進行模板的渲染。Controller
:主要用來處理視圖中的響應。它決定如何調用 Model(模型)的實體 Bean、如何調用業務層的數據增加、刪除、修改和查詢等業務操作,以及如何將結果返給視圖進行渲染。建議在控制器中盡量不放業務邏輯代碼。
這樣分層的好處是:將應用程序的用戶界面和業務邏輯分離,使得代碼具備良好的可擴展性可復用性、可維護性和靈活性。
如果不想使用 MVC 開發模式也是可以的,MVC 只是一個非常合理的規范。MVC 的關系如下圖所示。
如果讀者對 MVC 開發模式理解得不深入,那么往往會以為用戶通過瀏覽器訪問 MVC 模型的頁面就是訪問視圖(View)。實際上,它并不是直接訪問視圖,而是訪問 DispatcherServlet
處理映射和調用視圖渲染,然后返回給用戶的數據。
在整個 Spring MVC 框架中,DispatcherServlet 處于核心位置,繼承自 HttpServlet。它負責協調和組織不同組件,以完成請求處理并返回響應工作。
整個工程流程如下:
- 客戶端(用戶)發出的請求由 Tomcat(服務器)接收,然后 Tomcat 將請求轉交給 DispatcherServlet 處理。
- DispatcherServlet 匹配控制器中配置的映射路徑,進行下一步處理。
- ViewResolver 將 ModelAndView 或 Exception 解析成 View。然后 View 會調用
render()
方法,并根據 ModelAndView 中的數據渲染出頁面。
在 MVC 開發模式中,容易混淆的還有 Model,它往往會被認為是業務邏輯層或 DAO 層。這種理解并不能說是錯誤的,但并不是嚴格意義上的 MVC 模式。
1.2 MVC 和三層架構的關系
🚀 可參考博主寫的一篇博文《MVC框架和經典三層結構》
三層架構,就是將整個應用程序劃分為 表現層(UI)、業務邏輯層(Service)、數據訪問層(DAO / Repository)。
- 表現層:用于展示界面。主要對用戶的請求進行接收,以及進行數據的返回。它為客戶端(用戶)提供應用程序的訪問接口(界面)。
- 業務邏輯層:是三層架構的服務層,負責業務邏輯處理,主要是調用 DAO 層對數據進行增加、刪除、修改和查詢等操作。
- 數據訪問層:與數據庫進行交互的持久層,被 Service 調用。在 Spring Data JPA 中由 Hibernate 來實現。
- Repository 和 DAO 層一樣,都可以進行數據的增加、刪除、修改和查詢。它們相當于倉庫管理員,執行進 / 出貨操作。
- DAO 層的工作是 存取對象。Repository 層的工作是 存取和管理對象。
- 簡單理解就是:Repository = 管理對象(對象緩存和在 Repository 的狀態)+ DAO。
嚴格地說,MVC 是三層架構中的 UI 層。通過 MVC 把三層架構中的 UI 層又進行了分層。
由此可見,三層架構是基于業務邏輯或功能來劃分的,而 MVC 是基于頁面或功能來劃分的。
2.視圖技術 Thymeleaf
Spring Boot 主要支持 Thymeleaf、Freemarker、Mustache、Groovy Templates 等模板引擎。
Thymeleaf 可以輕易地與 Spring MVC 等 Web 框架進行集成。
Thymeleaf 語法并不會破壞文檔的結構,所以 Thymeleaf 模板依然是有效的 HTML 文檔。模板還可以被用作工作原型,Thymeleaf 會在運行期內替換掉靜態值。它的模板文件能直接在瀏覽器中打開并正確顯示頁面,而不需要啟動整個 Web 應用程序。
現代軟件開發基本遵循前后端分離模式,會有專門的前端框架,如 Vue、React 等,本文對于 Thymeleaf 不再展開,有興趣可以自行了解。
Thymeleaf 解決了前端開發人員要和后端開發人員配置一樣環境的尷尬和低效。它通過屬性進行模板渲染,不需要引入不能被瀏覽器識別的新的標簽。頁面直接作為 HTML 文件,用瀏覽器打開頁面即可看到最終的效果,可以降低前后端人員的溝通成本。
要使用 Thymeleaf,首先需要引入依賴。直接在 pom.xml
文件中加入以下依賴即可。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
3.使用控制器
在 Spring MVC 中,控制器(Controller)負責處理由 DispatcherServlet 接收并分發過來的請求。它把用戶請求的數據通過業務處理層封裝成一個 Model,然后再把該 Model 返回給對應的 View 進行展示。
Controller 無須繼承特定的類或實現特定的接口。只需使用 @Controller(@RestController)來標記一個控制器,然后用注解 @RequestMapping 定義 URL 請求和 Controller 方法之間的映射,這樣 Controller 就能被外界訪問到。它可以包含多個請求處理方法。
3.1 常用注解
Spring MVC 控制器中常使用的注解有如下幾種。
3.1.1 @Controller
@Controller 標記在類上。使用 @Controller 標記的類表示是 Spring MVC 的 Controller 對象。分發處理器將會掃描使用了該注解的類,并檢測其中的方法是否使用了注解 @RequestMapping。注解 @Controler 只是定義了一個控制器類,使用了注解 @RequestMapping 的方法才是真正處理請求的處理器,完成映射關系。
3.1.2 @RestController
@RestController 是 Spring 4.0 之后才有的注解。它等價于原來的注解 @Controller 加上注解 @ResponseBody 的功能,直接返回字符串。用它來標注 Rest 風格的控制器類。
3.1.3 @RequestMapping
它用來處理請求地址映射的注解,可用在類或方法上。如果用在類上,則表示類中的所有響應請求的方法都以該地址作為父路徑。
RequestMapping 注解有 6 個屬性。
value
:指定請求的地址。method
:指定請求的 method 類型,例如GET
、HEAD
、POST
、PUT
、PATCH
、DELETE
、OPTIONS
、TRACE
。consumes
:消費消息,指定處理請求的提交內容類型(Content-Type
),例如application/json
、text/html
。produces
:生產消息,指定返回的內容類型。僅當 request 請求頭中的Accept
類。含該指定類型時才返回。params
:指定 request 中必須包含某些參數值才讓該方法處理請求。headers
:指定 request 中必須包含某些指定的header
值才能讓該方法處理請求。
3.1.4 @PathVariable
將請求 URL 中的模板變量映射到功能處理方法的參數上,即獲取 URI 中的變量作為參數。以下代碼先通過獲取路徑中的 id
值,再根據獲取的 id
值來獲取數據庫中產品的對象。
@RequestMapping(value="/product/{id}",method = RequestMethod.GET)
public String getProduct(@PathVariable("id") String id){Product product = productRepository.findById(id);System.out.println("產品 id :" + product.getId());System.out.println("產品名稱 :" + product.getTitle());return "product/show"
}
3.2 將 URL 映射到方法
將 URL(統一資源定位符)映射到方法,是通過注解 @RequestMapping 來處理的。URL 映射其實就是用控制器定義訪問的 URL 路徑。用戶通過輸入路徑來訪問某個方法。
注解 @RequestMapping 可以在類和方法上使用。如在類上使用,則可以窄化映射。如以下代碼:
@RestController
@RequestMapping("news")
Public class NewsController {// GET方式@RequestMapping(value = "/", method = RequestMethod.GET)public void add(){}// POST方式@RequestMapping(value = "/", method = RequestMethod.POST)public void save(){}
}
訪問 add
和 save
方法都需要加上 news
級目錄,如下所示。
- GET 方式訪問
add
方法的路徑:http://localhost:8080/news/
。 - POST 方式訪問
save
方法的路徑:http://localhost:8080/news/
。
這里的路徑是一樣的,但并不錯誤,因為資源路徑一樣,只是 HTTP 方法不一樣。
Spring Boot 還提供了更簡潔的編寫 URL 映射的方法,如 @GetMapping("/")
,它等價于 @RequestMapping(value="", method = RequestMethod.GET)
。除此之外還有下面的寫法。
- @GetMapping:處理 GET 請求。
- @PostMapping:處理 POST請求
- @DeleteMapping:處理刪除請求。
- @PutMapping:處理修改請求。
3.3 在方法中使用參數
對于程序開發的初學者來說,比較困難的可能并不是理論,而是程序的具體實現。比如,如何把最簡單的程序運行起來,如何實現參數的接收和發送等。本節講解的就是如何在控制器的方法中使用參數。
3.3.1 獲取路徑中的值
/**
*Description:根據id 獲取文章對象
*/
@GetMapping("article/{id}")
public ModelAndView getArticle(@PathVariable("id") Integer id) {Article articles = articleRepository.findById(id);ModelAndView mav = new ModelAndView("article/show");mav.addObject("article", articles);return mav;
}
在訪問 http://localhostarticle/123
時,程序會自動將 URL 中的模板變量 {id}
綁定到通過 @PathVariable 注解的同名參數上,即 “程序獲取路徑中 123 的值”。
3.3.2 獲取路徑中的參數
對于路徑中的參數獲取,可以寫入方法的形參中。下面代碼是獲取參數 username
的值。
@RequestMapping("/addUser")
public String addUser(String username) {
}
這里的參數和上面所講的獲取路徑值是不一樣的,比如 http://localhostuser/?username=longzhiran
,它是由 =
隔開的。
3.3.3 通過 Bean 接收 HTTP 提交的對象
可以通過 Bean 獲取 HTTP 提交的對象,如以下代碼:
public String addUser(UserModel user)
3.3.4 用注解 @ModelAttribute 獲取參數
用于從 Model、Form 或 URL 請求參數中獲取屬性值,如以下代碼:
@RequestMapping(value="addUser",method=RequestMethod.POST)
public String addUser(@ModelAttribute("user") UserModel user)
3.3.5 通過 HttpServletRequest 接收參數
可以通過 HttpServletRequest 接收參數,如以下代碼:
@RequestMapping("/addUser")
public String addUser(HttpServletRequest request) {System.out.println("name:" + request.GETParameter("username"));return "/index";
}
3.3.6 用 @RequestParam 綁定入參
用法如以下代碼:
@RequestParam(value="username",required=false)
當請求參數不存在時會有異常發生,可以通過設置屬性 required=false
來解決。
3.3.7 用 @RequestBody 接收 JSON 數據
可以通過 @RequestBody 注解來接收 JSON 數據,如以下代碼:
@RequestMapping(value = "adduser", method = {RequestMethod.POST})
@ResponseBody
public void saveUser(@RequestBody List<User> users) {userService.Save(users);
}
3.3.8 上傳文件 MultipartFile
通過 @RequestParam 獲取文件,如以下代碼:
public String singleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {if (file.isEmpty()) {redirectAttributes.addFlashAttribute("message", "請選擇文件");return "redirect:uploadStatus";}try {byte[] bytes = file.getBytes();Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());Files.write(path, bytes);redirectAttributes.addFlashAttribute("message", "成功上傳 " + file.getOriginalFilename() + "");} catch (IOException e) {e.printStackTrace();}return "redirect:/uploadStatus";
}
出于安全考慮,在生產環境中需要判斷文件的類型,一般不允許上傳 .exe
等格式的可執行文件。
3.3.9 上傳圖片
很多人在整合富文本編輯器時不容易成功,特別是在不同版本要求返回的數據類型不一樣時,而網絡上的資料很多是不帶版本號或是過時的。
CKEditor 4.10.1 之后的版本只有返回的是 JSON 格式的數據才能成功,如 [{"uploaded":1, "fileName":"fileName", "url"="", "message":"上傳成功"}]
。
4.理解模型
模型(Model)在 MVC 模式中是實體 Bean,代表一個存取數據的對象或 POJO(Plain OrdinaryJava Object
)。它可以帶有邏輯,其作用是暫時存儲數據(存在內存中),以便進行持久化(存入數據庫或寫入文件),以及在數據變化時更新控制器。簡單地理解是:Model 是數據庫表對應的實體。
以下代碼定義了一個用戶實體 Bean(Model)。
@Getter
@Setter
public class User {//定義idprivate long id;//定義用戶名private String name;//定義用戶年齡private int age;
}
可以通過常用的 Getter
、Setter
封裝來對實體 Bean 進行賦值、獲值操作,并在其中添加邏輯代碼。
5.實現 MVC 模式的 Web 應用程序(實戰)
5.1 添加依賴
MVC 模式的 Web 應用程序需要依賴 Spring Boot 的 spring-boot-starter-web
(Starter)還需要添加模板的依賴。具體依賴如以下代碼:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
5.2 創建實體模型
創建實體 Bean,用于和 Controller 進行數據交互。
package com.example.demo.model;
import lombok.Data;@Data
public class User {//定義idprivate long id;//定義用戶名private String name;//定義用戶年齡private int age;
}
5.3 創建控制器
控制器層用來實例化實體 Bean(Model),并傳值給視圖模版。
package com.example.demo.controller;
import com.example.demo.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;@Controller
public class MVCDemoController {//映射URL地址@GetMapping("/mvcdemo")public ModelAndView hello() {//實例化對象User user = new User();user.setName("pipi");user.setAge(26);//定義mvc中的視圖模板ModelAndView modelAndView = new ModelAndView("mvcdemo");//傳遞user實體對象給視圖modelAndView.addObject("user",user);return modelAndView;}
}
5.4 創建用于展示的視圖
以下代碼用于獲取控制器中傳遞的實體 Bean,并進行渲染。
<!DOCTYPE html>
<!--thymeleaf模板支持-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body><div><!-- 顯示由控制器傳遞過來的實體user的值--><div th:text="${user.name}"></div><div th:text="${user.age}"></div></div>
</body>
</html>
啟動項目,然后訪問 http://localhost:8080/mvcdemo
。