ZB-044-01从零开始Spring应用

从零开始Spring

001新建一个目录 spring_demo001

002搜索 spring boot

新建 pom.xml 内容抄文档的

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
<?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>

<groupId>org.springframework</groupId>
<artifactId>gs-spring-boot</artifactId>
<version>0.1.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

<properties>
<java.version>1.8</java.version>
</properties>


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

003按文档新建目录src/main/java/hello

004Create an Application class

  • src/main/java/hello/Application.java
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
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);
}

};
}

}

005新建 src/main/java/hello/HelloController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package hello;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloController {

@RequestMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}

}

006右键运行Application.java文件

打开 http://localhost:8080/

此时一个非常基础的SpringBoot 应用成功运行了

header是什么?

  • 请求头

payload是什么?

  • 负载,请求体

queryString 获取查询参数

获取 get 参数

1
2
3
4
5
6
@RequestMapping("/search")
public String search(@RequestParam String q) {
// localhost:8080/search?q=123 时
System.out.println(q);
return "search query word is:" + q;
}

get请求是没有body的吗?

  • googleget payload

  • get请求一般在生产环境里是没有payload的

  • get请求是不禁止有 payload的
  • 只是 http 中明确规定了不推荐
1
2
3
4
5
6
原文回答
Yes. In other words, any HTTP request message is allowed to contain a message body, and thus must parse messages with that in mind. Server semantics for GET, however, are restricted such that a body, if any, has no semantic meaning to the request. The requirements on parsing are separate from the requirements on method semantics.

So, yes, you can send a body with GET, and no, it is never useful to do so.

This is part of the layered design of HTTP/1.1 that will become clear again once the spec is partitioned (work in progress).

控制get参数是否必填

  • 通过注解设置参数是否必填@RequestParam(value="charset",required = false)
1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping("/search2")
public String search2(@RequestParam String q ,
@RequestParam(value="charset",required = false) String charset) {

return "search query word is:" + q + ",and charset is :" + charset;
}

// http://localhost:8080/search2?q=123
// search query word is:123,and charset is :null

// http://localhost:8080/search2?q=123&charset=22
// search query word is:123,and charset is :22

get请求通常不能用来传递敏感信息,如登录。

幂等性

无论多少次get请求 都应该是一种结果

  • 例如 :x无论经历多少次 绝对值都是 “一种结果”

不同于 post/put/delete等

RESTful API

  • 使用HTTP动词代表动作
    • GET 获取资源
    • POST 新建资源
    • PUT 更新资源
    • DELETE 删除资源
  • 使用 URL(名词)代表资源
    • 使用复数来代表资源列表
    • 资源里面没有动词

搜索 github api 业界规范/行业标杆

RESTful API 好处

  • 优雅
  • 方便管理url

RESTful API风格url

参考 github issues api

  • @RequestMapping("repos") 统一处理 ‘/repos路径下的请求’

新建src/main/java/hello/IssueController.java

  • unlock issue

形如 DELETE /repos/:owner/:repo/issues/:issue_number/lock

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
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.RestController;

@RestController
@RequestMapping("repos")
public class IssueController {

// DELETE /repos/:owner/:repo/issues/:issue_number/lock
@DeleteMapping("/{owner}/{repo}/issues/{issue_number}/lock")
public String unlock(
@PathVariable("owner") String owner,
@PathVariable("repo")String repo,
@PathVariable("issue_number")String issue_number
){
// 把当前请求路径重的参数 绑定到当前变量中去
// @PathVariable()
System.out.println(owner);
System.out.println(repo);
System.out.println(issue_number);

return "owner:" + owner + ",repo:" + repo + ",issue_number:" + issue_number;
}
}
  • 访问 localhost:8080/repos/abc/spring/issues/1111/lock

create issue

形如POST /repos/:owner/:repo/issues

  • 除了路径参数
  • 还有 请求体内容
1
2
3
4
5
6
7
8
9
10
11
{
"title": "Found a bug",
"body": "I'm having a problem with this.",
"assignees": [
"octocat"
],
"milestone": 1,
"labels": [
"bug"
]
}

