springboot

仅做记录 原文https://blog.didispace.com/spring-boot-learning-2x/

新建项目

image-20220525203348557

选择spring web

image-20220525203415266

目录结构

image-20220525203518542

  • src/main/java 下程序入口 Demo1Application
  • src/main/rescurces下的配置文件 application.properties
  • src/test/下的测试入口 Demo1ApplicationTests

生成的Demo1Application和Demo1ApplicationTests类都可以直接运行来启动当前创建的项目,由于项目未配置任何参数,程序会在加载完成spring之后结束运行

<?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.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo1</name>
<description>demo1</description>
<properties>
<java.version>17</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>
</dependency>
</dependencies>

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

</project>

有四个部分

  • 项目元数据 创建时候输入的project metadata部分,也就是maven项目的基本元素,包括 groupid artifactid version name description等
  • parent 继承 spring-boot-starter-parent的依赖管理,控制版本与打包等内容
  • dependencies 项目具体依赖,这里包含了spring-boot-starter-web 用于实现htpp接口(该依赖中包含了spring mvc) spring-boot-starter-test用于编写单元此时的依赖包
  • build:构建配置部分.默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把spring boot应用打包成jar来直接运行

编写一个http接口

在包com.example.demo下新建一个HelloController类,写入一下内容

package com.wan.an;

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

@RestController
public class HelloController {
@RequestMapping("/hello")
public String index(){
return "Hello World";
}
}

运行主程序 访问

image-20220525205139176

编写单元测试用例

打开src/test/下的测试入口Demo1ApplicationTests类,在下面编写一个简单的单元测试来模拟http请求

package com.example.demo;


import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@RunWith(SpringRunner.class)
@SpringBootTest
public class Demo1ApplicationTests {

private MockMvc mvc;

@Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
}

@Test
public void getHello() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("Hello World")));
}

}

使用MockServletContext来构建一个空的WebApplicationContext,这样我们创建的HelloContorller就可以在@Before函数汇总创建并传递到MockMvcBuilders.standaloneSetup()函数中

工程结构推荐

com
+- example
+- myproject
+- Application.java
|
+- domain
| +- Customer.java
| +- CustomerRepository.java
|
+- service
| +- CustomerService.java
|
+- web
| +- CustomerController.java
|

  • root package com.example.myproject,所有的类和其他package都在root package之下
  • 应用主类 Application.java,该类之间位于root package下,通常我们会在应用主类中做一下框架配置扫描等配置,我么放在root package下面可以帮助程序减少手工配置来加载到我们希望被spring加载的内容
  • com.example.myproject.domain 包 用来定义实体映射关系与数据访问相关的接口和实现
  • com.example.myproject.service 包 用来编写业务逻辑相关的接口和时效内
  • com.example.myproject.web 用于编写web层相关的实现,比如:spring mvc的Controller

root package与应用主类的位置是整个结构的关键,由于应用主类在root package中,所以按照上面的规则定义的所有其他类都处于root package下的其他子包之后,默认情况下,spring boot 的应用主类都会自动扫描root package以及所有子包下的所有类来进行初始化

就像,假设我们将com.example.myproject.web包与上面所述的root package com.example.myproject放在同一级中,这个应用的主类Application.java 在默认情况下就无法扫描到com.example.myproject.web中定义的Controller定义,就无法初始化Controller中定义的接口

com
+- example
+- myproject
+- Application.java
|
+- domain
| +- Customer.java
| +- CustomerRepository.java
|
+- service
| +- CustomerService.java
|
+- web
| +- CustomerController.java
|

非典型结构下的初始化

如果我们一定要加载非root package中下的内容,

@ComponentScan

使用@ComponentScan注释指定具体的加载包

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;


@ComponentScan(basePackages = "com.example")
@SpringBootApplication
public class Demo1Application {

public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}

}

这种方法通过注释直接指定要扫描的包,比较直观,如果有这样的需求也是可以用的,但是原则上使用典型结构来定义就可以少写一些注释

