SpringBoot-常见场景
1.热部署(不是特别推荐)
SpringBoot为我们提供了一个方便我们开发测试的工具dev-tools。使用后可以实现热部署的效果。当我们运行了程序后对程序进行了修改,程序会自动重启。
原理是使用了两个ClassLoder,一个ClassLoader加载哪些不会改变的类(第三方jar包),另一个ClassLoader加载会更改的类.称之为Restart ClassLoader,这样在有代码更改的时候,原来的Restart Classloader被丢弃,重新创建一个Restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启。
1.1 准备工作
①设置IDEA自动编译
在idea中的setting做下面配置
②设置允许程序运行时自动启动
ctrl + shift + alt + / 这组快捷键后会有一个小弹窗,点击Registry 就会进入下面的界面,找到下面的配置项并勾选,勾选后直接点close
1.2使用
①添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
②触发热部署
当我们在修改完代码或者静态资源后可以切换到其它软件,让IDEA自动进行编译,自动编译后就会触发热部署。
或者使用Ctrl+F9手动触发重新编译。
2.单元测试
我们可以使用SpringBoot整合Junit进行单元测试。
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库。
Junit5功能相比于Junit4也会更强大。但是本课程是SpringBoot的课程,所以主要针对SpringBoot如何整合Junit进行单元测试做讲解。暂不针对Junit5的新功能做介绍。如有需要会针对Junit5录制专门的课程进行讲解。
2.1 使用
①添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
②编写测试类
在测试类当中定义对应的成员变量,并自动注入(后期一般注入service进行测试)
import com.sangeng.controller.HelloController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ApplicationTest {
@Autowired
private HelloController helloController;
@Test
public void testJunit(){
System.out.println(1);
System.out.println(helloController);
}
}
注意:测试类所在的包需要和启动类是在同一个包下。否则就要使用如下写法指定启动类。
@SpringBootTest(classes = HelloApplication.class)进行指定
import com.sangeng.controller.HelloController;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
//classes属性来指定启动类
@SpringBootTest(classes = HelloApplication.class)
public class ApplicationTest {
@Autowired
private HelloController helloController;
@Test
public void testJunit(){
System.out.println(1);
System.out.println(helloController);
}
}
2.2 兼容老版本
如果是对老项目中的SpringBoot进行了版本升级会发现之前的单元测试代码出现了一些问题。
因为Junit5和之前的Junit4有比较大的不同。
先看一张图:
从上图可以看出 JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: 这是Junit提供的平台功能模块,通过它,其它的测试引擎也可以接入
- JUnit JUpiter:这是JUnit5的核心,是一个基于JUnit Platform的引擎实现,它包含许多丰富的新特性来使得自动化测试更加方便和强大。
- JUnit Vintage:这个模块是兼容JUnit3、JUnit4版本的测试引擎,使得旧版本的自动化测试也可以在JUnit5下正常运行。
虽然Junit5包含了JUnit Vintage来兼容JUnit3和Junit4,但是SpringBoot 2.4 以上版本对应的spring-boot-starter-test移除了默认对 **Vintage 的依赖。**所以当我们仅仅依赖spring-boot-starter-test时会发现之前我们使用的@Test注解和@RunWith注解都不能使用了。
我们可以单独在依赖vintage来进行兼容。
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
注意:
org.junit.Test对应的是Junit4的版本,就搭配@RunWith注解来使用。
SpringBoot2.2.0之前版本的写法
import com.sangeng.controller.HelloController;
//import org.junit.jupiter.api.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
//classes属性来指定启动类
@SpringBootTest
@RunWith(SpringRunner.class)
public class ApplicationTest {
@Autowired
private HelloController helloController;
@Test
public void testJunit(){
System.out.println(1);
System.out.println(helloController);
}
}
3.整合mybatis
3.1 准备工作
①数据准备
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`address` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
/*Data for the table `user` */
insert into `user`(`id`,`username`,`age`,`address`) values (2,'pdd',25,'上海'),(3,'UZI',19,'上海11'),(4,'RF',19,NULL),(6,'三更',14,'请问2'),(8,'test1',11,'cc'),(9,'test2',12,'cc2');
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
②实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private Integer age;
private String address;
}
3.2 整合步骤
github: https://github.com/mybatis/spring-boot-starter/
①依赖
<!--mybatis启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
②配置数据库信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
③配置mybatis相关配置
classpath: 表示类加载路径下(resources下)
mapper/ /表示在mapper目录下
*Mapper.xml *表示Mapper结尾的xml文件
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml # mapper映射文件路径
type-aliases-package: com.sangeng.domain # 配置哪个包下的类有默认的别名
④编写Mapper接口
注意在接口上加上@Mapper (否则mybatis不知道它是mapper接口)和@Repository 注解(mybabtis整合包帮我们把UserMapper注入到容器里面了所以idea会识别不出来,如果非要解决,就用这个注解,都是把对应的对象放到容器里面)
@Repository
@Mapper
public interface UserMapper {
public List<User> findAll();
}
⑤编写mapper接口对应的xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sangeng.mapper.UserMapper">
<select id="findAll" resultType="com.sangeng.domain.User">
select * from user
</select>
</mapper>
⑥测试
@SpringBootTest(classes = HelloApplication.class)
public class SpringMyTest {
@Autowired
UserMapper userMapper;
@Test
public void tesMapper(){
System.out.println(userMapper.findAll());
}
}
4.Web开发
4.1 静态资源访问
由于SpringBoot的项目是打成jar包的所以没有之前web项目的那些web资源目录(webapps)。
那么我们的静态资源要放到哪里呢?
从SpringBoot官方文档中我们可以知道,我们可以把静态资源放到 resources/static
(或者 resources/public
或者resources/resources
或者 resources/META-INF/resources
) 中即可。
静态资源放完后,
例如我们想访问文件:resources/static/index.html 只需要在访问时资源路径写成/index.html即可。
例如我们想访问文件:resources/static/pages/login.html 访问的资源路径写成: /pages/login.html
4.1.1 修改静态资源访问路径
SpringBoot默认的静态资源路径匹配为/** 。
如果想要修改可以通过 spring.mvc.static-path-pattern
这个配置进行修改。
例如想让访问静态资源的url必须前缀有/res。例如/res/index.html 才能访问到static目录中的。我们可以修改如下:
在application.yml中
spring:
mvc:
static-path-pattern: /res/** #修改静态资源访问路径
4.1.2 修改静态资源存放目录
默认目录
我们可以修改 spring.web.resources.static-locations 这个配置来修改静态资源的存放目录。
例如:
spring:
web:
resources:
static-locations:
- classpath:/sgstatic/
- classpath:/static/
4.2 设置请求映射规则@RequestMapping
详细讲解:https://www.bilibili.com/video/BV1AK4y1o74Y P5-P12
该注解可以加到方法上或者是类上。(查看其源码可知)
我们可以用其来设定所能匹配请求的要求。只有符合了设置的要求,请求才能被加了该注解的方法或类处理。
4.2.1 指定请求路径
path或者value属性都可以用来指定请求路径。
例如:
我们期望让请求的资源路径为**/test/testPath的请求能够被testPath**方法处理则可以写如下代码
@RestController
@RequestMapping("/test")
public class HelloController {
@RequestMapping("/testPath")
public String testPath(){
return "testPath";
}
}
@RestController
public class HelloController {
@RequestMapping("/test/testPath")
public String testPath(){
return "testPath";
}
}
4.2.2 指定请求方式
method属性可以用来指定可处理的请求方式。
例如:
我们期望让请求的资源路径为**/test/testMethod的POST请求能够被testMethod**方法处理。则可以写如下代码
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testMethod",method = RequestMethod.POST)
public String testMethod(){
System.out.println("testMethod处理了请求");
return "testMethod";
}
}
注意:我们可以也可以运用如下注解来进行替换
-
@PostMapping 等价于 @RequestMapping(method = RequestMethod.POST)
-
@GetMapping 等价于 @RequestMapping(method = RequestMethod.GET)
-
@PutMapping 等价于 @RequestMapping(method = RequestMethod.PUT)
-
@DeleteMapping 等价于 @RequestMapping(method = RequestMethod.DELETE)
例如:
上面的需求我们可以使用下面的写法实现
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping(value = "/testMethod")
public String testMethod(){
System.out.println("testMethod处理了请求");
return "testMethod";
}
}
4.2.3 指定请求参数
我们可以使用params属性来对请求参数进行一些限制。可以要求必须具有某些参数,或者是某些参数必须是某个值,或者是某些参数必须不是某个值。
例如:
我们期望让请求的资源路径为**/test/testParams的GET请求,并且请求参数中具有code参数**的请求能够被testParams方法处理。则可以写如下代码
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testParams",method = RequestMethod.GET,params = "code")
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}
如果是要求不能有code这个参数可以把改成如下形式
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testParams",method = RequestMethod.GET,params = "!code")
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}
如果要求有code这参数,并且这参数值必须是某个值可以改成如下形式
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testParams",method = RequestMethod.GET,params = "code=sgct")
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}
如果要求有code这参数,并且这参数值必须不是某个值可以改成如下形式
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testParams",method = RequestMethod.GET,params = "code!=sgct")
public String testParams(){
System.out.println("testParams处理了请求");
return "testParams";
}
}
4.2.4 指定请求头
我们可以使用headers属性来对请求头进行一些限制。
例如:
我们期望让请求的资源路径为**/test/testHeaders的GET请求,并且请求头中具有deviceType**的请求能够被testHeaders方法处理。则可以写如下代码
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testHeaders",method = RequestMethod.GET,headers = "deviceType")
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}
如果是要求不能有deviceType这个请求头可以把改成如下形式
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testHeaders",method = RequestMethod.GET,headers = "!deviceType")
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}
如果要求有deviceType这个请求头,并且其值必须是某个值可以改成如下形式
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testHeaders",method = RequestMethod.GET,headers = "deviceType=ios")
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}
如果要求有deviceType这个请求头,并且其值必须不是某个值可以改成如下形式
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testHeaders",method = RequestMethod.GET,headers = "deviceType!=ios")
public String testHeaders(){
System.out.println("testHeaders处理了请求");
return "testHeaders";
}
}
4.2.5 指定请求头Content-Type
我们可以使用consumes属性来对Content-Type这个请求头进行一些限制。
范例一
我们期望让请求的资源路径为**/test/testConsumes**的POST请求,并且请求头中的Content-Type头必须为 multipart/from-data 的请求能够被testConsumes方法处理。则可以写如下代码
@RequestMapping(value = "/testConsumes",method = RequestMethod.POST,consumes = "multipart/from-data")
public String testConsumes(){
System.out.println("testConsumes处理了请求");
return "testConsumes";
}
范例二
如果我们要求请求头Content-Type的值必须不能为某个multipart/from-data则可以改成如下形式:
@RequestMapping(value = "/testConsumes",method = RequestMethod.POST,consumes = "!multipart/from-data")
public String testConsumes(){
System.out.println("testConsumes处理了请求");
return "testConsumes";
}
4.3 获取请求参数
4.3.1 获取路径参数
RestFul风格的接口一些参数是在请求路径上的。类似: /user/1 这里的1就是id。
如果我们想获取这种格式的数据可以使用**@PathVariable**来实现。
范例一
要求定义个RestFul风格的接口,该接口可以用来根据id查询用户。请求路径要求为 /user ,请求方式要求为GET。
而请求参数id要写在请求路径上,例如 /user/1 这里的1就是id。
我们可以定义如下方法,通过如下方式来获取路径参数:
@RestController
public class UserController {
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public String findUserById( @PathVariable("id")Integer id){
System.out.println("findUserById");
System.out.println(id);
return "findUserById";
}
}
范例二
如果这个接口,想根据id和username查询用户。请求路径要求为 /user ,请求方式要求为GET。
而请求参数id和name要写在请求路径上,例如 /user/1/zs 这里的1就是id,zs是name
我们可以定义如下方法,通过如下方式来获取路径参数:
@RestController
public class UserController {
@RequestMapping(value = "/user/{id}/{name}",method = RequestMethod.GET)
public String findUser(@PathVariable("id") Integer id,@PathVariable("name") String name){
System.out.println("findUser");
System.out.println(id);
System.out.println(name);
return "findUser";
}
}
4.3.2 获取请求体中的Json格式参数
RestFul风格的接口一些比较复杂的参数会转换成Json通过请求体传递过来。这种时候我们可以使用**@RequestBody**注解获取请求体中的数据。
4.3.2.1 配置
SpringBoot的web启动器已经默认导入了jackson的依赖,不需要再额外导入依赖了。
4.3.2.2 使用
范例一
要求定义个RestFul风格的接口,该接口可以用来新建用户。请求路径要求为 /user ,请求方式要求为POST。
用户数据会转换成json通过请求体传递。
请求体数据
{"name":"三更","age":15}
1.获取参数封装成实体对象
如果我们想把Json数据获取出来封装User对象,我们可以这样定义方法:
@RestController
public class UserController {
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String insertUser(@RequestBody User user){
System.out.println("insertUser");
System.out.println(user);
return "insertUser";
}
}
User实体类如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
}
2.获取参数封装成Map集合
也可以把该数据获取出来封装成Map集合:
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String insertUser(@RequestBody Map map){
System.out.println("insertUser");
System.out.println(map);
return "insertUser";
}
范例二
如果请求体传递过来的数据是一个User集合转换成的json,Json数据可以这样定义:
[{"name":"三更1","age":14},{"name":"三更2","age":15},{"name":"三更3","age":16}]
方法定义:
@RequestMapping(value = "/users",method = RequestMethod.POST)
public String insertUsers(@RequestBody List<User> users){
System.out.println("insertUsers");
System.out.println(users);
return "insertUser";
}
4.3.2.3 注意事项
如果需要使用**@RequestBody**来获取请求体中Json并且进行转换,要求请求头 Content-Type 的值要为: application/json 。
4.3.3 获取QueryString格式参数
如果接口的参数是使用QueryString的格式的话,我们也可以使用SpringMVC快速获取参数。
我们可以使用**@RequestParam**来获取QueryString格式的参数。
4.3.3.1 使用
范例一
要求定义个接口,该接口请求路径要求为 /testRequestParam,请求方式无要求。参数为id和name和likes。使用QueryString的格式传递。
1.参数单独的获取
如果我们想把id,name,likes单独获取出来可以使用如下写法:
在方法中定义方法参数,方法参数名要和请求参数名一致,这种情况下我们可以省略**@RequestParam**注解。
@RequestMapping("/testRquestParam")
public String testRquestParam(Integer id, String name, String[] likes){
System.out.println("testRquestParam");
System.out.println(id);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}
如果方法参数名和请求参数名不一致,我们可以加上**@RequestParam**注解例如:
@RequestMapping("/testRquestParam")
public String testRquestParam(@RequestParam("id") Integer uid,@RequestParam("name") String name, @RequestParam("likes")String[] likes){
System.out.println("testRquestParam");
System.out.println(uid);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}
2.获取参数封装成实体对象
如果我们想把这些参数封装到一个User对象中可以使用如下写法:
@RequestMapping("/testRquestParam")
public String testRquestParam(User user){
System.out.println("testRquestParam");
System.out.println(user);
return "testRquestParam";
}
User类定义如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private String[] likes;
}
测试时请求url如下:
http://localhost:8080/testRquestParam?id=1&name=三更草堂&likes=编程&likes=录课&likes=烫头
注意:实体类中的成员变量要和请求参数名对应上。并且要提供对应的set/get方法。
4.3.4 相关注解其他属性
4.3.4.1 required
代表是否必须,默认值为true也就是必须要有对应的参数。如果没有就会报错。
如果对应的参数可传可不传则可以把其设置为fasle
例如:
@RequestMapping("/testRquestParam")
public String testRquestParam(@RequestParam(value = "id",required = false) Integer uid,@RequestParam("name") String name, @RequestParam("likes")String[] likes){
System.out.println("testRquestParam");
System.out.println(uid);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}
400–请求参数错误
name因为前面没加注解所以没传也无所谓
4.3.4.2 defaultValue
如果对应的参数没有,我们可以用defaultValue属性设置默认值。
例如:
@RequestMapping("/testRquestParam")
public String testRquestParam(@RequestParam(value = "id",required = false,defaultValue = "777") Integer uid,@RequestParam("name") String name, @RequestParam("likes")String[] likes){
System.out.println("testRquestParam");
System.out.println(uid);
System.out.println(name);
System.out.println(Arrays.toString(likes));
return "testRquestParam";
}
默认值也不要乱传(不是同一类型)
4.4 响应体响应数据
无论是RestFul风格还是我们之前web阶段接触过的异步请求,都需要把数据转换成Json放入响应体中。
4.4.1 数据放到响应体
我们的SpringMVC为我们提供了 @ResponseBody 来非常方便的把Json放到响应体中。
@ResponseBody可以加在哪些东西上面?类上和方法上
具体代码请参考范例。
4.4.2 数据转换成Json
4.4.2.1 配置
SpringBoot项目中使用了web的start后,不需要进行额外的依赖和配置
4.4.2.2 使用
只要把要转换的数据直接作为方法的返回值返回即可。SpringMVC会帮我们把返回值转换成json。具体代码请参考范例。
4.4.3 范例
范例一
要求定义个RestFul风格的接口,该接口可以用来根据id查询用户。请求路径要求为 /response/user ,请求方式要求为GET。
而请求参数id要写在请求路径上,例如 /response/user/1 这里的1就是id。
要求获取参数id,去查询对应id的用户信息(模拟查询即可,可以选择直接new一个User对象),并且转换成json响应到响应体中。
@Controller
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/user/{id}")
@ResponseBody
public User findById(@PathVariable("id") Integer id){
User user = new User(id, "三更草堂", 15, null);
return user;
}
}
@ResponseBody springMVC将方法的返回值(user)转为json字符串放到响应体里