添加如下方法

1
2
3
4
5
6
7
8
9
10
@PostMapping("/{owner}/{repo}/issues")
public String create(
@PathVariable("owner") String owner,
@PathVariable("repo")String repo,
@RequestBody Object object
){

System.out.println(object);
return "owner:" + owner + ",repo:" + repo + ",request body:" + object;
}

此时用 postman发请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
localhost:8080/repos/abc/vue/issues
method 是 post

body 里添加 选择 raw
{
"title": "Found a bug",
"body": "I'm having a problem with this.",
"assignees": [
"octocat"
],
"milestone": 1,
"labels": [
"bug"
]
}

raw的 type 如果是 text 结果返回是 415
{
"timestamp": "2019-11-04T03:21:56.467+0000",
"status": 415,
"error": "Unsupported Media Type",
"message": "Content type 'text/plain;charset=UTF-8' not supported",
"path": "/repos/abc/vue/issues"
}

为什么返回415?

1
2
3
4
5
6
7
8
9
10
11
12
              请求
-------->
client server
<--------
响应


受限于底层 实际传输的数据都是 “字节流”

如何区分这些不同类型的数据
必须有一个东西 标识 这些不同的数据是什么类型
media type

media type 也叫做 Content-Type

修改刚刚 postman的 header

1
Content-Type:application/json

此时访问响应正常了

IDEA工具 GsonFormat

自动将json转换为 实体类字段

新建 MyParam.java

1
2
3
command + n 选择 GsonFormat 
复制你的json字符串
然后ok

再次修改我们的 POST 请求

  • method:post
  • 请求体是 json
  • content-type:application/json
1
2
3
4
5
6
7
8
9
10
11
@PostMapping("/{owner}/{repo}/issues")
public String create(
@PathVariable("owner") String owner,
@PathVariable("repo")String repo,
@RequestBody MyParam object
){
System.out.println(object);
object.getTitle();
object.getLabels().get(0);
return "owner:" + owner + ",repo:" + repo + ",request body:" + object;
}

form表单里的 post请求

1
2
3
4
5
6
7
8
9
10
// localhost:8080/repos/demo
// form-data 一般用于参数少的时候
@PostMapping("/demo")
public void postform(
@RequestParam String name,
@RequestParam Integer age
){
System.out.println(name);
System.out.println(age);
}

⽣成HTTP响应

  • 直接操作HttpServletResponse对象
    • 原始、简单、粗暴
  • 直接返回HTML字符串
    • 原始、简单、粗暴
  • 返回对象,并⾃动格式化成JSON
    • 常⽤
    • @ResponseBody
  • 模板引擎渲染
    • JSP/Velocity/Freemaker

操作HttpServletResponse对象

1
2
3
4
5
6
7
8
9
// 通过 HttpServletResponse 设置响应的内容
@RequestMapping("search")
public void search(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("haha");
}

返回对象,并⾃动格式化成JSON @ResponseBody

1
2
3
4
5
6
7
8
9
// 通过 @ResponseBody 设置响应为 json
@RequestMapping("search2")
@ResponseBody
public Object search2(){
Map<String,Object> result = new HashMap<>();
result.put("name","aaa");
result.put("age",18);
return result;
}

accept设置

  • client和 server通信,通过 accept/content-type 来约定通信的格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
              请求
accept:xml
-------->
client server
<--------
响应
content-type:xml

---------------------------------------------

请求
accept:json
-------->
client server
<--------
响应
content-type:json

服务器的职责:通过客户端的请求接受的 accept mediaType,来响应对应 mediaType的数据

周边⽣态系统

知道了上面的内容,你就可以在此基础上扩展你的应用

  • HTTPS
  • 分布式部署
  • 扩展功能
    • 数据库
    • Redis缓存
    • 消息队列
    • RPC(Dubbo/Spring Cloud)
    • 微服务化

代码仓库

解决 IDEA 导入项目问题

  • 依赖项一直爆红
  • mvn clean install
  • 删除项目的 .idea文件和 .iml文件然后重新导入项目