@Bean

使用@Bean注释来初始化

@SpringBootApplication
public class Bootstrap {

public static void main(String[] args) {
SpringApplication.run(Bootstrap.class, args);
}

@Bean
public CustomerController customerController() {
return new CustomerController();
}

}

没看懂

配置文件详解

src/main/resources目录时spring boot的配置目录,所以我们要为应用创建配置个性化配置时,就是在该目录之下.

spring boot的默认配置文件的位置为 src/main/resources/application.properties 关于spring boot应用的配置内容都可以集中在该文件中,根据我们引入的不同starter模块,可以在这里定义,容器端口号 数据库的连接信息 日志级别等各种配置信息,比如我们需要自定义web模块的服务端口号,可以在application.properties中添加server.prot=8888来指定端口号为8888,也可以通过spring.application.name=hello来指定应用名

还可以使用yaml来进行配置,yaml还可以在单个文件中通过使用spring.profiles属性来定义多个不同的环境配置.server.port将使用8882端口,而prod环境,server.prot将使用8883端口,如果没有指定环境server.prot将使用8881端口

server:
port: 8881
---
spring:
profiles: test
server:
port: 8882
---
spring:
profiles: prod
server:
port: 8883

自定义参数

我们除了可以在spring boot的配置文件中设置各个starter模块中预定义的配置属性,也可以在配置文件中定义一些我们需要的自定义属性,

book.name=SpringCloudInAction
book.author=ZhaiYongchao

然后 在应用中我们可以通过@Value注解来加载这些自定义的参数

package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Book {
@Value("${book.name}")
private String name;
@Value("${book.author}")
private String author;
}

@Value注释加载属性值的时候可以支持两种表达式来进行配置

  • 一种是上面的PlaceHolder方式,格式为${..},大括号内为PlaceHolder
  • 另外还可以使用spel表达式,格式为#{..},大括号里面为spel表达式

参数应用

在application.properties中的各个参数之间,我们可以直接通过使用placeHolder的方式来进行引用

book.name=SpringCloud
book.author=ZhaiYongchao
book.desc=${book.author} is writing《${book.name}》

book.desc参数引用了上文中定义的book.name和book.author属性,最后该属性的值就是ZhaiYongchao is writing《SpringCloud》

使用随机数

在一些情况下,有一些参数我们希望它每次加载的时候不是一个固定的值,比如 密钥,服务端口,在spring boot的属性配置文件中,我们可以通过使用${random}配置来产生随机的int值,long值或者string字符串,这样我们就可以容易的通过配置来随机生成属性,

# 随机字符串
com.didispace.blog.value=${random.value}
# 随机int
com.didispace.blog.number=${random.int}
# 随机long
com.didispace.blog.bignumber=${random.long}
# 10以内的随机数
com.didispace.blog.test1=${random.int(10)}
# 10-20的随机数
com.didispace.blog.test2=${random.int[10,20]}

该配置方式可以也能够在设值应用的端口等场景,避免在本地调试时出现端口冲突

命令行参数

在命令行方式启动spring boot应用时,连续的两个减号 – 就是对application.properties中的属性值进行赋值的表示,所以,java -jar xx.jar –server.port=8888 命令就等于在application.properties中添加属性server.prot=8888

多环境配置

在spring boot 中多环境配置文件名需要满足application-{profile}.properties的格式,其中{profile}对应你的环境表示

  • application-dev.properties:开发环境
  • application-test.properties:测试环境
  • application-prod.properties:生产环境

至于那个具体的配置文件会被加载,需要在application.properties文件汇总通过spring.profiles.active属性来设置,其中对应配置文件中的{profile}值,如spring.profiles.active=test就会加载application-test.properties配置文件内容。

属性的读取

