入门开发
入门案例
创建一个Maven工程
<!-- 父工程项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.14</version>
</parent>
<dependencies>
<!-- Web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!-- 打包-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
创建主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}
编写controller
package com.demo1.boot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
//@RestController == @Controller+@ResponseBody
@RestController
public class HelloController {
@RequestMapping("/")
public String HelloSpringBoot01(){
return "Hello Spring Boot";
}
}
访问地址
在resource目录下编写配置文件application.yml
server:
port: 80
Maven打包测试
依赖管理和自动配置原理
- 开发导入starter场景启动器
- 无需关注版本号,自动版本仲裁
- 引入依赖默认都可以不写版本
- 引入非版本仲裁的jar,要写版本号
- 可以修改默认版本号
在pom.xml中添加
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
在<dependencies>中添加mysql的依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
- 默认的包结构
- 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
- 无需以前的包扫描配置
- 想要改变扫描路径可以通过@SpringBootApplication(scanBasePackages=“com.atguigu”) 或者@ComponentScan 指定扫描路径
- 各种配置拥有默认值
- 默认配置最终都是映射到某个类上,如:MultipartProperties
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
- 按需加载所有自动配置项
- 非常多的starter
- 引入了哪些场景这个场景的自动配置才会开启
- SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.boot")
就是使用一个注解等于使用了三个注解
容器功能
组件添加
@Configuration
- @Configuration
- 告诉SpringBoot这是一个配置类 == 配置文件
- 在方法上通过使用@Bean注解给容器注册一个组件
- 本身也是一个组件(类也是一个组件)
- proxyBeanMethods:默认为true
- 无论外部调用多少次,都是获取到之前从容器中注册到的对象
- 如果代理对象调用方法,SpringBoot总会检查组件中是否有实例,就不会创建新实例
- @Bean
- 给容器中添加组件,以方法名作为组件的id
- 返回类型就是组件类型
- 返回的值,就是组件在容器中的实例
- @Bean(“us”)自定义组件名
- 默认是单例模式 - Full、Lite
- Full: @Configuration(proxyBeanMethods=true) 组件依赖
- Lite:@Configuration(proxyBeanMethods=false)
- 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
- 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
import com.demo1.boot.config.MyConfig;
import com.demo1.boot.vo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
// 返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
// 查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
// 获取容器中的组件
User user1 = run.getBean("us", User.class);
User user2 = run.getBean("us", User.class);
System.out.println(user1 == user2);
MyConfig myConfig = run.getBean(MyConfig.class);
User user3 = myConfig.user();
User user4 = myConfig.user();
System.out.println(user3 == user4);
}
}
@Import
- @Import({User.class,MyConfig.class})
- 给容器中自动创建出这两个类型的组件
- 默认组件的名字就是全类名
@Conditional
条件装配:满足Conditional指定的条件,则进行组件注入
- @ConditionalOnBean(name = “”)
- 当有指定名字的组件是就进行注册
- 否则不注册
- 当在配置类上时,如果满足条件才能执行下面的组件注册,否则不执行
原生配置文件引入
@ImportResource
- @ImportResource(“classpath:test.xml”)
- 相当于把xml中配置的容器在配置类中进行注册
配置绑定
使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用
@ConfigurationProperties+ @Component
在yml文件中
mycar:
name: "byd"
money: 100
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String name;
private int money;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", money=" + money +
'}';
}
}
@Autowired
private Car car;
@RequestMapping("/getCar")
public String getCar(){
return car.toString();
}
@EnableConfigurationProperties+ @ConfigurationProperties
- @EnableConfigurationProperties(Car.class)
- 开启属性配置功能
- 把这个组件自动注册到容器中
自动包规则
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
- @SpringBootConfiguration
- 底层是核心配置类
- @ComponentScan
- spring注解,自带2个扫描器
- 主要用于指定扫描路径
- @EnableAutoConfiguration
@AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration
-
@AutoConfigurationPackage
- 自动配置包
@Import({Registrar.class}) public @interface AutoConfigurationPackage
-
就是利用Registrar给容器批量导入一系列组件
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])); } public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata)); } } // registerBeanDefinitions /* 1.传了两个参数 2.AnnotationMetadata metadata 相当于是注解信息 就相当于是标在SpringApplication.run那个类上的注解信息 3.第二个参数就是获取包名并封装成一个数组 4.具体作用是把某一个包下的所有组件批量注册 */
-
@Import({AutoConfigurationImportSelector.class})
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } } /* 1. 调用this.getAutoConfigurationEntry(annotationMetadata)方法 2. 给容器中导入一些组件 3. 调用this.getCandidateConfigurations(annotationMetadata, attributes) 4. 获取到所有需要导入到容器中的配置类 5. 利用工厂加载 Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) 得到所有组件 6. 从META-INF/spring.factories位置来加载一个文件。 7. 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件 8. spring-boot-autoconfigure-2.5.44.jar包里面也有META-INF/spring.factories 9. 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类(127个) */
-
按需配置
- 按照条件装配规则(@Conditional),最终会按需配置。
-
自动配置流程
- SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
- SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于这些功能就有了
- 定制化配置
- 用户直接自己@Bean替换底层的组件
- 用户去看这个组件是获取的配置文件什么值就去修改。
- xxxxxAutoConfiguration >>> 组件 >>> xxxxProperties里面拿值 >>>application.properties
如何编写
- 引入场景依赖
- 查看自动配置了哪些(选做)
- 自己分析,引入场景对应的自动配置一般都生效了
- 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
- 是否需要修改
- 参照文档修改配置项
- 自己分析。xxxxProperties绑定了配置文件的哪些。
- 自定义加入或者替换组件
- @Bean、@Component
- 自定义器 XXXXXCustomizer
- 参照文档修改配置项
开发技巧
简化开发-lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
- 需要在idea中装上插件
- 可以不用写get、set、构造方法,由lombok在编译时生成
- @Data注解生成get、set方法,@ToString 生成toString方法,@AllArgsConstructor 生成全参构造方法,@NoArgsConstructor生成无参构造,@EqualsAndHashCode生成重写equals和hashCode方法,@Slf4j生成日志
热(重)部署-devtools
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
- 就是代码更改后可以进行重新部署
- 本质上还是重启应用 >>> JRebel,热部署(付费)
- 热部署只是针对class文件,源文件这些。。对于属性文件.xml或.properties或.yaml等,只要修改了,都需要重启服务器,因为属性文件是在服务器启动的时候加载的,不可能被热部署识别到。
核心
yaml文件说明
基本语法
- key: value;kv之间有空格
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释
- 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义
数据类型
-
字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
-
键值对的集合。map、hash、set、object
行内写法: k: {k1:v1,k2:v2,k3:v3} #或 k: k1: v1 k2: v2 k3: v3
-
数组:一组按次序排列的值。array、list、queue
行内写法: k: [v1,v2,v3] #或者 k: - v1 - v2 - v3
@Data
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salarys;
private Map<String, List<Pet>> allPets;
}
@Data
public class Pet {
private String name;
private Double weight;
}
# yaml表示以上对象
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
pet:
name: tomcat
weight: 23.4
interests: [篮球,游泳]
animal:
- jerry
- mario
score:
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: {first: 128,second: 136}
salarys: [3999,4999.98,5999.99]
allPets:
sick:
- {name: tom}
- {name: jerry,weight: 47}
health: [{name: mario,weight: 47}]
<!-- 对标yml文件提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
Web开发
静态资源
- 静态资源目录
- 只要静态资源放在类路径下: resource目录下的 /static (or /public or /resources or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
- 只要静态资源放在类路径下: resource目录下的 /static (or /public or /resources or /META-INF/resources
原理: 静态映射 /**
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
- 改变静态资源路径
spring:
mvc:
static-path-pattern: /res/** # 表示需要加上前缀
web:
resources:
static-locations: [classpath:/abc/] # 指定静态资源
add-mappings: false # 禁用所有静态资源
cache:
period: 100000 # 缓存时间
- 网页图标
- 放在静态资源下,但是不能使用静态资源前缀,否则不生效
- 放在静态资源下,但是不能使用静态资源前缀,否则不生效
请求处理
- Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
- 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter;HiddenHttpMethodFilter
- 用法: 表单method=post,隐藏域 _method=put
- SpringBoot中手动开启
@RequestMapping(value = "/hello" , method = RequestMethod.DELETE)
public String Hello(){
return "访问请求";
}
mvc:
hiddenmethod:
filter:
enabled: true #开启Restful风格请求
//自定义filter名称
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
基本注解和参数
注解
- 注解:
-
@PathVariable 用于Restful风格请求
-
@RequestHeader用于获取请求头
-
@RequestParam用于普通传参
-
@CookieValue用于获取Cookie
-
以上参数如果不指定就是全部获取所有参数的意思但接收类型必须是Map类型
-
@RequestBody用于获取请求体(一般是post请求)
-
@CookieValue 想要获取全部必须是Cookie类型
-
@RequestAttribute用于获取request域中数据
-
@MatrixVariable用于矩阵变量,用于cookie被禁用时,默认禁用
-
拦截器
HandlerInterceptor 接口
package com.demo.springbootagainstudyweb.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
// 1.配置拦截器
// 2.把拦截器放入容器中,使用config
public class LoginInterceptor implements HandlerInterceptor {
@Override
// 在目标方法执行前执行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录检查逻辑
// 判断session中是否存在登录信息
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if (loginUser != null){
return true;
}
session.setAttribute("msg","请先登录!"); // 返回前端信息
response.sendRedirect("/"); //重定向到登录界面
return false;
}
@Override
// 在目标方法执行完后执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
// 在页面渲染完成后执行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
配置Config
package com.demo.springbootagainstudyweb.config;
import com.demo.springbootagainstudyweb.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
// 1. 编写一个拦截器实现HandlerInterceptor接口
// 2.拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
// 3.指定拦截规则【如果是拦截所有,静态资源也会被拦截】
public class InterceptorWebConfig implements WebMvcConfigurer {
@Override
// 添加拦截器方法
public void addInterceptors(InterceptorRegistry registry) {
// registry拦截器注册中心
registry.addInterceptor(new LoginInterceptor()) // 添加自定义的拦截器
.addPathPatterns("/**") // 添加拦截路径
.excludePathPatterns("/login"); // 放行哪些路径
}
}
执行原理
- 根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】
- 先来顺序执行 所有拦截器的 preHandle方法
- 如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
- 如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;
- 如果任何一个拦截器返回false。直接跳出不执行目标方法
- 所有拦截器都返回True。执行目标方法
- 倒序执行所有拦截器的postHandle方法。
- 前面的步骤有任何异常都会直接倒序触发 afterCompletion
- 页面成功渲染完成以后,也会倒序触发 afterCompletion
文件上传
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
/**
* MultipartFile 自动封装上传过来的文件
* @param username
* @param headerImg
* @param photos
* @return
*/
@PostMapping("/upload")
public String upload(@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("上传的信息:email={},username={},headerImg={},photos={}",
email,username,headerImg.getSize(),photos.length);
if(!headerImg.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("C:\\cache\\"+originalFilename));
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("H:\\cache\\"+originalFilename));
}
}
}
return "index";
}
异常处理
- 默认情况下,Spring Boot提供/error处理所有错误的映射
- 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
- 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
- error/下的4xx,5xx页面会被自动解析;
- 自定义错误页
- error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
- @ControllerAdvice+@ExceptionHandler处理全局异常
- 底层是 ExceptionHandlerExceptionResolver 支持的
- @ResponseStatus+自定义异常
- 底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
定制化
-
修改配置文件;
-
xxxxxCustomizer;
-
编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;视图解析器
-
Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能+ @Bean给容器中再扩展一些组件
-
@EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能
- 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页…
- 2、一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.class)
- 3、DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用
- 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
- 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
- public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
- 4、WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
- 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。
单元测试
JUnit5
介绍
- Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。- JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
- JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
- JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。
- JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- SpringBoot整合Junit以后。
- 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
- Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
常用注解
- @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest :表示方法可重复执行,下方会有详细介绍
- @DisplayName :为测试类或者测试方法设置展示名称
- @BeforeEach :表示在每个单元测试之前执行
- @AfterEach :表示在每个单元测试之后执行
- @BeforeAll :表示在所有单元测试之前执行
- @AfterAll :表示在所有单元测试之后执行
- @Tag :表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith :为测试类或测试方法提供扩展类引用
断言
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
检查业务逻辑返回的数据是否合理。
所有的测试运行结束以后,会有一个详细的测试报告;
1.用来对单个值进行简单的验证。如:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
2.数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
3.组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
4.异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用
5.超时断言
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间
6.快速失败
通过 fail 方法直接使得测试失败
前置条件(assumptions)
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。
迁移
在进行迁移的时候需要注意如下的变化:
● 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
● 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
● 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
● 把@Ignore 替换成@Disabled。
● 把@Category 替换成@Tag。
● 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。
指标监控
SpringBoot Actuator
简介
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
使用
- 引入场景
- 访问 http://localhost:8080/actuator/**
- 暴露所有监控信息为HTTP
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
Actuator Endpoint
- 最常使用的端点
ID | 描述 |
---|---|
auditevents | 暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件。 |
beans | 显示应用程序中所有Spring Bean的完整列表。 |
caches | 暴露可用的缓存。 |
conditions | 显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops | 显示所有@ConfigurationProperties。 |
env | 暴露Spring的属性ConfigurableEnvironment |
flyway | 显示已应用的所有Flyway数据库迁移。 |
需要一个或多个Flyway组件。 | |
health | 显示应用程序运行状况信息。 |
httptrace | 显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。 |
info | 显示应用程序信息。 |
integrationgraph | 显示Spring integrationgraph 。需要依赖spring-integration-core。 |
loggers | 显示和修改应用程序中日志的配置。 |
liquibase | 显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。 |
metrics | 显示当前应用程序的“指标”信息。 |
mappings | 显示所有@RequestMapping路径列表。 |
scheduledtasks | 显示应用程序中的计划任务。 |
sessions | 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown | 使应用程序正常关闭。默认禁用。 |
startup | 显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup。 |
threaddump | 执行线程转储。 |
如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点
ID | 描述 |
---|---|
heapdump | 返回hprof堆转储文件。 |
jolokia | 通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core。 |
logfile | 返回日志文件的内容(如果已设置logging.file.name或logging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。 |
prometheus | 以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus。 |
最常用的Endpoint
- Health:监控状况
- Metrics:运行时指标
- Loggers:日志记录
管理Endpoints
-
开启与禁用Endpoints
- 默认所有的Endpoint除过shutdown都是开启的。
- 需要开启或者禁用某个Endpoint。配置模式为 management.endpoint..enabled = true
management: endpoint: beans: enabled: true
- 或者禁用所有的Endpoint然后手动开启指定的Endpoint
management: endpoints: enabled-by-default: false endpoint: beans: enabled: true health: enabled: true
-
暴露Endpoints
- 支持的暴露方式
- HTTP:默认只暴露health和info Endpoint
- JMX:默认暴露所有Endpoint
- 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID | JMX | Web |
---|---|---|
auditevents | Yes | No |
beans | Yes | No |
caches | Yes | No |
conditions | Yes | No |
configprops | Yes | No |
env | Yes | No |
flyway | Yes | No |
health | Yes | Yes |
heapdump | N/A | No |
httptrace | Yes | No |
info | Yes | Yes |
integrationgraph | Yes | No |
jolokia | N/A | No |
logfile | N/A | No |
loggers | Yes | No |
liquibase | Yes | No |
metrics | Yes | No |
mappings | Yes | No |
prometheus | N/A | No |
scheduledtasks | Yes | No |
sessions | Yes | No |
shutdown | Yes | No |
startup | Yes | No |
threaddump | Yes | No |
定制 Endpoint
-
定制 Health 信息
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class MyHealthIndicator implements HealthIndicator { @Override public Health health() { int errorCode = check(); // perform some specific health check if (errorCode != 0) { return Health.down().withDetail("Error Code", errorCode).build(); } return Health.up().build(); } } //构建Health Health build = Health.down() .withDetail("msg", "error service") .withDetail("code", "500") .withException(new RuntimeException()) .build();
management: health: enabled: true show-details: always #总是显示详细信息。可显示每个模块的状态信息
@Component public class MyComHealthIndicator extends AbstractHealthIndicator { /** * 真实的检查方法 * @param builder * @throws Exception */ @Override protected void doHealthCheck(Health.Builder builder) throws Exception { //mongodb。 获取连接进行测试 Map<String,Object> map = new HashMap<>(); // 检查完成 if(1 == 2){ // builder.up(); //健康 builder.status(Status.UP); map.put("count",1); map.put("ms",100); }else { // builder.down(); builder.status(Status.OUT_OF_SERVICE); map.put("err","连接超时"); map.put("ms",3000); } builder.withDetail("code",100) .withDetails(map); } }
-
定制info信息
- 编写配置文件
info: appName: boot-admin version: 2.0.1 mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值 mavenProjectVersion: @project.version@
- 编写InfoContributor
import java.util.Collections; import org.springframework.boot.actuate.info.Info; import org.springframework.boot.actuate.info.InfoContributor; import org.springframework.stereotype.Component; @Component public class ExampleInfoContributor implements InfoContributor { @Override public void contribute(Info.Builder builder) { builder.withDetail("example", Collections.singletonMap("key", "value")); } }
http://localhost:8080/actuator/info 会输出以上方式返回的所有info信息
-
定制Metrics信息
-
SpringBoot支持自动适配的Metrics
- JVM metrics, report utilization of:
- Various memory and buffer pools
- Statistics related to garbage collection
- Threads utilization
- Number of classes loaded/unloaded
- CPU metrics
- File descriptor metrics
- Kafka consumer and producer metrics
- Log4j2 metrics: record the number of events logged to Log4j2 at each level
- Logback metrics: record the number of events logged to Logback at each level
- Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
- Tomcat metrics (server.tomcat.mbeanregistry.enabled must be set to true for all Tomcat metrics to be registered)
- Spring Integration metrics
- JVM metrics, report utilization of:
-
增加定制Metrics
class MyService{ Counter counter; public MyService(MeterRegistry meterRegistry){ counter = meterRegistry.counter("myservice.method.running.counter"); } public void hello() { counter.increment(); } } //也可以使用下面的方式 @Bean MeterBinder queueSize(Queue queue) { return (registry) -> Gauge.builder("queueSize", queue::size).register(registry); }
-
-
定制Endpoint
@Component
@Endpoint(id = "container")
public class DockerEndpoint {
@ReadOperation
public Map getDockerInfo(){
return Collections.singletonMap("info","docker started...");
}
@WriteOperation
private void restartDocker(){
System.out.println("docker restarted....");
}
}
其他
Profile功能
- application-profile功能
- 默认配置文件 application.yaml;任何时候都会加载
- 指定环境配置文件 application-{env}.yaml
- 激活指定环境
- 配置文件激活
- 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
- 修改配置文件的任意值,命令行优先
- 默认配置与环境配置同时生效
- 同名配置项,profile配置优先
- @Profile条件装配功能
@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
// ...
}
- profile分组
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
#使用:--spring.profiles.active=production 激活
外部配置源
- 常用:Java属性文件、YAML文件、环境变量、命令行参数;
- 配置文件查找位置
- classpath 根路径
- classpath 根路径下config目录
- jar包当前目录
- jar包当前目录的config目录
- /config子目录的直接子目录
- 配置文件加载顺序:
- 当前jar包内部的application.properties和application.yml
- 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
- 引用的外部jar包的application.properties和application.yml
- 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
- 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项
自定义starter
-
starter启动原理
- starter-pom引入 autoconfigurer 包
- starter-pom引入 autoconfigurer 包
-
autoconfigure包中配置使用 META-INF/spring.factories 中 EnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类
-
编写自动配置类 xxxAutoConfiguration -> xxxxProperties
- @Configuration
- @Conditional
- @EnableConfigurationProperties
- @Bean
- …
引入starter — xxxAutoConfiguration — 容器中放入组件 ---- 绑定xxxProperties ---- 配置项
-
创建一个空项目
-
添加2个module(maven)
-
一个是启动器,一个是自动配置
-
编写自动配置
业务类
import com.hello.bean.HelloProperties; import org.springframework.beans.factory.annotation.Autowired; // 默认不要放在容器中 public class HelloService { @Autowired private HelloProperties helloProperties; public String sayHello(String name){ return helloProperties.getPrefix()+":"+name+" >>> "+ helloProperties.getSuffix(); } }
自动配置类
import com.hello.bean.HelloProperties; import com.hello.service.HelloService; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(HelloProperties.class) //开启属性文件绑定功能并且HelloProperties放在容器中 public class HelloAutoConfiguration { @ConditionalOnMissingBean(HelloService.class) //当容器中没有配置HelloService时候才生效 @Bean public HelloService helloService(){ return new HelloService(); } }
属性配置类
import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("hello") public class HelloProperties { private String prefix; private String suffix; public HelloProperties() { } public HelloProperties(String prefix, String suffix) { this.prefix = prefix; this.suffix = suffix; } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } }
在resource目录下创建META-INF文件夹,编写spring.faactories文件,可以模仿springboot的自动配置jar来编写
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.myw.hello.config.HelloAutoConfiguration
-
maven保存进本地仓库
-
在启动器添加自动配置的
把启动器添加进本地maven
以上就是自定义starter的步骤
SpringBoot启动过程
- 创建 SpringApplication
- 保存一些信息。
- 判定当前应用的类型。ClassUtils。Servlet
- bootstrappers:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.Bootstrapper
- 找 ApplicationContextInitializer;去spring.factories找 ApplicationContextInitializer
- List<ApplicationContextInitializer<?>> initializers
- 找 ApplicationListener ;应用监听器。去spring.factories找 ApplicationListener
- List<ApplicationListener<?>> listeners
- 运行 SpringApplication
- StopWatch
- 记录应用的启动时间
- 创建引导上下文(Context环境)createBootstrapContext()
- 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
- 让当前应用进入headless模式。java.awt.headless
- 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
- getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener.
- 遍历 SpringApplicationRunListener 调用 starting 方法;
- 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
- 保存命令行参数;ApplicationArguments
- 准备环境 prepareEnvironment();
- 返回或者创建基础环境信息对象。StandardServletEnvironment
- 配置环境信息对象。
- 读取所有的配置源的配置属性值。
- 绑定环境信息
- 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
- 创建IOC容器(createApplicationContext())
- 根据项目类型(Servlet)创建容器,
- 当前会创建 AnnotationConfigServletWebServerApplicationContext
- 准备ApplicationContext IOC容器的基本信息 prepareContext()
- 保存环境信息
- IOC容器的后置处理流程。
- 应用初始化器;applyInitializers;
- 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
- 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared
- 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
- 刷新IOC容器。refreshContext
- 创建容器中的所有组件(Spring注解)
- 容器刷新完成后工作 afterRefresh
- 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
- 调用所有runners;callRunners()
- 获取容器中的 ApplicationRunner
- 获取容器中的 CommandLineRunner
- 合并所有runner并且按照@Order进行排序
- 遍历所有的runner。调用 run 方法
- 如果以上有异常,
- 调用Listener 的 failed
- 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
- running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed