《Spring Boot實戰》之(zhī)二:開發第一個(gè)應用程序
發表時(shí)間:2019-7-5
發布人(rén):融晨科技
浏覽次數:41
本章使用Spring Boot實現一個(gè)簡單的(de)例子(zǐ),主要(yào / yāo)包括兩個(gè)知識點:
- 使用Spring Boot的(de)起步依賴
- 使用Spring Boot自動進行Spring的(de)配置
2.1 運用Spring Boot
下面将使用Spring Boot創建一個(gè)簡單的(de)閱讀列表程序,用戶可以(yǐ)輸入想讀的(de) 圖書信息,查看列表,删除已經讀過的(de)書。
首先,使用Spring Boot初始化項目,從技術角度來(lái)看,我們要(yào / yāo)用Spring MVC來(lái)處理Web請求,用Thymeleaf來(lái)定義Web視圖,用Spring Data JPA來(lái)把閱讀列表持久化到(dào)數據庫裏,這(zhè)裏先用嵌入式的(de)H2數據庫。我們使用Initializr的(de)Web界面進行初始化,在(zài)添加依賴的(de)地(dì / de)方添加Web、Thymeleaf和(hé / huò)JPA。還要(yào / yāo)添加H2,以(yǐ)在(zài)開發應用程序時(shí)使用内嵌式數據庫。
初始化後的(de)文件結構:
2.1.1 ReadListApplication.java
首先看ReadListApplication.java,ReadListApplication在(zài)Spring Boot應用程序裏有兩個(gè)作用:配置和(hé / huò)啓動引導。首先, 這(zhè)是(shì)主要(yào / yāo)的(de)Spring配置類。雖然Spring Boot的(de)自動配置免除了(le/liǎo)很多Spring配置,但你還需要(yào / yāo)進行 少量配置來(lái)啓用自動配置。@SpringBootApplication開啓了(le/liǎo)Spring的(de)組件掃描和(hé / huò)Spring Boot的(de)自動配置功能。實際 上(shàng),@SpringBootApplication将三個(gè)有用的(de)注解組合在(zài)了(le/liǎo)一起。
- Spring的(de)@Configuration:标明該類使用Spring基于(yú)Java的(de)配置。
- Spring的(de)@ComponentScan:啓用組件掃描,這(zhè)樣你寫的(de)Web控制器類和(hé / huò)其他(tā)組件才能被 自動發現并注冊爲(wéi / wèi)Spring應用程序上(shàng)下文裏的(de)Bean。本章稍後會寫一個(gè)簡單的(de)Spring MVC 控制器,使用@Controller進行注解,這(zhè)樣組件掃描才能找到(dào)它。
- Spring Boot 的(de) @EnableAutoConfiguration :這(zhè)個(gè)注解也(yě)可以(yǐ)稱爲(wéi / wèi) @Abracadabra,就(jiù)是(shì)這(zhè)一行配置開啓了(le/liǎo)Spring Boot自動配置的(de)魔力,讓你不(bù)用再寫成篇的(de)配置了(le/liǎo)。
如前所述,ReadListApplication還有一個(gè)作用是(shì)啓動引導,運行Spring Boot應用程序有兩種方式:一是(shì)通過傳統的(de)WAR包形式;二是(shì)通過可執行jar包的(de)方式。這(zhè)裏向SpringApplication.run()傳遞了(le/liǎo)一個(gè)ReadingListApplication類的(de)引用,還有命令行參數,通過這(zhè)些東西啓動應用程序。
package com.yuan.readlist;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication //開啓組件掃描和(hé / huò)自動配置
public class ReadlistApplication {
public static void main(String[] args) {
SpringApplication.run(ReadlistApplication.class, args); //負責啓動引導應用程序
}
}
2.1.2 ReadlistApplicationTests.java
ReadingListApplicationTests不(bù)僅提供了(le/liǎo)一個(gè)用于(yú)測試Spring Boot項目的(de)框架,它還是(shì)一個(gè)例子(zǐ),告訴你如何爲(wéi / wèi)Spring Boot應用程序編寫測試。
contextLoads()這(zhè)個(gè)空方法足以(yǐ)證明應用程序上(shàng)下文的(de)加載沒有問題,如果ReadingListApplication裏定義的(de)配置是(shì)好的(de),就(jiù)能通過測試。如果有問題,測試就(jiù)會失敗。
當然,現在(zài)這(zhè)隻是(shì)一個(gè)新的(de)應用程序,你還會添加自己的(de)測試。但contextLoads()方法是(shì)個(gè)良好的(de)開端,此刻可以(yǐ)驗證應用程序提供的(de)各種功能。第4章會更詳細地(dì / de)讨論如何測試Spring Boot應用程序。
package com.yuan.readlist;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ReadlistApplicationTests {
@Test
public void contextLoads() {
}
}
2.1.3 application.properties
Initializr爲(wéi / wèi)你生成的(de)application.properties文件是(shì)一個(gè)空文件,該文件可以(yǐ)用來(lái)調整Spring Boot的(de)自動配置。
要(yào / yāo)注意的(de)是(shì),你完全不(bù)用告訴Spring Boot爲(wéi / wèi)你加載application.properties,隻要(yào / yāo)它存在(zài)就(jiù)會被加載,Spring和(hé / huò)應用程序代碼都能獲取其中的(de)屬性,當然如果它不(bù)存在(zài)也(yě)完全沒有問題,Spring Boot會使用其默認配置。
2.1.4 運行項目
構建一個(gè)Spring Boot應用程序和(hé / huò)構建其他(tā)Java應用程序的(de)過程類似,可以(yǐ)選擇Gradle或Maven作爲(wéi / wèi)構建工具。描述構建說(shuō)明文件的(de)方法和(hé / huò)描述非Spring Boot應用程序的(de)方法也(yě)相似。Spring Boot爲(wéi / wèi)Gradle和(hé / huò)Maven提供了(le/liǎo)構建插件,以(yǐ)便輔助構建Spring Boot項目。
将項目導入到(dào)MyEclipse中,執行ReadlistApplication的(de)main方法,即可運行項目,然後浏覽器訪問127.0.0.1:8080,由于(yú)還沒有寫控制器類,會打開一個(gè)404的(de)錯誤頁面。
2.2 使用起步依賴
指定基于(yú)功能的(de)依賴
通過使用Spring Boot的(de)起步依賴,可以(yǐ)使我們不(bù)用指定具體的(de)版本号,甚至不(bù)需要(yào / yāo)指定具體使用的(de)依賴。比如我們前面在(zài)引入依賴時(shí),我們隻是(shì)在(zài)Initializr裏添加了(le/liǎo)Web、Thymeleaf、JPA和(hé / huò)H2,而(ér)通過傳遞依賴,它們聲明的(de)依賴也(yě)會被傳遞依賴進來(lái),添加這(zhè)四個(gè)依賴就(jiù)等價于(yú)将相關的(de)庫都添加了(le/liǎo)進來(lái)。在(zài)《Spring Boot實戰》附錄B羅列出(chū)了(le/liǎo)全部起步依賴,并簡要(yào / yāo)描述了(le/liǎo)一下它們向項目構建引入了(le/liǎo)什麽。
覆蓋起步依賴引入的(de)傳遞依賴
起步依賴和(hé / huò)你項目裏的(de)其他(tā)依賴沒什麽區别。也(yě)就(jiù)是(shì)說(shuō),你可以(yǐ)通過構建工具中的(de)功能,選擇性地(dì / de)覆蓋它們引入的(de)傳遞依賴的(de)版本号,排除傳遞依賴,當然還可以(yǐ)爲(wéi / wèi)那些Spring Boot起步依賴沒有涵蓋的(de)庫指定依賴。在(zài)Maven裏,可以(yǐ)用元素來(lái)排除傳遞依賴。
Maven總是(shì)會用最近的(de)依賴,也(yě)就(jiù)是(shì)說(shuō),如果你在(zài)項目裏想要(yào / yāo)覆蓋Spring Boot引入的(de)傳遞依賴,可以(yǐ)直接在(zài)在(zài)項目的(de)構建說(shuō)明文件裏增加的(de)這(zhè)個(gè)依賴。
(關于(yú)起步依賴更多詳細内容,參考《Spring Boot實戰》2.2節使用起步依賴。)
2.3 使用自動配置
簡每當應用程序啓動的(de)時(shí)候,Spring Boot檢測ClassPath中包含的(de)包,并對包含的(de)包做一個(gè)基本的(de)配置。比如Spring Security是(shì)不(bù)是(shì)在(zài)Classpath裏?如果是(shì),則進行一個(gè)非常基本的(de)Web安全設置。
每當應用程序啓動的(de)時(shí)候,Spring Boot的(de)自動配置都要(yào / yāo)做将近200個(gè)這(zhè)樣的(de)決定,涵蓋安全、 集成、持久化、Web開發等諸多方面。所有這(zhè)些自動配置就(jiù)是(shì)爲(wéi / wèi)了(le/liǎo)盡量不(bù)讓你自己寫配置,從而(ér)更多的(de)專注于(yú)程序的(de)功能。
下面我們繼續ReadList的(de)功能實現:
- 定義領域模型
讀者閱讀列表上(shàng)的(de)書是(shì)本例的(de)核心領域概念,我們定義一個(gè)實體類Book來(lái)表示這(zhè)個(gè)概念。
@Entity注解表明它是(shì)一個(gè)JPA實體,id屬性加了(le/liǎo)@Id和(hé / huò)@GeneratedValue注解,說(shuō)明這(zhè)個(gè)字段是(shì)實體的(de)唯一标識,并且這(zhè)個(gè)字段的(de)值是(shì)自動生成的(de)。
package com.yuan.readlist;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String reader;
private String isbn;
private String title;
private String author;
private String description;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getReader() {
return reader;
}
public void setReader(String reader) {
this.reader = reader;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
- 定義倉庫接口
用于(yú)把Book對象持久化到(dào)數據庫的(de)倉庫了(le/liǎo)。因爲(wéi / wèi)用了(le/liǎo)Spring Data JPA,所以(yǐ)我們要(yào / yāo)做的(de)就(jiù)是(shì)簡單地(dì / de)定義一個(gè)接口,擴展一下Spring Data JPA的(de)JpaRepository接口:
package com.yuan.readlist;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ReadingListRepository extends JpaRepository<Book, Long> {
List<Book> findByReader(String reader);
}
通過擴展JpaRepository,ReadingListRepository直接繼承了(le/liǎo)18個(gè)執行常用持久化操作的(de)方法。JpaRepository是(shì)個(gè)泛型接口,有兩個(gè)參數:倉庫操作的(de)領域對象類型,及其ID屬性的(de)類型。此外,我還增加了(le/liǎo)一個(gè)findByReader()方法,可以(yǐ)根據讀者的(de)用戶名來(lái)查找閱讀列表。 如果你好奇誰來(lái)實現這(zhè)個(gè)ReadingListRepository及其繼承的(de)18個(gè)方法,請不(bù)用擔心,Spring Data提供了(le/liǎo)很神奇的(de)魔法,隻需定義倉庫接口,在(zài)應用程序啓動後,該接口在(zài)運行時(shí)會自動實現。
- 創建控制器
我們創建Spring MVC控制器來(lái)處理HTTP請求。ReadingListController使用了(le/liǎo)@Controller注解,這(zhè)樣組件掃描會自動将其注冊爲(wéi / wèi) Spring應用程序上(shàng)下文裏的(de)一個(gè)Bean。它還用了(le/liǎo)@RequestMapping注解,将其中所有的(de)處理器 方法都映射到(dào)了(le/liǎo)“/”這(zhè)個(gè)URL路徑上(shàng)。
package com.yuan.readlist;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
@Controller
@RequestMapping("/")
public class ReadingListController {
private ReadingListRepository readingListRepository;
@Autowired
public ReadingListController(ReadingListRepository readingListRepository) {
this.readingListRepository = readingListRepository;
}
@RequestMapping(value = "/{reader}", method = RequestMethod.GET)
public String readersBooks(@PathVariable("reader") String reader,
Model model) {
List<Book> readingList = readingListRepository.findByReader(reader);
if (readingList != null) {
model.addAttribute("books", readingList);
}
return "readingList";
}
@RequestMapping(value = "/{reader}", method = RequestMethod.POST)
public String addToReadingList(@PathVariable("reader") String reader,
Book book) {
book.setReader(reader);
readingListRepository.save(book);
return "redirect:/{reader}";
}
}
該控制器有兩個(gè)方法。
readersBooks():處理/{reader}上(shàng)的(de)HTTP GET請求,根據路徑裏指定的(de)讀者,從(通過控制器的(de)構造器注入的(de))倉庫獲取Book列表。随後将這(zhè)個(gè)列表塞入模型,用的(de)鍵是(shì)books,最後返回readingList作爲(wéi / wèi)呈現模型的(de)視圖邏輯名稱。
addToReadingList():處理/{reader}上(shàng)的(de)HTTP POST請求,将請求正文裏的(de)數據綁定到(dào)一個(gè)Book對象上(shàng)。該方法把Book對象的(de)reader屬性設置爲(wéi / wèi)讀者的(de)姓名,随後通過倉庫的(de)save()方法保存修改後的(de)Book對象,最後重定向到(dào)/{reader}(控制器中的(de)另一個(gè)方法會處理該請求)。
- 創建WEB界面
readersBooks()方法最後返回readingList作爲(wéi / wèi)邏輯視圖名,爲(wéi / wèi)此必須創建該視圖。因爲(wéi / wèi)在(zài)項目開始之(zhī)初就(jiù)決定要(yào / yāo)用Thymeleaf來(lái)定義應用程序的(de)視圖,所以(yǐ)接下來(lái)就(jiù)在(zài)src/main/resources/templates裏創建一個(gè)名爲(wéi / wèi)readingList.html的(de)文件:
<html>
<head>
<title>Reading List</title>
<link rel="stylesheet" th:href="@{/style.css}"></link>
</head>
<body
<h2>Your Reading List</h2>
<div th:unless="${#lists.isEmpty(books)}">
<dl th:each="book : ${books}">
<dt class="bookHeadline">
<span th:text="${book.title}">Title</span> by <span
th:text="${book.author}">Author</span> (ISBN: <span
th:text="${book.isbn}">ISBN</span>)
</dt>
<dd class="bookDescription">
<span th:if="${book.description}" th:text="${book.description}">Description</span>
<span th:if="${book.description eq null}"> No description
available</span>
</dd>
</dl>
</div>
<div th:if="${#lists.isEmpty(books)}">
<p>You have no books in your book list</p>
</div>
<hr />
<h3>Add a book</h3>
<form method="POST">
<label for="title">Title:</label> <input type="text" name="title"
size="50"></input><br /> <label for="author">Author:</label> <input
type="text" name="author" size="50"></input><br /> <label for="isbn">ISBN:</label>
<input type="text" name="isbn" size="15"></input><br /> <label
for="description">Description:</label><br />
<textarea name="description" cols="80" rows="5">
</textarea>
<br /> <input type="submit"></input>
</form>
</body>
</html>
添加名爲(wéi / wèi)style.css的(de)樣式文件,該文件位于(yú)src/main/resources/static目錄:
body {
background-color: #cccccc;
font-family: arial,helvetica,sans-serif;
}
.bookHeadline {
font-size: 12pt;
font-weight: bold;
}
.bookDescription {
font-size: 10pt;
}
label {
font-weight: bold;
}
- 運行程序
到(dào)目前爲(wéi / wèi)止,我們就(jiù)已經完成了(le/liǎo)一個(gè)完整的(de)Spring WEB程序,可見我們并沒有進行什麽配置,所有的(de)配置都是(shì)Spring Boot自動完成的(de)。
下面我們來(lái)運行這(zhè)個(gè)WEB程序,在(zài)MyEclipse中右鍵,Run as Java Application,然後浏覽器訪問127.0.0.1:8080/name即可打開網頁,可提交查詢表單。
關于(yú)Spring Boot自動配置更多内容,及其如何實現自動配置功能,參見《Spring Boot實戰》2.3.3節内容。
2.4 總結
通過Spring Boot的(de)起步依賴和(hé / huò)自動配置,你可以(yǐ)更加快速、便捷地(dì / de)開發Spring應用程序。在(zài)下一章,我們将會看到(dào)如何覆蓋Spring Boot自動配置,借此達成應用程序的(de)一些特殊要(yào / yāo)求,還有如何運用類似的(de)技術來(lái)配置自己的(de)應用程序組件。