我们如果要在spring 应用程序的environment中读取属性的时候,没一个属性的唯一名称符合以下规则

  • 通过.分离各个元素
  • 最后一个.将前缀与属性名称分开
  • 必须是字母(a-z)和数字(0-9)
  • 必须是小写字母
  • 用连字符-来分隔单词
  • 唯一允许的其他字符是[和],用于List的索引
  • 不能以数字开头

如果我们要读取配置文件中spring.jpa.database-platform的配置可以

this.environment.containsProperty("spring.jpa.database-platform")

全新的绑定api

在propertes配置中有这样一个配置 com.didispace.foo=bar

我们为他创建对应的配置类,原文这里没加@Component我尝试的时候会爆错

package com.example.demo;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "com.didispace")
public class FooProperties {

private String foo;

@Override
public String toString() {
return "FooProperties{" +
"foo='" + foo + '\'' +
'}';
}

public String getFoo() {
return foo;
}

public void setFoo(String foo) {
this.foo = foo;
}
}

接下来可以使用Binder就可以这样来拿配置信息了

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Demo1Application {

public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Demo1Application.class, args);

Binder binder = Binder.get(context.getEnvironment());

// 绑定简单配置
FooProperties foo = binder.bind("com.didispace",
Bindable.of(FooProperties.class)).get();
System.out.println(foo.getFoo());

}


}

image-20220525224422239

list类型

com.didispace.post[0]=Why Spring Boot
com.didispace.post[1]=Why Spring Cloud

com.didispace.posts[0].title=Why Spring Boot
com.didispace.posts[0].content=It is perfect!
com.didispace.posts[1].title=Why Spring Cloud
com.didispace.posts[1].content=It is perfect too!
ApplicationContext context = SpringApplication.run(Application.class, args);

Binder binder = Binder.get(context.getEnvironment());

// 绑定List配置
List<String> post = binder.bind("com.didispace.post", Bindable.listOf(String.class)).get();
System.out.println(post);

List<PostInfo> posts = binder.bind("com.didispace.posts", Bindable.listOf(PostInfo.class)).get();
System.out.println(posts);

配置元数据的应用

可见这里添加配置的时候idea无法识别

image-20220525230217994

创建一个配置类

package com.example.demo;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "com.fifispace")

public class FooProperties {
private String from;
}

xml中导入自动生成配置的元数据依赖

image-20220525230120247

这样就没有高亮警告了

image-20220525230205723

加密配置中的敏感信息

设置准备加密的配置

datasource.password=wanan

编写输出配置信息的单元测试

package com.example.demo;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;

@Slf4j
@SpringBootTest
public class Demo1ApplicationTests {

@Value("${datasource.password:}")
private String password;

@Test
public void test() {
log.info("datasource.password : {}", password);
}

}

运行以下test()类就可以发现

image-20220526101840535

接着我们开始加密,先添加依赖到pom.xml中

<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>

插件配置

<plugin>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-maven-plugin</artifactId>
<version>3.0.3</version>
</plugin>

在配置文件中,加入加密需要使用的密码

jasypt.encryptor.password=mima

修改要加密的内容,用DEC()将待加密的内容包裹起来

datasource.password=DEC(wanan)

image-20220526102639626

使用jasypt-maven-plugin插件来给DEC()包裹的内容实现批量加密

mvn jasypt:encrypt -Djasypt.encryptor.password=mima

注意这里的-Djasypt.encryptor.password=mima 参数必须与配置文件中的mima中一致,不然后面会解密时候

image-20220526102720109

image-20220526102731095

这里面的ENC()和DEC()一样都是jasypt提供的标识,分别用来标识括号内的是加密后的内容和待加密的内容

mvn jasypt:decrypt -Djasypt.encryptor.password=mima

image-20220526103241739

再次测试,可以发现输出还是正常的

image-20220526103449801

但是这里的密钥我们还是可以在配置文件中拿到,照样解密就可,那么就相当于没有加密了,但是在实际情况下,我们可以通过环境变量或者启动的参数中注入,而不是写在配置文件中

api开发

