Spring boot学习总结
学习笔记根据此demo示例撰写
https://github.com/liushuijinger/springboot
第一天
03 helloworld
标题为spring boot实战项目的章节名
这一章为springboot入门案例,了解到了springboot基础架子的搭建,
-
pom
- 需要导入Spring boot starter parent,可以统一spring的version,避免了spring由于版本不一致的冲突
- 还要导入spring-boot-starter-web,此依赖为web编程的起步依赖
- test类用spring-boot-starter-test依赖;
-
配置属性
- 可以通过 servlet.context-path来映射整个工程的父路径
- 同时集成mysql、redis等属性配置也可在yml文件配置
- 两个文件:
- bootstrap.yml:在此文件配置的属性要优先于application.yml
- application.yml:用于配置属性;
-
编码
-
启动类
- 需要加上@SpringBootApplication,如果要排除依赖,可以在此通过exclude属性排除
- 通过SpringApplication.run()方法来启动;
-
controller:主要涉及到几个controller层的注解
-
@RestController
:标识此类是一个controller类,并将此类的返回结果返回为json格式;跟进此注解,会看到此注解类同时添加了@Controller
和@ResponseBody
注解;@Controller
:- 作用:标识此类是一个controller类,并把此类对象添加进IOC容器;
- 理解:跟
@Component
注解类似;handleMapping会扫描有@Controller的类,如果带有@RequestMapping
注解,分发处理器会根据资源路径来进行分发到对应的方法上;
@ResponseBody
:此注解能将返回值转为json格式;
-
延伸:
@RestController
和@Controller
区别,应用场景?- @RestController也是基于@Controller进行增强的,结合了@ResponseBody注解;如果想要返回视图资源就用@Controller注解,如果在前后端分离的情况下,可以直接用@RestController注解;
-
@GetMapping
:-
作用:可以设置映射路径接受对应get请求;
-
理解:此注解是一个组合注解,通过在注解里设置属性达到只接收get请求;
@PostMapping
等同理@RequestMapping(method = {RequestMethod.GET}
-
-
@RequestParam
:- 作用:用于接收请求路径携带的参数;
- 理解:请求路径中的属性名要与方法参数的属性同名,如果不同名可以通过属性值设置;与
@RequestBody
注解类似,都是获取参数,不过获取的范围不一样;
-
总结:
- 一个基础的Controller控制类,首先需要在类上加上@RestController或@Controller注解,标识此类为一个Controller;
- 其次根据需求写对应的映射方法,方法上也要加上@RequestMapping等注解来映射路径;
- 如果根据参数所在位置来使用对应获取参数的注解;如:
- 路径携带的参数:/stu/delete/{id},这种用
@ PathVariable
- get请求用@RequestParam
- post请求用@RequestBody
- 路径携带的参数:/stu/delete/{id},这种用
-
-
测试类:主要用了通知类注解来测试,通过观察console打印结果可以看出执行顺序
@BeforeClass
:在类加载之前就出现,一个类中只运行一次,就算抛异常也一定会执行;且必须声明为public static;@Before
:在标注了@Test注解方法前执行,可以有多个方法标注@Before,执行顺序不确定,必须声明为public并且非static;@AfterClass
:类加载完毕后执行,只执行一次,一定会执行;@After
:在标有@Test方法执行之后执行;
-
测试:
- 为了验证@BeforeClass是否一定会执行:
- 环境:以上四个注解都存在,并且在
@BeforeClass
注解里手动制造除零异常 - 测试结果:
@BeforeClass
和@AfterClass
都会执行,其余注解方法不执行;
-
-
总结:
- 此章主要讲了spring boot基础架子的搭建,以及通知方法的引入;
- 才看demo时,看起来很简单,当深入去对每个知识点去理解,又会延伸出新的知识,越挖越深。这种学习方式有利于在我的脑海里建立一个思维树,以及问题的联想;
07 Swagger
1. Swagger 概述
- 概述:是一款接口调试工具,用于生成、描述、可视化Restful风格的web服务;
- 作用:
- 便于前后端分离开发,团队协作更方便;
- 可以在线生成接口文档,方便调试;
- 接口测试
- 常用注解:
@Api
:修饰整个类,描述Controller的作用 @ApiOperation:描述一个类的一个方法,或者说一个接口@ApiParam
:单个参数的描述信息@ApiModel
:用对象来接收参数@ApiModelProperty
:用对象接收参数时,描述对象的一个字段@ApiResponse
:HTTP响应其中1个描述@ApiResponses
:HTTP响应整体描述@ApiIgnore
:使用该注解忽略这个API@ApiError
:发生错误返回的信息@ApiImplicitParam
:一个请求参数@ApiImplicitParams
:多个请求参数的描述信息@ApiImplicitParam
属性:
属性 | 取值 | 作用 |
---|---|---|
paramType | 查询参数类型 | |
path | 以地址的形式提交数据 | |
query | 直接跟参数完成自动映射赋值 | |
body | 以流的形式提交 仅支持POST | |
header | 参数在request headers 里边提交 | |
form | 以form表单的形式提交 仅支持POST | |
dataType | 参数的数据类型 只作为标志说明,并没有实际验证 | |
Long | ||
String | ||
name | 接收参数名 | |
value | 接收参数的意义描述 | |
required | 参数是否必填 | |
true | 必填 | |
false | 非必填 | |
defaultValue | 默认值 |
2. 测试:springboot集成Swagger、mysql进行测试
- pom
<!--springboot依赖的父工程;方便统一管理spring的version-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.2.5.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--spring boot web起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--test-->
<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>
<!--swagger依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
</dependencies>
- 配置属性
server:
port: 8081
spring:
# 数据库连接
datasource:
url: jdbc:mysql://192.168.5.128/db1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
# mybatis
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
configuration: #sql打印日志输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
type-aliases-package: com.example.pojo
-
编码
- 启动类
- config
/** * @author scx */ @Configuration @EnableSwagger2 public class SwaggerConfig { /** * 创建API应用 * createApiInfo() 增加API相关信息 * 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现 * 采用指定扫描的包路径来定义指定要建立API的目录 * @return */ @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select() .apis(RequestHandlerSelectors.withClassAnnotation(Api.class)) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Spring Boot集成Swagger") .description("Spring Boot实战的RESTFul接口文档说明") .version("1.0") .build(); } }
- pojo
/** * 学生实体类 * * @author 沈晨曦 * @version 2021/4/8 10:10:43 */ @Data public class Stu { @ApiModelProperty("stu主键ID") @TableId(type = IdType.AUTO) private Integer id; @ApiModelProperty("学生姓名") private String name; @ApiModelProperty("学生年龄") private Integer age; }
-
service
public interface StuService { /** * @return 学生集合 */ List<Stu> selectAllStu(); }
- impl
/** * @author 沈晨曦 * @version 2021/4/8 10:22:21 */ @Service public class StuServiceImpl implements StuService { @Autowired private StuMapper stuMapper; @Override public List<Stu> selectAllStu() { List<Stu> stus = stuMapper.selectAllStu(); // 异常判断 if (stus==null||stus.size()==0){ return null; } return stus; } }
-
mapper
/** * stu的dao层 * * @author 沈晨曦 * @version 2021/4/8 10:06:55 */ @Mapper public interface StuMapper { /** * @return 学生信息列表 */ @Select("select id,name,age from stu") List<Stu> selectAllStu(); }
- Controller
/** * 学生表Controller * * @author scx * @version 2021/4/8 10:01:44 */ @Api(value = "学生信息",tags = "stu",description = "学生信息api") @RestController @RequestMapping("/springboot") public class StuController { @Autowired private StuService stuService; @ApiOperation(value = "查询所有学生信息",notes = "查询所有学生信息") @GetMapping("/selectAll") public List<Stu> selectAllStu(){ List<Stu> stus = stuService.selectAllStu(); return stus; } }
打开浏览器访问:
默认地址:http://localhost:8083/swagger-ui.html#/
由于我在配置文件配了api/v1
http://localhost:8083/api/v1/swagger-ui.html#/
3. 异常
APPLICATION FAILED TO START
***************************
Description:
Field stuService in com.example.controller.StuController required a bean of type 'com.example.service.StuService' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.example.service.StuService' in your configuration.
原因:@Service
注解加在了service接口类上
解决:把@Servlice
加在service实现类
4. 延伸
- 接口调试工具:
- postman:接口调试工具,功能较为单一,但是方便好用;
- Swagger:
- Knife4j:
- 是Swagger的增强,功能更丰富,
- 文档说明:根据Swagger规范,详细列出接口文档的说明:接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等;
- 在线调试:在线接口联调。自动解析当前接口参数:表单验证、调用参数可返回接口相应内容、headers、Curl请求命令;
- 个性化配置:可自定义UI的相关显示信息
- 离线文档:根据标准规范,生成在线markdown离线接口文档;
- 接口排序:如一个注册功能包含多个步骤,可以根据Swagger提供的接口排序规则实现接口排序。step化接口操作,方便其他开发者进行接口对接;
08 unittest
这一章主要讲了单元测试
1. 测试:创建一个spring工程、一个spring boot工程,观察两者的不同
-
spring:
- pom:
Junit
和spring - test
两个坐标 - test类:加入
@Runwith(SpringRunner.class)
获取spring的执行器即可
import org.junit.Test;
- pom:
-
spring boot:
- pom:
spring-boot-starter-test
即可 - test类:
@SpringBootTest
注解,指定启动类,@Runwith
获取执行器spring-boot-starter-test
依赖下的test与spring test所属包不同
import org.junit.jupiter.api.Test;
- pom:
探索到这里发现@SpringRunner
底层继承自@SpringJunit4JClassRunner
,且并未做增强扩展;源码:
public final class SpringRunner extends SpringJUnit4ClassRunner {
public SpringRunner(Class<?> clazz) throws InitializationError {
super(clazz);
}
}
异常:
-
异常环境:搭建好一个maven工程,不导入任何依赖,创建一个类,sout(1)
-
异常:需要发行版jdk1.8
-
解决:
- 检查所有需要jdk的地方:Project Settings->project、Modules、SDKs
- Modules是用的1.5,修正后还是报此异常
- 在pom文件加入jdk版本,运行正常
<properties> <java.version>1.8</java.version> </properties>
- 又把这段代码去掉,运行也正常,推测应该在修正jdk版本后按下
Ctrl+shift+F9
对编译保存
2. Junit常用注解
@BeforeClass
:针对所有测试,只执行一次,且必须为static void
@Before
:初始化方法,执行当前测试类的每个测试方法前执行。
@Test
:测试方法,在这里可以测试期望异常和超时时间
@After
:释放资源,执行当前测试类的每个测试方法后执行
@AfterClass
:针对所有测试,只执行一次,且必须为static void
@Ignore
:忽略的测试方法(只在测试类的时候生效,单独执行该测试方法无效)
@RunWith
:可以更改测试运行器 ,缺省值 org.junit.runner.Runner
- 一个单元测试类执行顺序为:
@BeforeClass
(必定执行) –> @Before
–> @Test
–> @After
–> @AfterClass
3. test参数
这些参数在只导入了spring-boot-starter-test
下,无法使用,因为此依赖的test所属包为:
import org.junit.jupiter.api.Test;
timeout
:单位毫秒值,超时测试,当一个测试方法超出设置时间则抛出异常
org.junit.runners.model.TestTimedOutException: test timed out after 1000 milliseconds
expected
:异常测试,属性值为异常类,测试方法如果抛出预设的异常则会测试成功;
09-springmvc
1. 日志输出
-
@Slf4j
:- 日志输出
- 好处,可以替换自定义的logger
private final Logger logger = LoggerFactory.getLogger(LoggerTest.class);
- 需要导入lombok依赖才能使用;
- logger传统方式实现日志
@Test public void loggerTest(){ logger.debug("debug");//默认日志级别为info logger.info("info"); logger.error("error"); logger.warn("warn"); }
- @slf4j方式
@Test public void test2(){ log.debug("debug");//默认日志级别为info log.info("info"); log.error("error"); log.warn("warn"); }
1.1 @slf4j和log4j对比
-
@Log4j
是具体的日志实现,而@Slf4j
是一个抽象的,它允许程序使用任意一个日志类库,使程序更独立; -
@Slf4j
可以使用占位符{}
,减少代码中字符串连接次数,在运行时会被提供的实际字符串被替换; -
好处:
- 降低了字符串连接次数;
- 节省新建String对象;由于Sting不可变,如果有不常用的字符串,就造成内存消耗;
- 使用@Slf4j,可以在运行时延迟字符串的建立,只有需要String对象才被建立;
-
使用对比:
- log4j
if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " symbol: " + symbol); }
- slf4j
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
-
如何集成slf4j
- pom
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency>
- 在要使用日志输入的地方加上
@Slf4j
,使用示例:
@Slf4j @Service public class StuServiceImpl implements StuService { @Autowired private StuMapper stuMapper; @Override public List<Stu> selectAllStu() { List<Stu> stus = stuMapper.selectAllStu(); if (stus==null||stus.size()==0){ log.info("学生表里无数据"); return null; } log.info("查询学生列表成功"); return stus; } }
- 控制台结果
2021-04-08 15:09:33.306 INFO 3424 --- [nio-8081-exec-7] com.example.service.impl.StuServiceImpl : 查询学生列表成功
2. spring mvc
2.1 接收参数的注解
一个请求由:协议、uri、queryParam、header、cookie、body组成
- 如果不加注解,如这样,有参数则封装进去,无参数则不封
// http://localhost:8080/springboot/noannotation?id=1&name=dd&age=22
public User noAnnotation( User user)
@RequestParam
:接收**(queryParam)**请求路径上的参数,如果参数名与方法上的参数名不匹配可以在此注解里指定name
// http://localhost:8080/springboot/requestparam?name=scx&age=22
public User RequestParam(@RequestParam String name, @RequestParam int age)
@PathVariable
:接收uri上的参数;restful风格,通常用于拼接id,
// http://localhost:8080/springboot/pathvariable/scx/22
public User PathVariable(@PathVariable String name,@PathVariable int age)
@RequestBody
:接收请求体(body)里的参数,可以通过开发者模式观察到请求的请求体请求头等信息;
@PostMapping("/requestbody")
public User RequestBody(@RequestBody @Valid User user)
2.2校验参数的注解
@Valid
:用于验证实体类上的对应注解是否符合要求,不符合要求会报错,并返回我们给出的message错误提示信息;
@PostMapping("/requestbody")
public User RequestBody(@RequestBody @Valid User user)
public class User {
private String id;
@NotBlank(message = "密码不能为空")
private String password;
}
@Null
:限制只能为null@NotNull
:限制必须不为null@AssertFalse
:限制必须为false@AssertTrue
:限制必须为true@DecimalMax(value)
:限制必须为一个不大于指定值的数字@DecimalMin(value)
:限制必须为一个不小于指定值的数字@Digits(integer,fraction)
:限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction;@Future
:限制必须是一个将来的日期@Max(value
:限制必须为一个不大于指定值的数字@Min(value)
:限制必须为一个不小于指定值的数字@Past
:限制必须是一个过去的日期@Pattern(value)
:限制必须符合指定的正则表达式@Size(max,min)
:限制字符长度必须在min到max之间@Past
:验证注解的元素值(日期类型)比当前时间早@NotEmpty
: 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)@NotBlank
:验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty@NotBlank
:只应用于字符串且在比较时会去除字符串的空格@Email
:验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
2.3 自定义校验注解
- 定义注解类
/**
* @author scx
*/
@Constraint(validatedBy = {MyConstraintValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno4Valid {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
- 定义校验类
@Slf4j
public class MyConstraintValidator implements ConstraintValidator<MyAnno4Valid, Object> {
@Autowired
private StuService stuService;
@Override
public void initialize(MyAnno4Valid constraintAnnotation) {
System.out.println("初始化");
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
System.out.println("校验开始");
// 自定义校验规则
if ((int) value < 0) {
log.error("年龄小于0");
return false;
}
return true;
}
}
- 实体类加上自定义的校验注解
@Data
public class Stu {
@ApiModelProperty("stu主键ID")
@TableId(type = IdType.AUTO)
private Integer id;
@ApiModelProperty("学生姓名")
private String name;
@ApiModelProperty("学生年龄")
@MyAnno4Valid(message = "年龄不能小于0")
private Integer age;
}
- Controller编写一个方法接收参数并校验
@ApiOperation(value = "新增一条学生信息")
@PostMapping("/insert")
public String insert(@RequestBody @Valid Stu stu) {
boolean b = stuService.insert(stu);
if (b){
return "成功";
}
return "失败";
}
- 测试的时候把age写为-1,测试结果,以下是Swagger返回的错误码片段
"defaultMessage": "年龄不能小于0",
"objectName": "stu",
"field": "age",
"rejectedValue": -1,
"bindingFailure": false,
"code": "MyAnno4Valid"
- 控制台日志打印
2021-04-08 16:36:34.001 ERROR 17544 --- [nio-8081-exec-2] c.example.myAnno.MyConstraintValidator : 年龄小于0
观察控制台,可以看出initialize()只执行一次,而isValid()方法每次请求都会执行;
3. 总结
看似简单的注解,里面的东西不少,大部分好用的注解都是基于最原始的注解进行增强优化,今天学习到比较有意思的就是自定义校验注解,以后在项目中可以自己封装一些工具类对参数进行校验;