之前學(xué)習(xí)了 Java 的各種必備基礎(chǔ)知識荆萤,這一篇是一個(gè)分水嶺,開始真正從零創(chuàng)建一個(gè) Spring Web 項(xiàng)目称诗,可以運(yùn)用到生產(chǎn)環(huán)境中的那種蒸痹。
使用 Spring 進(jìn)行基本的 Java Web 開發(fā):
- 創(chuàng)建和聲明 Service Bean
- 創(chuàng)建和聲明 Controller Bean
- 處理各種各樣的 HTTP 請求
1. 從零開始 Spring 應(yīng)用
從官網(wǎng) Building an Application with Spring Boot 抄即可。
- pom.xml
- src/main/java/hello/Application.java
- src/main/java/hello/HelloController.java
添加 pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
創(chuàng)建基本的 Spring 目錄結(jié)構(gòu)虫碉,在 src/main/java 下面定義自己的包名:
創(chuàng)建一個(gè)入口類贾惦,以便讓 Spring 來啟動我們的程序:
package hello;
import java.util.Arrays;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
};
}
}
現(xiàn)在在瀏覽器中請求 localhost:8080,已經(jīng)有響應(yīng)了敦捧,但是 404须板。
所以再創(chuàng)建一個(gè) Web Controller 作為一個(gè)簡單的 Web 應(yīng)用,該 Controller 中定義了一個(gè)可以用來處理路徑響應(yīng)并順便返回個(gè)字符串的方法兢卵。
也就是我們常說的前后端通信時(shí)的“接口”习瑰,科學(xué)點(diǎn)叫做“路徑”,這是 HTTP 協(xié)議中的概念:
package hello;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
public class HelloController {
@RequestMapping("/")
public String index() {
return "Hello World";
}
}
2. Web 應(yīng)用的本質(zhì)(HTTP)
處理 HTTP 請求:
- 從 HTTP 請求中提取 query string (查詢字符串)
- 從 HTTP 請求中接收 payload(負(fù)載/請求體/請求 body)中的參數(shù)
返回 HTTP 響應(yīng):
- status code
- HTTP response header
- HTTP response body:JSON秽荤、HTML 等等
3. 從 GET 請求的查詢字符串中獲取參數(shù)
一條請求的參數(shù)可以來自于:
- 查詢字符串:通常用來傳遞非敏感信息 ?name=tony&age=25
- 請求路徑
- 請求體
其實(shí)還有其他的請求甜奄,比如 POST 也可以在查詢字符串中攜帶參數(shù)。但實(shí)際應(yīng)用中王滤,如果用了 POST贺嫂,參數(shù)就可以全放在請求體中,而用不著放在請求路徑后面了雁乡。
使用 @RequestParam 處理查詢字符串參數(shù):
@RequestMapping("/search")
public String index(@RequestParam("q") String searchKeyword) {
return "You are searching: " + searchKeyword;
}
可以繼續(xù)增加查詢參數(shù)第喳,并且可以指定為非必傳:
@RequestMapping("/search")
public String index(@RequestParam("q") String searchKeyword,
@RequestParam(value = "charset", required = false) String charset) {
return "You are searching: " + searchKeyword + " " + charset;
}
4. RESTful API、HTTP method 與參數(shù)獲取
RESTful API 只是一種近年來流行的約定踱稍。
參考 MDN 中 HTTP 的方法動詞曲饱,以及 RESTful API 的業(yè)界標(biāo)桿 Github 的 REST API v3悠抹。
使用 HTTP 動詞來代表動作:
- GET:獲取資源
- POST:新建資源
- PUT:更新資源
- DELETE:刪除資源
使用 URL 名詞來代表資源:
- 資源里面沒有動詞
- 使用復(fù)數(shù)來代表資源列表
RESTful 風(fēng)格好處:
- 清晰、優(yōu)雅扩淀、語義化
- 方便批量自動創(chuàng)建接口
接下來實(shí)現(xiàn)一個(gè) Github 風(fēng)格的 API:
Unlock an issue:
Users with push access can unlock an issue's conversation.
DELETE /repos/:owner/:repo/issues/:issue_number/lock
可以繼續(xù)使用 @RequestMapping 注解楔敌,會把所有類型(動詞)的請求都映射到當(dāng)前方法中進(jìn)行處理。
可以為注解傳入?yún)?shù) method驻谆,限制為只處理 delete 請求卵凑。
或者,直接使用 @DeleteMapping(當(dāng)然胜臊,也可以同時(shí)配合 @RequestMapping 定義在類上來處理根路徑)勺卢。
Spring 會識別路徑中的參數(shù)并和方法的參數(shù)進(jìn)行綁定:
package hello;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.HashMap;
@RestController
@RequestMapping("repos") // 該 Controller 響應(yīng) repos 路徑及其子路徑的所有請求類型
public class IssueController {
// DELETE /repos/:owner/:repo/issues/:issue_number/lock
@DeleteMapping("{owner}/{repo}/issues/{issueNumber}/lock") // 只響應(yīng) delete 方法
// @RequestMapping(
// value = "{owner}/{repo}/issues/{issueNumber}/lock",
// method = { RequestMethod.DELETE }
// )
public void unlock(
@PathVariable("owner") String owner,
@PathVariable("repo") String repo,
@PathVariable("issueNumber") String issueNumber) {
System.out.println(owner);
System.out.println(repo);
System.out.println(issueNumber);
}
}
5. 從 POST 請求中獲取參數(shù)
從 HTTP POST 請求中提取 body:
場景 | Content-Type | 使用注解 | 適用于 |
---|---|---|---|
提取整個(gè) body 中的對象 | application/json | @RequestBody | JSON |
提取 body 中的參數(shù) | application/x-www-form-urlencoded | @RequestParam | 表單 |
Create an issue
POST /repos/:owner/:repo/issues
本例中,同時(shí)處理了路徑參數(shù)和請求體中的參數(shù)象对。
安裝插件黑忱,幫助把 json 參數(shù)變成有類型的 Java Bean,方便進(jìn)一步獲取 body 中的參數(shù)勒魔。否則 @RequestBody 默認(rèn)會把 json 處理成 LinkedHashMap 傳入進(jìn)來:
// POST /repos/:owner/:repo/issues
@PostMapping("/{owner}/{repo}/issues")
public void create(
@PathVariable("owner") String owner,
@PathVariable("repo") String repo,
@RequestBody RequestBodyBean object) {
System.out.println(object);
object.getTitle();
object.getLabels().get(0);
}
另一種 POST 請求體中的參數(shù)還可以是 form 表單的形式甫煞,這和查詢字符串其實(shí)是一樣的,只不過是放在了 body 中冠绢,所以還是繼續(xù)使用 @RequestParam 來處理這種形式的字符串參數(shù)抚吠。
一般適用于參數(shù)比較少的時(shí)候,也就不再單獨(dú)綁定一個(gè) Java Bean唐全,而是直接將參數(shù)取出:
@PostMapping("/login")
public void formDemo(
@RequestParam("username") String username,
@RequestParam("password") String password
) {
System.out.println(username);
System.out.println(password);
}
Postman 抓包看一下原始 http 請求文本可以驗(yàn)證埃跷,雖然使用了 POST,參數(shù)放在了請求體邮利,但因?yàn)槭褂?x-www-form-urlencoded弥雹,所以還是和查詢字符串的拼接形式一樣,還是拼出了字符串延届。其實(shí)很好理解剪勿,因?yàn)樾畔⒔涣鳎@樣子拼是雅信達(dá)的方庭。典型的比如登錄場景中厕吉,比較適合。至少用戶名密碼不會被記錄在 URL 地址中械念。
6. 返回 HTTP 響應(yīng)
- 直接操作 HttpServletResponse 對象【簡單头朱、粗暴、原始】
- 直接返回 HTML 字符串【簡單龄减、粗暴项钮、原始】
- 返回對象,并自動序列化為 JSON 字符串【常用,@ResponseBody】
- 模板引擎渲染【JSP/Velocity/Freemaker(參見下一篇筆記)】
Spring boot 底層內(nèi)嵌了 Servlet 容器烁巫,Servlet 是 Java 世界中 Web 容器的標(biāo)準(zhǔn)署隘。
從機(jī)器端口中讀取字節(jié)流,封裝成 Java 對象亚隙,方便上層 WebApp 處理磁餐,處理完之后再把返回值交給 Servlet 容器轉(zhuǎn)化為字節(jié)流作為 HTTP 的響應(yīng)。
字節(jié)流 <--> Servlet(HttpServletRequest/HttpServletResponse) <--> Java 對象
- 操作“裸”的 Servlet 接口:
@RequestMapping("/servlet")
public void search(HttpServletRequest request, HttpServletResponse reponse) throws IOException {
reponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
reponse.getWriter().write("404 Not Found");
}
- 使用 @ResponseBody 標(biāo)記響應(yīng)阿弃,默認(rèn)會被自動轉(zhuǎn)換為 JSON:
@RequestMapping("/demo")
@ResponseBody
public Object search() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "Tony");
map.put("friends", Arrays.asList("Tom", "Bob", "Eric"));
return map;
}
Accpet/Content-Type 是一對 HTTP Header诊霹,請求方可以指定接受的媒體類型。
7. 周邊生態(tài)系統(tǒng)
- HTTPS
- 分布式部署
- 擴(kuò)展功能
- 數(shù)據(jù)庫
- Redis緩存
- 消息隊(duì)列
- RPC(Dubbo/Spring Cloud)
- 微服務(wù)化