构建restful api与单元测试

  • @Controller 修饰class,用来创建处理http请求的对象
  • @RestController Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接使用@RestController代替@Controller就不需要在配置@ResponseBody,默认返回json格式
  • @RequestMapping 配置url映射.现在更多的也会直接以Http Method直接关联的映射注解来定义,比如 GetMapping PostMapping DeleteMapping PutMapping

RESTful api设计如下

image-20220526104326067

定义User实体

package com.example.mysp;


import lombok.Data;


@Data
public class User {
private Long id;
private String name;
private Integer age;



@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}

public User() {
}

public User(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}
}

这里使用@Data注解可以时效内在编译器自动添加set和get函数的效果,该注解是lombok同一个的,需要在pom.xml中引入以下依赖

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

编写UserController接口类

package com.example.mysp;

import org.springframework.web.bind.annotation.*;

import java.util.*;

@RestController
//通过这里配置使下面的映射都在/users下
@RequestMapping(value = "/users")
public class UserController {
// 创建线程安全的Map,模拟users信息的存储
static Map<Long,User> users = Collections.synchronizedMap(new HashMap<Long, User>());
// 处理/users/的get请求,用来获取用户列表
@GetMapping("/")
public List<User> getUserList(){

ArrayList<User> users1 = new ArrayList<>(users.values());
return users1;


}

/**
* 处理/users/的post请求,用来创建User
* @param user
* @return
*/
@PostMapping("/")
// @requestBody注释用来绑定通过http请求中application/json类型上传的数据
public String postUser(@RequestBody User user){
users.put(user.getId(), user);
return "success";


}

/**
* 处理/users/{id}的get请求,用来获取url中id值的User信息
* @param id
* @return
*/
@GetMapping("/{id}")
// url中的id可以同@PathVariable绑定到函数的参数中
public User getUser(@PathVariable Long id){
return users.get(id);

}

/**
* 处理/users/{id}的put请求,用来更新user信息
* @param id
* @param user
* @return
*/
@PutMapping("/{id}")
public String putUser(@PathVariable Long id , @RequestBody User user){
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id,u);
return "success";
}

@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id){
users.remove(id);
return "success";
}

}

编写单元测试

package com.example.mysp;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyspApplicationTests {
private MockMvc mvc;

@Before
public void setUp() {
mvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
}

@Test
public void testUserController() throws Exception{
// 测试UserController
RequestBuilder request;

// get查一下user列表,应该为空
request = get("/users/");
mvc.perform(request)
.andExpect(status().isOk())
.andExpect(content().string(equalTo("[]")));


// post 提交一个user
request = post("/users/")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"id\":1,\"name\":\"test\",\"age\":20}");
mvc.perform(request)
.andExpect(content().string(equalTo("success")));


// get获取user列表,应该有刚才插入的数据
request = get("/users/");
mvc.perform(request)
.andExpect(status().isOk())
.andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"test\",\"age\":20}]")));


// put修改id为1的user
request = put("/users/1")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"test2\",\"age\":30}");
mvc.perform(request)
.andExpect(content().string(equalTo("success")));

// get一个id为1的user
request = get("/users/1");
mvc.perform(request)
.andExpect(content().string(equalTo("{\"id\":1,\"name\":\"test2\",\"age\":30}")));

// 删除id为1的user
request = delete("/users/1");
mvc.perform(request)
.andExpect(content().string(equalTo("success")));

// 查一下user表应该为空
request = get("/users/");
mvc.perform(request)
.andExpect(status().isOk())
.andExpect(content().string(equalTo("[]")));

}


}

这里的测试类采用的@RunWith(SpringRunner.class)和@SpringBootTest修饰启动; 由于POST和PUT接口的参数采用@RequestBody注解,所以提交的会是一个json字符串,而不是之前的参数信息,这里在定义请求的时候使用contentType(MediaType.APPLICATION_JSON)指定内容为json格式,使用content传入要提交的json字符串,如果使用ModelATTribute的话就得用param方法添加参数.

