Spring Boot是一个基于Spring框架的开发框架,旨在简化Java应用程序的开发和部署。它提供了一种快速、便捷的方式来创建独立的、生产级别的Spring应用程序,无需繁琐的配置。
-
简化配置:Spring Boot采用约定优于配置的原则,自动配置了许多常见的应用程序配置,大大减少了开发人员需要手动配置的工作量。
-
内嵌式容器:Spring Boot内置了常用的Servlet容器,如Tomcat、Jetty等,可以将应用程序打包成一个可执行的JAR文件,并通过内置的容器直接运行,无需额外部署WAR文件到外部容器。
-
自动化配置:Spring Boot提供了大量的自动配置功能,可以根据类路径上的依赖和用户的自定义配置来自动配置应用程序,减少了手动配置的烦恼。
-
起步依赖:Spring Boot提供了一系列“起步依赖”,这些依赖项集成了常用的库和框架,使得开发人员能够更快速地构建出功能完善的应用程序。
-
Actuator端点:Spring Boot提供了Actuator模块,可以通过暴露的端点来监控和管理应用程序的运行状态,包括健康检查、配置信息、日志输出等。
-
集成测试支持:Spring Boot提供了对集成测试的良好支持,可以方便地编写和运行集成测试,确保应用程序的各个组件正常工作。
一. 初识SpringBoot
1 使用其编写hello world
1. 创建 Maven 项目,添加SpringBoot的插件及依赖。注:SpringBoot 2.x版本支持的jdk最低版本为8,而3.x版本中Java17 为最低支持版本。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2. 创建主类,添加对应的注解,运行项目即可。注解的使用,可参考 SpringMVC注解详细解释
// 主类
@SpringBootApplication
public class MiainFunction {
public static void main(String[] args) {
SpringApplication.run(MiainFunction.class,args);
}
}
@RestController
public class Hello {
@RequestMapping("/")
public String test01() {
return "hello , SpringBoot";
}
}
3. SpringBoot 提供 resources 下创建 application.properties 配置文件,来对我们的服务器 可以完成相对应的配置。
4. SpringBoot 提供 ,把我们的项目,打包成可运行的jar包 通过java -jar example 执行。
maven添加插件如下:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2 分析spring boot的项目及环境配置依赖
1. 依赖管理
父项目对改环境所用到的所有jar包,阐明了依赖版本,再添加一些依赖时,可不声明版本号
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
</parent>
2. 开发导入 start 启动器开发环境
spring boot中所有支持环境的启动器开发环境,都在官方文档中
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3. 如果不想使用springbooot默认的版本号信息,可在当前的pom.xml 下配置信息。如下:
2. 自动配置
1. Tomcat自动配置
2. 配置SpringMVC常见的功能及编码问题。
3. 默认的包结构:无需配置包扫描器,默认注解扫描为当前主程序的包下。若修改默认的扫描包的范围,可使用该属性。
//@SpringBootApplication(scanBasePackages = "com.liubo.*")
// 代表注解扫描到 下所有的包 只有再注解扫描包的范围下 注解才可以使用
@SpringBootApplication // 默认扫描包的范围为 com.liubo.boot
4. 在 application.properties 配置文件中,里面所有的配置项关联着 某个java类的熟悉。
5. 按需加载。引用该场景时,所有的Bean,不是一次性加载完成,而是使用到该功能才会加载。
二、底层注解原理
1. @Comfiguration 注解
@Configuration
注解用于标记一个类作为配置类,并且定义了一些bean的创建和组装方式。使用该注解的类被称为配置类,它们可以包含@Bean
注解的方法,用于声明和初始化Spring管理的bean。
@Configuration // 声明该类是注解类
public class MyBeanConfig {
@Bean // 将该方法注入容器中 ID为方法名getUser 方法返回值类型 即为 bean 返回类型
public User getUser() {
return new User("小米",15);
}
@Bean("pet") // 指定容器中 ID 为pet
public Pet getPet() {
return new Pet("小狗",4);
}
}
属性 proxyBeanMethods:
proxyBeanMethods
是@Configuration
注解的一个属性,默认值为true
。当proxyBeanMethods
属性设置为true
时,Spring Boot会为@Configuration配置类生成一个代理对象,以确保@Bean
注解的方法在被调用时能够正确处理依赖关系。换句话说,使用代理对象可以确保每次调用@Bean
注解的方法时都会返回同一个实例。
当proxyBeanMethods
属性设置为false
时,Spring Boot将不会为@Configuration配置类生成代理对象。这意味着每次调用@Bean
注解的方法时都会重新创建一个新的实例。这通常适用于那些没有依赖关系或者不希望通过代理对象来管理的bean。
true 或 false 使用场景:
当@Configuration
配置类内部存在循环依赖或者复杂的依赖关系时,建议将proxyBeanMethods
属性设置为true。
对于简单的配置类,将proxyBeanMethods
属性设置为false
可以避免生成额外的代理对象,提升性能。
2. @Import 注解
@Import
注解可以用在@Configuration
注解标记的类上,也可以用在普通的类上。使用方式如下:
-
导入配置类:在一个配置类中使用
@Import
注解,可以导入其他的配置类,将其包含到当前配置类中。示例:@Import(OtherConfig.class)
这样,当前配置类就可以访问并使用被导入的OtherConfig
中定义的bean。 -
导入组件类:除了导入配置类,
@Import
注解还可以导入普通的组件类,即不带@Configuration
注解的类。示例:@Import(OtherComponent.class)
这样,被导入的组件类OtherComponent
就会被自动注册到容器中,可以在当前配置类中进行使用。
值得注意的是,@Import
注解不仅支持单个配置类或组件类的导入,还支持同时导入多个类。示例:@Import({ClassA.class, ClassB.class})
这样就可以一次性导入多个配置类或组件类,并将它们一起纳入到当前配置类的管理范围。
// 在容器中导入{}里类型的组件,默认该组件名为该类的全类名
@Import({User.class, Pet.class})
3. @Conditional 注解
@Conditional
注解用于根据特定的条件来决定是否加载某个配置类或创建某个bean。通过在@Configuration
类或@Bean
方法上添加@Conditional
注解,可以根据条件灵活地控制组件的创建和加载。再检验容器中是否符合条件时,有先后顺序 以上倒下执行。
4. @ImportRource 注解
可以将之前的 SpringMVC 的xml配置文件,直接导入并创建该组件。
5. 配置绑定
将类中的属性 与 springboot核心配置文件 application.properties 绑定。
application.properties 配置文件内容如下:
1. 类上使用@ConfigurationProperties
2. 在配置类上使用 @EnableConfigurationProperties
被绑定类上,说明属性前缀绑定。
6. 默认包原理
主程序中 @SpringBootApplication 注解包含了 以下注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
说明 @SpringBootConfiguration主程序也是SpringBoot核心配置类。
@EnableAutoConfiguration 结构如下:
在当前接口时,使用@AutoConfigurationPackage 自动配置包,其内部使用@Import导入
{AutoConfigurationPackages.Registrar.class}组件,该类的Registrar 静态方法 返回当前主方法的所在包。
7. 自动配置原理
-
启动阶段:当应用程序启动时,Spring Boot 2.x 会扫描 classpath 下的所有依赖,并加载其中的
META-INF/spring.factories
文件。 -
加载自动配置类:与之前版本相同,Spring Boot 2.x 也使用
META-INF/spring.factories
文件来定义自动配置类的全限定名。Spring Boot 2.x 会根据这些全限定名,加载对应的自动配置类。 -
条件匹配:加载自动配置类后,Spring Boot 2.x 会对每个自动配置类进行条件判断。这些条件可以通过
@ConditionalOnXxx
系列注解来指定,例如@ConditionalOnClass
、@ConditionalOnProperty
等。如果条件满足,则继续进行下一步操作。 -
自动配置:对于满足条件的自动配置类,Spring Boot 2.x 会根据其内部的逻辑进行相应的自动配置。自动配置类通常使用
@Configuration
注解标记,并用@Bean
注解定义需要自动创建的 Bean。 -
装配 Bean:在自动配置过程中,会根据需要创建和装配一系列的 Bean。Spring Boot 2.x 使用智能的装配机制,根据应用程序的依赖关系和配置信息,自动创建所需的 Bean,包括数据源、事务管理器、模板等。
-
配置优先级:与之前版本类似,Spring Boot 2.x 中应用程序的显式配置会覆盖自动配置的默认值。这意味着,如果应用程序显式地定义了某个 Bean 或配置项,它将覆盖自动配置提供的默认值。
-
条件注解扩展:Spring Boot 2.x 引入了条件注解扩展的机制。开发者可以通过自定义
@Conditional
注解和对应的条件(Condition)类来扩展条件判断逻辑。这使得自动配置更加灵活和可扩展。 -
模块化的自动配置:Spring Boot 2.x 进一步将自动配置进行了模块化划分,不同功能的自动配置被放置在不同的模块中,通过 Maven 或 Gradle 的依赖管理机制,可以选择性地引入和 使用特定的自动配置模块。
三、实践
1. 添加场景依赖,参考 官方文档。
2. 是否需要修改配置,同样参考官方文档。
开发小技巧
1. lombok 使用注解,在编译时对 Bean类实现 get/set 方法,有参无参构造器,及toString方法 和hashcode方法。
引入依赖,如下所示:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
各个注解的作用
@Data | 对该类的所有属性添加get-set方法 |
@AllArgsConstructor | 对该类添加 所有属性的有参构造器 |
@NoArgsConstructor | 无参构造器 |
@ToString | 添加 toString 方法 |
@EqualsAndHashCode | 添加hashcode方法 |
@Slf4j | 启动日志功能 |
2. dev-tools 编译程序重启。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
点击 ctrl + F9 或者:
3. 更加方便创建spring boot 项目
四、SpringBoot的核心功能
1. 配置文件
1.1 properties 文件
创建全局配置文件 application.properties
1.2 yaml文件
- 键值对:使用冒号
:
将键和值分隔,例如:key: value
。 - 层级结构:通过缩进表示层级关系,用空格或制表符进行缩进。子项需要比父项多两个空格的缩进。
- 列表:使用连字符
-
表示列表项,可以在一行上定义多个列表项。例如: - 数组表示法:
# 数组表达法
arrayName: [value1, value2]
arrayName:
-v1
-v2
#@ConfigurationProperties(prefix = "user")
#@Component
#public class User {
user:
name: zhangsan
age: 12
# 数组表达法
arrayName: [value1, value2]
arrayName:
- v1
- v2
# map 数组表示法
#mapName: {k1: v1, k2: v2}
mapName:
k1: v1
k2: v2
- 注解处理器 对配置文件与类中属性相匹配时 可以给提示
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
-配置处理器和业务无关,将其不打包,
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<proc>none</proc>
</configuration>
</plugin>
2. web开发
spring boot 对 spring MVC 做了自动化配置,有如下配置:
-
自动配置 DispatcherServlet:Spring Boot 会自动配置并注册
DispatcherServlet
,作为应用程序的前端控制器。它处理所有进入应用程序的 HTTP 请求,并将请求分发给相应的控制器进行处理。 -
静态资源处理自动配置:Spring Boot 会自动配置静态资源的处理,例如 CSS、JavaScript、图片等。默认情况下,它会在 classpath 下的
/static
、/public
、/resources
和/META-INF/resources
目录中查找静态资源。 -
默认的视图解析器配置:Spring Boot 会根据所添加的依赖自动配置适当的视图解析器,用于解析和渲染视图。常见的模板引擎如 Thymeleaf、FreeMarker 和 JSP 都可以被自动配置。
-
自动化处理 Web 请求参数:Spring Boot 会自动将请求参数绑定到方法参数上,并支持各种数据类型的自动转换。通过使用注解(如
@RequestParam
、@PathVariable
),可以方便地将请求参数映射到方法的参数上。 -
全局错误处理自动配置:Spring Boot 提供了全局的错误处理机制,用于捕获并处理应用程序中抛出的异常。它会自动将异常信息转换为适当的 HTTP 响应,并可以根据需要进行自定义异常处理。
-
自动配置校验器:Spring Boot 会自动配置并注册校验器(Validator),用于进行表单验证。它支持 JSR-303 标准的校验注解,例如
@NotNull
、@Size
等,以便在请求参数绑定过程中进行数据校验。 -
默认的日期和时间格式化配置:Spring Boot 可以自动将请求中的日期和时间字符串转换为 Java 的日期和时间对象,并将其绑定到方法参数上。通过合适的注解(如
@DateTimeFormat
),可以指定日期和时间的格式化方式
1 静态资源访问
默认情况下,它会在 classpath 下的 /static
、/public
、/resources
和 /META-INF/resources
。classpath : /main/resources下。对于以上目录下的静态资源可直接访问。 但如果控制器中的方法接受,对应的请求路径,则会走控制器的方法。
可以改变默认的静态资源的包下:
# 把静态资源路径 改为/res/** ,静态资源访问路径前缀 必须添加 /res
# 如 未配置 /1.jpg -》配置过后 /res/1.jpg
spring:
mvc:
static-path-pattern: /res/**
web:
resources:
static-locations: classpath:/sanker # 改变静态资源的位置,全部 /sanker/static
-也可以访问资源包中的资源 /webjars/jquery/jquery.min.js
2 欢迎页
1. 在静态资源路径下的index.html ,会作为默认的欢迎页。不可以配置访问静态资源的前缀,static-path。
3 自定义网页头像 Custom Favicon
必须在静态资源路径下 后缀为 favicon.ico,该图片作为网页头像。
4 静态资源配置原理
-
ResourceProperties
:该类位于org.springframework.boot.autoconfigure.web
包下,用于管理静态资源的位置和映射规则。它通过读取application.properties
或application.yml
中的相关spring.resources
配置属性来获取静态资源的配置信息。 -
ResourceLoader
:该接口位于org.springframework.core.io
包下,定义了加载资源的方法。Spring Boot中使用DefaultResourceLoader
作为默认的资源加载器,实现了ResourceLoader
接口。在静态资源处理过程中,ResourceLoader
通过调用相应的方法来加载并提供静态资源的访问。 -
WebMvcAutoConfiguration
:该类位于org.springframework.boot.autoconfigure.web.servlet
包下,是Spring Boot自动配置Web MVC的核心类。其中,addStaticLocations()
方法用于添加静态资源的位置,将资源路径添加到ResourceHttpRequestHandler
的locations
列表中。 -
ResourceHttpRequestHandler
:该类位于org.springframework.web.servlet.resource
包下,继承了Spring MVC的AbstractHandlerMapping
抽象类,用于处理静态资源请求。在处理请求时,它会根据配置的资源位置、映射规则以及请求URL路径,将静态资源返回给客户端。
总体上,Spring Boot对静态资源的配置原理是通过自动配置、资源加载器、资源位置映射和资源处理器等组件配合使用。自动配置机制会根据约定的规则和配置文件中的属性,将静态资源的位置添加到资源加载器中,并将资源路径映射到资源处理器上,最终在请求过程中根据映射关系返回相应的静态资源。
具体的源码实现涉及多个模块和类,可以通过查看Spring Boot的源码仓库(如GitHub)中相关模块的代码来深入了解每一部分的具体实现细节。
3. 数据访问
1. rest风格
使用以下 HTTP 动词进行资源的操作 。GET 获取。DELETE 删除 PUT修改, POST 添加 。
如对用户操作,请求路径都为 /User,通过不同的提交方式(GET/POST/PULL/DELETE)
Rest原理(表单提交要使用REST的时候)
。表单提交会带上method=PUT
。请求过来被HiddenHttpMethodFilter拦截
请求是否正常,并且是POST
获取到 method的值
兼容以下请求PUT DELETE PATCH
原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
前端页面
<form action="/user" method="get"><input type="submit" value="get"></form>
<form action="/user" method="post"><input type="submit" value="post"></form>
<form action="/user" method="post">
<input type="hidden" name="_method" value="put">
<input type="submit" value="put">
</form>
<form action="/user" method="post"><input type="hidden" name="_method" value="delete"><input type="submit" value="delete"></form>
</body>
User Contoller 后端页面
package com.liubo.boot.controller;
import org.apache.coyote.Request;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* @Author 再进一步
* @Date 2023/9/13 20:58
* @DAY_NAME_FULL: 星期三
*/
@RestController
public class UserController {
// @RequestMapping(path = "/user", method = RequestMethod.GET)
@GetMapping("/user")
public String getUser() {
return "GetUser";
}
// @RequestMapping(path = "/user", method = RequestMethod.POST)
@PostMapping("/user")
public String postUser() {
return "PostUser";
}
// @RequestMapping(path = "/user", method = RequestMethod.DELETE)
@DeleteMapping("/user")
public String deleteUser() {
return "DeleteUser";
}
// @RequestMapping(path = "/user", method = RequestMethod.PUT)
@PutMapping("/user")
public String putUser() {
return "PutUser";
}
}
application.yanl
# 开启 隐藏域 _method 方法判别
spring:
mvc:
hiddenmethod:
filter:
enabled: true
默认为 _method 自定义
// 自定义表单 hidden的名字
@Bean
public HiddenHttpMethodFilter diyHiddenFilter() {
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("m"); // 改为m 默认 _method
return hiddenHttpMethodFilter;
}
HTML网页
<form action="/user" method="post">
<input type="hidden" name="m" value="put">
<input type="submit" value="put">
</form>
2 请求参数
2.1 普通注解
(1)@PathVariable 获取路径参数,rest请求风格。
/**
* 请求路径 /user/1/zyx123
* @param id 代表id
* @param pwd
* @param map 默认把所有占位符的数据封装到 map集合中
* @outcome 1 zyx123
* {id=1, pwd=zyx123}
*/
@GetMapping("/user/{id}/{pwd}")
public String test(@PathVariable("id") Integer id,
@PathVariable("pwd") String pwd,
@PathVariable Map<String,Object> map) {
System.out.println(id+" "+pwd);
System.out.println(map);
return "success";
}
(2) @RequestParam 获取 k-v 类型的值
/**
* 1. 请求路径 /people?name=张三&hobbies=篮球&hobbies=足球
* @RequestParam("name") 指定获取参数名为name的值
* 2. 若为相同参数名对应多个数据值,则封装为map
* @RequestParam Map<String,String> map 对于多个数据,只会封装第一个数据
* 3. 输出结果
* 张三
* [篮球, 足球]
* {name=张三, hobbies=篮球}
* */
@GetMapping("/people")
public String test2(@RequestParam("name") String name,
@RequestParam("hobbies") List<String> hobby,
@RequestParam Map<String,String> map) {
// map 封装所有的请求参数
System.out.println(name);
System.out.println(hobby);
System.out.println(map);
return "request parameter";
}
(3)@RequestHeader 获得请求头。
@GetMapping("/people")
public String test2(@RequestHeader("User-Agent") String user,
@RequestHeader Map<String, Object> map) {
// map 封装所有的请求参数
System.out.println(user);
System.out.println(map);
return "request parameter";
}
(4)@CookieValue 获取cookie值。
@GetMapping("/people")
public String test2(@CookieValue("ajs_anonymous_id") String ajs_anonymous_id,
@CookieValue("ajs_anonymous_id") Cookie cookie) {
// 封装到 cookie
System.out.println(ajs_anonymous_id);
System.out.println(cookie.getName());
return "request parameter";
}
(5) @RequestBody 返回请求体的数据 POST方式提交
/**
* 返回 表单提交的数据 POST 提交方式
* @param content
* @return
*/
@GetMapping("/animal")
public String test3(@RequestBody String content ) {
// 封装到 cookie
System.out.println(content);
return "request parameter";
}
(6) @RequestAttribute 获取request域的数据
@GetMapping("/do")
public String forward(HttpServletRequest request) {
request.setAttribute("1","50"); // 对 request 域中存入属性值
request.setAttribute("code",200);
return "forward:/do2";
}
@ResponseBody
@GetMapping("/do2")
public Map<String, Object> forward2(@RequestAttribute("1") String c1,
@RequestAttribute("code") Integer code) {
Map<String, Object> map = new HashMap<>();
map.put("c1",c1);
map.put("code",code);
return map;
}
(7)@MatrixVariable 矩阵变量
如果cookie被禁用,如何找到session,解决方案 url重写 /abc;sessionID=xxx ,把cookie的值使用矩阵变量的方式进行传递。
开启矩阵变量
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false); // 设置为false
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
// /cars/sell;price=30;con=50,20,10
// /cars/sell;price=30;con=50;con=40;oon=20
// 矩阵变量要与{path} 路径变量 绑定
@GetMapping("/cars/{path}")
@ResponseBody
public Map<String, Object> test2(@MatrixVariable("price") Integer price,
@MatrixVariable("con") List<Integer> count,
@PathVariable("path") String path) {
Map<String, Object> map = new HashMap<>();
map.put("price",price);
map.put("count",count);
map.put("path",path);
return map;
}
输出结果
2.2 ServletAPI
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
处理源码
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
2.3 复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
Map、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap 是Model 也是Map
mavContainer.getModel(); 获取到值的
2.4 自定义POJO参数
自定义的参数,油前端提交的表单中各个属性的输入框的name属性与自定义对象的属性名保持一致,在controller方法中,使用该对象类型的参数去接受,自动封装到其参数类型。
前端提交的表单
<form action="/saveUser" method="get">
<input type="text" name="name" value="鲨鱼">
<input type="text" name="age" value="10">
<input type="text" name="pet.name" value="大黄">
<input type="text" name="pet.age" value="89"44>
<input type="submit">
</form>
Bean - 对象类
@Component
public class User {
private String name;
private int age;
private Pet pet;
}
public class Pet {
private String name;
private int age;
}
Controller层
@GetMapping("/saveUser")
public User saveUser (User user) {
return user;
}
返回对象参数
原理解析
1. ServletModelAttrtbuteMethodProcessor这个参数解析器支持。 判断是否支持解析:如果请求参数类型不是简单类型,ServletModelAttrtbuteMethodProcessor就可以支持解析。
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
2. ServletModelAttrtbuteMethodProcessor解析过程
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
解析: mavContainer.setBinding(name, ann.binding()); 创建了自定义空的实例。
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder 是Web数据绑定器,将请求参数的值绑定到指定的JavaBean里面,webRequest是原生的Servlert请求。
WebDataBinde中有ConversionService,ConversionService中的converters有124个converter,converter将Http传输的文本转为自定义类型需要的数据类型。
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean -- Integer)。
WebDataBinder 结构如下:
3、bindRequestParameters(binder, webRequest);这步执行完,自定义对象就会完成值绑定。
先原获取生request中的键值对,再用拿到的request中的属性值,利用converts转换成对应的数据类型,最后反射设置自定对象的所有属性值。
2.5 自定义converter (参数类型转换)
自定义规则接受数据,并封装到自定义的对象中。
前端数据,各个属性使用逗号分隔。
<form action="/editor_pet" method="post">
<input type="text" name="pet" value="鲨鱼,18">
<INPUT TYPE="submit">
</form>
接受的Controller
@PostMapping("/editor_pet")
public Pet editor_pet (Pet pet) {
return pet;
}
自定义的converter,实现转换
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
});
}
3. 数据响应与内容协商
3.1 响应json数据
在方法上注解:@RespondBody 注解,返回的就是json数据
Controller层
@Controller
public class RespondBodyController {
@ResponseBody
@GetMapping("/returnJson")
public User returnJson() {
User user1 = new User();
user1.setAge(10);
user1.setName("张三");
return user1;
}
}
数据返回
返回值器原理
1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
2、返回值处理器调用 handleReturnValue 进行处理
3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
- 1. 利用 MessageConverters 进行处理 将数据写为json
-
- 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
- 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
- 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
- 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
-
-
- 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
- 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去
- 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
-
内容协商:MVC对浏览器能返回什么内容,基于参数的内容协商:
# 请求参数携带format=内容格式,内容格式是什么就返回什么内容格式
# 如 https://localhost:5050/frmat=json 返回json数据的
spring:
mvc:
contentnegotiation:
favor-parameter: true
自定义MessageConverter
1. 基于亲求头的MessageConverter
(1)创建自定义的Converter
public class UserConverter implements HttpMessageConverter<User> {
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(User.class); // 判断返回值的类型是否为User
}
@Override
public List<MediaType> getSupportedMediaTypes() {
// 向服务器中,添加这一种类型
return MediaType.parseMediaTypes("application/x-user");
}
@Override
public User read(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(User user, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// 创建输出流 输出给客户端
HttpOutputMessage outputMessage1 = outputMessage; // 输出流
String data = user.getName()+";"+user.getAge() ;
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
(2)再WebMvcConfig添加该Converter
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new UserConverter());
}
2. 基于请求参数的内容协商
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json",MediaType.APPLICATION_JSON);
mediaTypes.put("xml",MediaType.APPLICATION_XML);
// 请求中携带参数 format=gg 对应的是媒体类型application/x-user 参考上面的自定义converter
mediaTypes.put("GG",MediaType.parseMediaType("application/x-user"));
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);
configurer.strategies(Arrays.asList(strategy));
}
返回结果
4. 视图解析与模板引擎
Thymelaf是一个适用于web和独立环境的现代服务器端Java模板引擎,能够处理HTML、XML、JavaScript、CSS甚至纯文本。
2、基本语法
2.1、表达式
表达式名字 | 语法 | 用途 |
变量取值 | ${...} | 获取请求域、session域、对象等值 |
选择变量 | *{...} | 获取上下文对象值 |
消息 | #{...} | 获取国际化等值 |
链接 | @{...} | 生成链接 |
片段表达式 | ~{...} | jsp:include 作用,引入公共页面片段 |
2.2、字面量
文本值: 'one text' , 'Another one!' ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false
空值: null
变量: one,two,.... 变量不能有空格
2.3、文本操作
字符串拼接: +
变量替换: |The name is ${name}|
2.4、数学运算
运算符: + , - , * , / , %
2.5、布尔运算
运算符: and , or
一元运算: ! , not
2.6、比较运算
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )
2.7、条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
3. 使用Thymelaf
controller层
@GetMapping("/viewtest")
public String test01(Model model) {
//向 model 中添加数据 ,共享在session中
model.addAttribute("date","2023.11.27");
model.addAttribute("link","https://wwww.baidu.com");
return "success";
}
前端页面
<!--使用 $取出session作用域中的属性值-->
<h1 th:text="${date}">hello</h1>
<!--${link} 取出作用域中link,为href赋值-->
<!--@{link} 将括号中的值 作为 href值 如果 该项目的上下文路径
发生改变,会自动添加发生的路径 -->
<a href="" th:href="${link}"><h2>hahaha</h2></a>
<a href="" th:href="@{link}"><h2>hahaha</h2></a>
结果值
解决表单重复提交
// 刷新 重复表单提交解决方法
@PostMapping("/index")
public String toIndex(User user, HttpSession session) {
if (!(StringUtils.isEmpty(user.getUserName()) && StringUtils.isEmpty(user.getPassword()))) {
session.setAttribute("loginUser",user);
return "redirect:index2";
}
else {
return "login";
}
}
@GetMapping("index2")
public String doubleLogin(HttpSession session) {
Object loginUser = session.getAttribute("loginUser");
if (loginUser != null) {
return "index";
}
return"login";
}
4 抽取公共页面
抽取到一个公共页面 common.html ,在标签中说明 ID 或者 fragment
在其他页面应用
th:replace | 外层标签相互替换 |
th:insert | 保留外层标签,将公共部分插入进去 |
th:include | 插入公共部分的里面的内容,保留外面标签 |
5. 数据遍历
users为放入session作用域的数据列表,status为当前遍历的状态
6. 拦截器
实现 handlerInterceptor
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if (loginUser != null) {
return true;
}
session.setAttribute("message","请先登录");
response.sendRedirect("/login");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
在配置类添加拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration interceptorRegistration = registry.addInterceptor(new LoginInterceptor());
// 拦截资源路径 /** 代表所有的路径
interceptorRegistration.addPathPatterns("/**");
// 打开静态资源,排除拦截之外的路径
interceptorRegistration.excludePathPatterns("/","/login",
"/css/**","/js/**","/fonts/**","/images/**");
}
}
拦截器原理
1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】
2、先来顺序执行 所有拦截器的 preHandle方法
- 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
- 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;
3、如果任何一个拦截器返回false。直接跳出不执行目标方法
4、所有拦截器都返回True。执行目标方法
5、倒序执行所有拦截器的postHandle方法。
6、前面的步骤有任何异常都会直接倒序触发 afterCompletion
7、页面成功渲染完成以后,也会倒序触发 afterCompletion
7. 文件上传
前端页面
<input type="file" name="headerImg" id="exampleInputFile">
<!-- multiple代表上传多个文件-->
<input type="file" name="photos" multiple>
接受请求的控制器函数
@PostMapping("loadFile")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("email={},username={},headerImh={},photos={}", email,username,headerImg.getSize(),
photos.length);
// 把文件上传到文件服务器中
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("E:\\PHTOTS\\" + originalFilename));
for (int i = 0; i < photos.length; i++) {
if (!photos[i].isEmpty()) {
String name = photos[i].getOriginalFilename();
photos[i].transferTo(new File("E:\\PHTOTS\\" + name));
}
}
return"index";
}
对上传文件的配置
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
# 上传文件最大文件大小10MB,上床文件总大小100MB
8. 错误处理
1、默认规则
- 默认情况下,Spring Boot提供
/error
处理所有错误的映射。 - 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据。
- 要对其进行自定义,添加
View
解析为error
- 要完全替换默认行为,可以实现
ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes类型的组件
以使用现有机制但替换其内容。 - error/下的4xx,5xx页面会被自动解析;
自定义异常处理器
@ControllerAdvice
public class ErrorException {
// 处理对应的异常 所执行的方法
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String handle() {
return "index";
}
}
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserToManyError extends RuntimeException{
public UserToManyError() {
}
// 可以将错误信息放入message中
public UserToManyError(String message) {
super(message);
}
}
9. 原生组件注入(Servlet,Filter,Listener)
1. servletAPI
可以使用三个注解声明,servlet原生API如下,同时需要在配置类中注明,原生组件所扫描的包:@ServletComponentScan(basePackages = "com.sanker"),自定义servlet不会被拦截器拦截,因为不会通过dispatchservlet。
servlet | @WebServlet |
过滤器Flter | @WebFilter |
监听器 listener | @WebListeren |
@WebFilter // 过滤器
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletResponse.getWriter().write("666");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
@WebListener // 监听器
public class MyListener implements ServletContextListener {
// 项目初始化
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
}
// 项目销魂
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
@WebServlet
public class MyServlet extends HttpServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
super.service(req, res);
}
}
2. RegistrationBean 方式【不推荐】
@Configuration(proxyBeanMethods = true)
public class MyRegistrationBean {
@Bean
public ServletRegistrationBean getServlet() {
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean((Servlet) myServlet,"/my","/my02");
}
@Bean
public FilterRegistrationBean getFilter() {
MyFilter myFilter = new MyFilter();
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter((Filter) myFilter);
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean getListener() {
MyListener myListener = new MyListener();
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
servletListenerRegistrationBean.setListener(myListener);
return servletListenerRegistrationBean;
}
5. 数据库数据访问
1. SQL - MySQL
1.1 基本使用
导入数据库连接池
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
导入数据库驱动程序
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
在yaml配置文件注册数据源信息 spring.datasource
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_name
username: root
password: liubo321
driver-class-name: com.mysql.jdbc.Driver
可以通过 spring.jdbc.xxxx , 修改jdbcTemplate配置项
1.2 更改数据库连接池Druid
// 配置druid数据库的连接池基本信息
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
// 与配置文件前缀绑定 spring.datasource
public DataSource dataSource () {
DruidDataSource dataSource = new DruidDataSource();
// dataSource.setUrl();
// dataSource.setDriverClassName();
// dataSource.setPassword();
return dataSource;
}
1.3 配置druid内置监控页
参考官方网址 GitHub网址
2 整合MyBatis
导入依赖
<!--MyBatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
若修改MyBatis的配置信息,可以在配置文件以mybatis开头配置
mapper层,只需添加@mapper注解,就可以自动扫描
1. 配置文件模式
# 配置mybatis规则
mybatis:
config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
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.atguigu.admin.mapper.AccountMapper">
<!-- public Account getAcct(Long id); -->
<select id="getAcct" resultType="com.atguigu.admin.bean.Account">
select * from account_tbl where id=#{id}
</select>
</mapper>
也可以在yaml文件修改mybatis的配置
# 配置mybatis规则
mybatis:
# config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
map-underscore-to-camel-case: true
可以不写全局;配置文件,所有全局配置文件的配置都放在configuration配置项中即可
2. 注解方式启动mybatics
controller层接受请求,找到对应请求的方法,获取请求参数,调用service层,service调用mapper,mapper使用sql语句,操作对应的表。
mapper层
@Mapper
public interface StudentMapper {
@Select("select * from student where Sno = #{Sno}")
public Student selectStudentByNo(String Sno);
}
service层
@Service
public class StudentService {
@Autowired
private StudentMapper studentMapper;
public Student queryStudent(String Sno) {
return studentMapper.selectStudentByNo(Sno);
}
}
controller层
@ResponseBody
@GetMapping("/student")
public String getStudent() {
return service.queryStudent("10001").toString();
}
3 整合mybatis-plus
1. mybatis-plus的基础使用。
下载mybatisX ,助于开发
添加依赖
<!--导入MySQL驱动程序-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
编写mapper继承baseMapper 实现简单的sql语句查询
public interface StudentMapper extends BaseMapper<Student> {
}
// 调用mapper查询数据库
@Autowired
StudentMapper studentMapper;
@Test
void getStudent() {
List<Student> students = studentMapper.selectList(null);
students.forEach(System.out::println);
}
2. 实现分页数据
UserService 继承勒Iservice,获取固定的方法
// Mapper层 调用数据库
public interface UserMapper extends BaseMapper<TableUser> {
}
// service 层 继承mybatis-plus 中 Iservice 获取其中接口,以方便书写
public interface UserInfoService extends IService<TableUser> {
}
// ServiceImpl 实现层
/**
erviceImpl<UserMapper,TableUser>
第一个参数 传入Mapper层 ,第二个返回的结果类型
*/
public class UserInfoServiceImpl extends ServiceImpl<UserMapper,TableUser> implements UserInfoService {
}
// controller层
@GetMapping("dynamic_table")
public String dynamicTable(@RequestParam(value = "pn",defaultValue = "1")
Integer pn, Model model) {
// 获取数据当前页对象 第一页 两条记录
Page<TableUser> tableUserPage = new Page<>(pn,2);
IPage<TableUser> page = userInfoService.page(tableUserPage,null);
// page 对象包含了所有信息如 查询的结果 当前页数 和总数
model.addAttribute("page",page);
return"table/dynamic_table";
}
添加分页插件
@Configuration
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件 DbType:数据库类型
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
前端页面
<tr class="gradeX" th:each="user,status:${page.records}">
<td th:text="${status.count}">Trident</td>
<td th:text="${user.id}">Internet 4.0</td>
<td th:text="${user.userName}">Win 95+</td>
<td th:text="${user.password}">Win 95+</td>
<td th:text="${user.name}">Win 95+</td>
<td th:text="${user.age}">Win 95+</td>
<td>[[${user.email}]]</td>
<td class="center hidden-phone" style="background-color: red">X</td>
</tr>
<li th:class="${num == page.current?'active':''}" th:each="num: ${#numbers.sequence(1,page.pages)}">
<a th:href="@{/dynamic_table(pn=${num})}">[[${num}]]</a>
</li>
2. 删除用户数据并停留在当前页面中
前端页面 thyme leaf 超链接动态传递多个参数
<a th:href="@{/delete(id=${user.id},pn=${page.current})}">
<button style="width: 10px;height: 10px;color: white;background-color: red">删除</button>
</a>
@GetMapping("delete")
public String delUser(@RequestParam("id") Integer id,
@RequestParam(value = "pn",defaultValue = "1") Integer pn, RedirectAttributes redirectAttributes) {
userInfoService.removeById(id);
redirectAttributes.addAttribute("pn",pn);
return "redirect:dynamic_table";
}
4 Redis数据库操作
记录访问连接的次数
1. 添加依赖
<!-- Redis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redis数据连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
拦截器
public class UrlInterceptor implements HandlerInterceptor {
@Autowired
StringRedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURL = request.getRequestURI();
redisTemplate.opsForValue().increment(requestURL);
return true;
}
}
6. 单元测试
1. Junit5 常用注解
-
@Test
- 用于标记测试方法,表示该方法是一个测试用例。
- 使用场景:标记单元测试方法,测试特定功能或代码块。
-
@BeforeEach
- 在每个测试方法执行之前执行。
- 使用场景:进行测试前的准备工作,如初始化对象或资源。
-
@AfterEach
- 在每个测试方法执行之后执行。
- 使用场景:进行测试后的清理工作,如释放资源或重置对象状态。
-
@BeforeAll
- 在所有测试方法执行之前执行,只会执行一次。
- 使用场景:执行一些全局的初始化工作,如启动一个数据库服务。
-
@AfterAll
- 在所有测试方法执行之后执行,只会执行一次。
- 使用场景:执行一些全局的清理工作,如关闭数据库连接。
-
@DisplayName
- 用于为测试类或测试方法设置自定义的显示名称。
- 使用场景:提高测试报告的可读性,更清晰地描述测试目的。
-
@Disabled
- 用于禁用测试类或测试方法,不会执行被标记的测试。
- 使用场景:暂时屏蔽某些测试用例,或者标记已知不可用的测试。
-
@ParameterizedTest
- 用于指定参数化测试方法,可以多次运行同一个测试方法,每次使用不同的参数。
- 使用场景:测试同一个逻辑在不同参数下的行为,提高测试覆盖范围。
2. 断言机制
-
assertEquals():
- 用于验证两个值是否相等。
- 语法:
assertEquals(expected, actual)
。
-
assertTrue():
- 用于验证给定的条件是否为真。
- 语法:
assertTrue(condition)
。
-
assertFalse():
- 用于验证给定的条件是否为假。
- 语法:
assertFalse(condition)
。
-
assertNotNull():
- 用于验证给定的对象是否非空。
- 语法:
assertNotNull(object)
。
-
assertNull():
- 用于验证给定的对象是否为空。
- 语法:
assertNull(object)
。
-
assertSame():
- 用于验证两个对象是否引用同一个对象。
- 语法:
assertSame(expected, actual)
。
-
assertNotSame():
- 用于验证两个对象是否不是同一个对象。
- 语法:
assertNotSame(expected, actual)
。
这些断言方法通常用于单元测试框架(如JUnit、TestNG等)中,用来编写测试用例时进行断言操作,以验证程序的行为是否符合预期。它们提供了更灵活、更丰富的断言功能,可以更方便地进行测试。
3. 参数化测试
JUnit是一个常用的Java单元测试框架,它提供了参数化测试的支持。下面是介绍如何在JUnit中进行参数化测试的用法:
-
使用
@ParameterizedTest
注解:在测试类中,通过@ParameterizedTest
注解来标记参数化测试方法。这个注解告诉JUnit这是一个参数化测试方法。 -
提供参数源:在参数化测试方法上方,需要使用
@MethodSource
注解来指定参数源。参数源可以是一个静态方法,它会返回一个Stream对象,该Stream包含了多组参数。 -
编写测试方法:编写参数化测试方法,该方法的参数列表应该和参数源方法返回的参数类型一致,JUnit会自动注入参数并执行测试。
下面是一个简单的示例,演示了如何在JUnit中使用参数化测试:
javaCopy Code
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MyParameterizedTest {
// 参数源方法,返回一个Stream对象,包含多组参数
static Stream<Arguments> provideTestData() {
return Stream.of(
Arguments.of(0, 0, 0), // 参数组1
Arguments.of(1, 2, 3), // 参数组2
Arguments.of(-1, 1, 0) // 参数组3
);
}
// 参数化测试方法,使用 @ParameterizedTest 和 @MethodSource 注解
@ParameterizedTest
@MethodSource("provideTestData") // 指定参数源方法
void testAdd(int a, int b, int expectedResult) {
Calculator calculator = new Calculator();
int result = calculator.add(a, b);
assertEquals(expectedResult, result); // 断言结果是否符合预期
}
}
在上面的示例中,provideTestData()
方法是参数源方法,它返回了一个Stream对象,其中包含了三组参数。然后,testAdd()
方法被@ParameterizedTest
注解标记为参数化测试方法,并通过@MethodSource
注解指定了参数源方法。JUnit会自动从参数源中获取参数,并将其注入到testAdd()
方法中执行测试。