使用swagger2构建强大的api文档

整合swagger2

在pom.xml中添加依赖,这里要注意版本问题由于spring-boot-starter-parent版本与swagger-spring-boot-starter可能会不匹配,所以需要降版本

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.0.RELEASE</version>
</dependency>

应用主类中添加@EnableSwagger2注解

package com.example.demo;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@EnableSwagger2

@SpringBootApplication
public class Demo2Application {

public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}

}

在application.properties中配置文档相关内容

swagger.title=spring-boot-starter-swagger
swagger.description=Starter for swagger 2.x
swagger.version=1.4.0.RELEASE
swagger.license=Apache License, Version 2.0
swagger.licenseUrl=https://www.apache.org/licenses/LICENSE-2.0.html
swagger.termsOfServiceUrl=https://github.com/dyc87112/spring-boot-starter-swagger
swagger.contact.name=didi
swagger.contact.url=http://blog.didispace.com
swagger.contact.email=dyc87112@qq.com
swagger.base-package=com.didispace
swagger.base-path=/**
  • swagger.title 标题
  • swagger.description 描述
  • swagger.version 版本
  • swagger.license 许可证
  • swagger.licenseUrl 许可证url
  • swagger.termsOfServiceUrl 服务条款url
  • swagger.contact.name 维护人
  • swagger.contact.url 维护人url
  • swagger.contact.email 维护人email
  • swagger.base-package swagger扫描的基础包,默认 全扫描
  • swagger.base-path 需要处理的基础url规则,默认/**

访问

http://127.0.0.1:8080/swagger-ui.html

image-20220526135111437

这里我们可以看到关于各个接口的描述还都是英文或者遵循代码定义的名称产生的,这些内容对用户并不友好,所以我们需要自己添加一些说明来丰富文档内容.我们通过@api,@apioperation注释来给api增加说明,通过@apiimplicitparam @apimodel @apimodelproperty注释来给参数增加说明

package com.didispace.chapter22;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description="用户实体")
public class User {

@ApiModelProperty("用户编号")
private Long id;
@ApiModelProperty("用户姓名")
private String name;
@ApiModelProperty("用户年龄")
private Integer age;

}

image-20220526141804266

package com.example.demo;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;



import java.util.*;

@Api(tags = "用户管理")
@RestController
//通过这里配置使下面的映射都在/users下
@RequestMapping(value = "/users")
public class UserController {
//创建线程安全的map,模拟users信息的存储
static Map<Long , User> users = Collections.synchronizedMap(new HashMap<>());
@GetMapping("/")
@ApiOperation(value = "获取用户列表")
public List<User> getUserList(){
List<User> r = new ArrayList<>(users.values());
return r;
}
@PostMapping("/")
@ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
public String postUser(@RequestBody User user) {
users.put(user.getId(), user);
return "success";
}

@GetMapping("/{id}")
@ApiOperation(value = "获取用户详细信息",notes = "根据url的id来获取用户的详细信息")
public User getUser(@PathVariable Long id){
return users.get(id);

}
@PutMapping("/{id}")
@ApiImplicitParam(paramType = "path", dataType = "Long", name = "id", value = "用户编号", required = true, example = "1")
@ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
public String putUser(@PathVariable Long id, @RequestBody User user) {
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return "success";
}

@DeleteMapping("/{id}")
@ApiOperation(value = "删除用户",notes = "根据url的id来指定删除对象")
public String deleteUser(@PathVariable Long id){
users.remove(id);
return "success";
}
}

写完之后重新访问可以发现

image-20220526141454989

jsr-303 实现请求参数效验

Bean validation中内置的constraint

image-20220526142731157

Hibernate validator附加的constraint

image-20220526142752867

比如要定义字段不能为null

更改依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>

在校验的字段上添加@NotNull注解

package com.example.demo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.*;


@Data
@ApiModel(description="用户实体")
public class User {

@ApiModelProperty("用户编号")
private Long id;

@NotNull
@ApiModelProperty("用户姓名")
private String name;

@NotNull
@ApiModelProperty("用户年龄")
private Integer age;

}

在需要效验的参数实体前面添加@Valid注解

package com.example.demo;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.*;

@Api(tags = "用户管理")
@RestController
//通过这里配置使下面的映射都在/users下
@RequestMapping(value = "/users")
public class UserController {
//创建线程安全的map,模拟users信息的存储
static Map<Long , User> users = Collections.synchronizedMap(new HashMap<>());
@GetMapping("/")
@ApiOperation(value = "获取用户列表")
public List<User> getUserList(){
List<User> r = new ArrayList<>(users.values());
return r;
}
@PostMapping("/")
@ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
public String postUser(@Valid @RequestBody User user) {
users.put(user.getId(), user);
return "success";
}

@GetMapping("/{id}")
@ApiOperation(value = "获取用户详细信息",notes = "根据url的id来获取用户的详细信息")
public User getUser(@PathVariable Long id){
return users.get(id);

}
@PutMapping("/{id}")
@ApiImplicitParam(paramType = "path", dataType = "Long", name = "id", value = "用户编号", required = true, example = "1")
@ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
public String putUser(@PathVariable Long id, @RequestBody User user) {
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return "success";
}

@DeleteMapping("/{id}")
@ApiOperation(value = "删除用户",notes = "根据url的id来指定删除对象")
public String deleteUser(@PathVariable Long id){
users.remove(id);
return "success";
}
}

image-20220526144604823

  • timestamp 请求时间
  • status http返回的状态码,这里返回400,即请求无效错误的请求,通常参数校验不通过均为400
  • error http返回的错误描述,这里对应的就是400状态的错误描述 badrequest
  • errors 具体错误原因
  • message概要错误消息

添加效验字符串的长度 效验数字的大小 效验字符串格式是否为邮箱等等

package com.example.demo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.*;


@Data
@ApiModel(description="用户实体")
public class User {

@ApiModelProperty("用户编号")
private Long id;

@NotNull
@Size(min = 2,max = 5)
@ApiModelProperty("用户姓名")
private String name;

@NotNull
@Max(100)
@Min(10)
@ApiModelProperty("用户年龄")
private Integer age;

@NotNull
@Email
@ApiModelProperty("用户邮箱")
private String email;

}

swagger接口分类与各元素排序问题详解

我们在spring boot 中定义的各个接口是以controller作为第一级难度来进行组织的,controller与具体接口之间是一对多的关系.我们可以将同属一个模块的接口定义在一个controller里.默认情况下,swagger是一contrlller为单位,对接口进行分组管理的,这个分组的元素在swagger中称为tag,但是这里的tag与接口的关系不是一对多的,它支持更丰富的多对多关系

定义两个Controller,分别负责教室管理和学生管理接口

package com.example.demo;



import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@EnableSwagger2

@SpringBootApplication
public class Demo2Application {

public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}

@RestController
@RequestMapping(value = "/teacher")
static class TeacherController{
@GetMapping("/xxx")
public String xxx(){
return "xxx";
}
}

@RestController
@RequestMapping(value = "/student")
static class StudentController{
@ApiOperation("获取学生清单")
@GetMapping("/list")
public String bbb(){
return "bbb";
}

@ApiOperation("获取教某个学生的老师清单")
@GetMapping("/his-teachers")
public String ccc(){
return "ccc";
}

@ApiOperation("创建一个学生")
@PostMapping("/aaa")
public String aaa(){
return "aaa";
}

}

}

image-20220526155849096

可以看到默认的tag名字是student-comtroller

package com.example.demo;



import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@EnableSwagger2

@SpringBootApplication
public class Demo2Application {

public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}
@Api(tags = "教师管理")
@RestController
@RequestMapping(value = "/teacher")
static class TeacherController{
@GetMapping("/xxx")
public String xxx(){
return "xxx";
}
}
@Api(tags = "学生管理")
@RestController
@RequestMapping(value = "/student")
static class StudentController{
@ApiOperation("获取学生清单")
@GetMapping("/list")
public String bbb(){
return "bbb";
}

@ApiOperation("获取教某个学生的老师清单")
@GetMapping("/his-teachers")
public String ccc(){
return "ccc";
}

@ApiOperation("创建一个学生")
@PostMapping("/aaa")
public String aaa(){
return "aaa";
}

}

}

image-20220526160115038

@Api(tags = {"教师管理", "教学管理"})
@RestController
@RequestMapping(value = "/teacher")
static class TeacherController {

// ...

}

@Api(tags = {"学生管理", "教学管理"})
@RestController
@RequestMapping(value = "/student")
static class StudentController {

// ...

}

image-20220526160433877

更细颗粒的接口分组

通过@Api可以实现将controller中的接口合并到一个Tag中,但是如果我们希望精确到某个接口的合并,比如教学管理包含教师管理中的所有接口以及学生管理 中的获取学生清单接口

这个时候我们可以通过使用@Apioperation注解中的tags属性做更细颗粒度的分类定义

Api(tags = {"教师管理","教学管理"})
@RestController
@RequestMapping(value = "/teacher")
static class TeacherController {

@ApiOperation(value = "xxx")
@GetMapping("/xxx")
public String xxx() {
return "xxx";
}

}

@Api(tags = {"学生管理"})
@RestController
@RequestMapping(value = "/student")
static class StudentController {

@ApiOperation(value = "获取学生清单", tags = "教学管理")
@GetMapping("/list")
public String bbb() {
return "bbb";
}

@ApiOperation("获取教某个学生的老师清单")
@GetMapping("/his-teachers")
public String ccc() {
return "ccc";
}

@ApiOperation("创建一个学生")
@PostMapping("/aaa")
public String aaa() {
return "aaa";
}


image-20220526161002374

swagger静态文档的生成

SwaggerMarkup主要用来将swagger自动生成的文档转换成几种流行的格式以便于静态部署和使用

添加依赖

  <dependencies>
...
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup</artifactId>
<version>1.3.3</version>
<scope>test</scope>
</dependency>
</dependencies>

<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>jcenter-releases</id>
<name>jcenter</name>
<url>http://jcenter.bintray.com</url>
</repository>
</repositories>
package com.example.demo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.NotNull;

@Data
@ApiModel(description="用户实体")
public class User {

@ApiModelProperty("用户编号")
private Long id;

@NotNull
@ApiModelProperty("用户姓名")
private String name;

@NotNull
@ApiModelProperty("用户年龄")
private Integer age;

}
package com.example.demo;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@Api(tags = "用户管理")
@RestController
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在/users下
public class UserController {

// 创建线程安全的Map,模拟users信息的存储
static Map<Long, User> users = Collections.synchronizedMap(new HashMap<>());

@GetMapping("/")
@ApiOperation(value = "获取用户列表")
public List<User> getUserList() {
List<User> r = new ArrayList<>(users.values());
return r;
}

@PostMapping("/")
@ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
public String postUser(@RequestBody User user) {
users.put(user.getId(), user);
return "success";
}

@GetMapping("/{id}")
@ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息")
public User getUser(@PathVariable Long id) {
return users.get(id);
}

@PutMapping("/{id}")
@ApiImplicitParam(paramType = "path", dataType = "Long", name = "id", value = "用户编号", required = true, example = "1")
@ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
public String putUser(@PathVariable Long id, @RequestBody User user) {
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return "success";
}

@DeleteMapping("/{id}")
@ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象")
public String deleteUser(@PathVariable Long id) {
users.remove(id);
return "success";
}

}

spring boot ./env

https://www.jianshu.com/p/ae4be3af5231

https://www.jianshu.com/p/8c18f1e05c94