springboot3学习笔记

#1 SpringBoot简介
雷丰阳老师https://www.yuque.com/leifengyang/springboot3/wuil67bq85d88gso
SpringBoot 是框架的框架,能够帮我们简单、快速地创建一个独立的、生产级别的 Spring 应用。它能够简化开发,简化配置,简化整合,简化部署,简化监控,简化运维。

  • SpringBoot根据不同的场景提供了不同的starter,例如web-startermybatis-starter。它能够将场景所需的依赖自动进行导入并进行版本控制。
  • SpringBoot能够按需自动配置 Spring 以及 第三方库。
    约定大于配置:每个场景都有很多默认配置。如需自定义,可利用application.properties 配置文件对整个项目进行统一配置。

1.1 自动配置机制

1.导入场景启动器,会将该场景所需的所有依赖进行导入(maven的依赖传递)。
2.所有的场景启动器都引入了一个spring-boot-starter,核心场景启动器,而spring-boot-starter引入了spring-boot-autoconfigure包。
3.spring-boot-autoconfigure里面囊括了所有场景的所有配置(通过配置类实现)。只要该配置类生效,则自动配置生效。
4.按需配置原理:主程序:@SpringBootApplication,该注解主要由三个注解组成:@SpringBootConfiguration@EnableAutoConfiguratio@ComponentScan

  • @ComponentScan:开启组件扫描,默认只扫描主程序所在包及子包。
  • @EnableAutoConfiguration:SpringBoot 开启自动配置的核心。是由 @Import(AutoConfigurationImportSelector.class) 提供功能:批量给容器中导入组件。项目启动的时候利用 @Import 批量导入组件机制把 autoconfigure 包下的自动配置类导入进来。而组件扫描扫描不到,再利用条件注解使得自动配置类生效。自动配置类中通过@bean给容器中提供相关的组件。

5.组件默认值:容器中放的所有组件的一些核心参数,通过@EnableConfigurationProperties注解与xxxProperties.class绑定,而xxxProperties类中有组件的默认值,且类中的属性通过@ConfigurationProperties( prefix = "xxx")注解和配置文件绑定(换句话说,只有xxxProperties.class类中的可以修改的属性才能再配置文件中进行重新配置)。只需要改配置文件的值,核心组件的底层参数都能修改
(修改方法:在配置文件中直接写xxx.属性名=所需值)
效果:开发者只需导入starter、修改配置文件,即可定制所需开发场景。
6.版本控制:每个boot项目都有一个父项目spring-boot-starter-parentparent的父项目是spring-boot-dependencies。 父项目是版本仲裁中心,把所有常见的jar的依赖版本都声明好了。如果需要切换版本,利用maven的就近原则,直接添加所需依赖即可。

##1.2 YAML配置文件
由于要使用一个配置文件对整个项目进行配置,properties文件层级不够清晰,也可以使用application.yml进行配置文件的编写。

1.基本语法:

  • 大小写敏感
  • 使用缩进表示层级关系,k: v,使用空格分割k,v
  • 缩进时不允许使用Tab键,只允许使用空格。换行
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • #表示注释,从这个字符一直到行尾,都会被解析器忽略。
person:
  name: 张三
  age: 18
  birthDay: 2010/10/10 12:12:12
  like: true
  child:
    name: 李四
    age: 20
    birthDay: 2018/10/10
    text: ["abc","def"]
  dogs:
    - name: 小黑
      age: 3
    - name: 小白
      age: 2
  cats:
    c1:
      name: 小蓝
      age: 3
    c2: {name: 小绿,age: 2} #对象也可用{}表示

##1.3 日志功能

1. SpringBoot如何实现日志的默认配置

  • 1、每个starter场景,都会导入一个核心场景spring-boot-starter
  • 2、核心场景引入了日志的所用功能spring-boot-starter-logging
  • 3、默认使用了logback + slf4j 组合作为默认底层日志
  • 4、日志是系统一启动就要用,xxxAutoConfiguration是系统启动好了以后放好的组件,后来用的。
  • 5、日志是利用监听器机制配置好的。ApplicationListener
  • 6、日志所有的配置都可以通过修改配置文件实现。以logging开始的所有配置。
    ###2. 日志级别
  • 由低到高:ALL,TRACE, DEBUG, INFO, WARN, ERROR,FATAL,OFF
  • 不指定级别的所有类,都使用root指定的级别作为默认级别
  • SpringBoot日志默认级别是 INFO
    ###3. 日志配置
  • logging.level.<logger-name>=<level>指定日志级别
    logger-name若为root则表示所有日志,也可指定包或指定类(也可以将相同配置的logger分组进行统一设置,SpringBoot 预定义两个组:sql 和 web)
    level指定日志级别
  • logging.file.name指定日志文件输出的路径及文件名
  • 文件归档与滚动切割
    每天的日志应该独立分割出来存档。如果使用logback,可以通过application.properties/yaml文件指定日志滚动规则。
    如果是其他日志系统,需要自行配置(添加log4j2-spring.xml)
  • 切换日志组合
    利用maven的就近原则和<exclusion>标签屏蔽掉原本的日志依赖并引入所需依赖。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

#2.Web开发
##2.1 WebMvcAutoConfiguration原理

  • 生效条件
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class }) //在这些自动配置之后
@ConditionalOnWebApplication(type = Type.SERVLET) //如果是web应用就生效,类型SERVLET、REACTIVE 响应式web
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //容器中没有这个Bean才生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)//优先级
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration { 
}
  • 默认效果
    参照WebMvcAutoConfiguration配置类源码,其内部使用@bean注解添加了各种组件
  1. 两个过滤器组件
    a. HiddenHttpMethodFilter;页面表单提交Rest请求(GET、POST、PUT、DELETE)
    b. FormContentFilter: 表单内容Filter,GET(数据放URL后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略
  2. 一个WebMvcConfigurer组件(WebMvcAutoConfigurationAdapter implements WebMvcConfigurer接口)
 @Configuration(
        proxyBeanMethods = false
    )
    @Import({EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
}

该组件给SpringMVC添加各种定制功能,所有的功能最终会和配置文件进行绑定(WebMvcProperties.class:spring.mvc 配置文件, WebProperties.class:spring.web 配置文件)

  • WebMvcConfigurer接口
    提供了配置SpringMVC底层的所有组件入口


  • 配置SpringMVC底层组件的方法:
    1.利用配置文件
    2.自己写一个配置类,添加@Configuration注解并实现WebMvcConfigurer接口,内部通过重写接口方法或通过@Bean放入组件来实现组件配置
    注意,配置类若添加@EnableWebMvc注解,会禁用webmvc的自动配置,观察@EnableWebMvc源码,有@Import({DelegatingWebMvcConfiguration.class}),而DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport
    所以该配置类相当于导入了WebMvcConfigurationSupport组件,而webmvc默认配之类的生效条件之一就是没有这个组件。

  • 为什么容器中放一个WebMvcConfigurer就能配置底层行为?
    WebMvcAutoConfiguration 是一个自动配置类,它里面有一个EnableWebMvcConfiguration组件,继承于DelegatingWebMvcConfiguration,并与WebProperties.class绑定,DelegatingWebMvcConfiguration利用 依赖注入DI 把容器中 所有的 WebMvcConfigurer 注入进来,别人调用 DelegatingWebMvcConfiguration 的方法配置底层规则,而它调用所有 WebMvcConfigurer 的配置底层方法。
    WebMvcConfigurationSupport提供了很多的默认设置和组件。

  • 静态资源访问规则
    规则一:访问: /webjars/** 路径就去 classpath:/META-INF/resources/webjars/ 下找资源
    规则二:访问: /** 路径就去 静态资源默认的四个位置找资源
    a. classpath:/META-INF/resources/
    b. classpath:/resources/
    c. classpath:/static/
    d. classpath:/public/
    规则三:静态资源默认都有缓存规则的设置
    a. 所有缓存的设置,直接通过配置文件: spring.web
    b. cachePeriod: 缓存周期; 多久不用找服务器要新的。 默认没有,以s为单位
    c. cacheControl: HTTP缓存控制;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching
    d. useLastModified:是否使用最后一次修改。配合HTTP Cache规则

  • 欢迎页与favicon.ico
    欢迎页规则:在 WebMvcAutoConfiguration 中进行了定义,在静态资源目录下找 index.html,没有就在 templates下找index.html
    favicon.ico:页面访问时的小图标,在静态资源目录下找(一般在前端进行设置)
    ##2.2 自定义静态资源规则

#1、spring.web:静态资源策略(开启、处理链、缓存)

#开启静态资源映射规则
spring.web.resources.add-mappings=true

#设置缓存
spring.web.resources.cache.period=3600

##缓存详细合并项控制,覆盖period配置:
## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
spring.web.resources.cache.cachecontrol.max-age=7200
## 共享缓存
spring.web.resources.cache.cachecontrol.cache-public=true
#使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304
spring.web.resources.cache.use-last-modified=true

#自定义静态资源文件夹位置
spring.web.resources.static-locations=classpath:/a/,classpath:/b/,classpath:/static/

#2、 spring.mvc
## 2.1. 自定义webjars路径前缀
spring.mvc.webjars-path-pattern=/wj/**
## 2.2. 静态资源访问路径前缀
spring.mvc.static-path-pattern=/static/**
  • 采取配置类的方法自定义静态资源规则
@Configuration //这是一个配置类
public class MyConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //保留以前规则
        //自己写新的规则。
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/a/","classpath:/b/")
                .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
    }
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/a/", "classpath:/b/")
                .setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
          }
}

2.3 路径匹配

  1. springboot现在默认使用PathPatternParser路径匹配策略,它与Ant风格的路径匹配语法上区别不大,性能更好,但是 ** 只允许出现在路径的结尾处。
  2. 通过spring.mvc.pathmatch.matching-strategy=ant_path_matcher可以实现路径匹配策略的转换

2.4 内容协商


###2.4.1 默认规则
a.基于请求头内容协商:(默认开启)
客户端向服务端发送请求,携带HTTP标准的Accept请求头(Accept: application/json、text/xml、text/yaml),服务端根据客户端请求头期望的数据类型进行动态返回。
b.基于请求参数内容协商:(需要开启)
发送请求 GET /projects/spring-boot?format=json
匹配到 @GetMapping(“/projects/spring-boot”)
根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置】
发送请求 GET /projects/spring-boot?format=xml,优先返回 xml 类型数据

# 开启基于请求参数的内容协商功能。 默认参数名:format。 默认此功能不开启
spring.mvc.contentnegotiation.favor-parameter=true
# 指定内容协商时使用的参数名。默认是 format
spring.mvc.contentnegotiation.parameter-name=type

若要能够返回xml格式,需要导入相关的依赖

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

###2.4.2 自定义内容返回(yaml为例)

    1. 增加yaml返回的依赖
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
    1. 编写配置
#新增一种媒体类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
    1. 增加HttpMessageConverter组件,专门负责把对象写出为yaml格式
public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    private ObjectMapper objectMapper = null; //把对象转成yaml

    public MyYamlHttpMessageConverter(){
        //告诉SpringBoot这个MessageConverter支持哪种媒体类型  //媒体类型
        super(new MediaType("text", "yaml", Charset.forName("UTF-8")));
        YAMLFactory factory = new YAMLFactory()
                .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
        this.objectMapper = new ObjectMapper(factory);
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        //只要是对象类型,不是基本类型
        return true;
    }

    @Override  //@RequestBody
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override //@ResponseBody 把对象怎么写出去
    protected void writeInternal(Object methodReturnValue, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

        //try-with写法,自动关流
        try(OutputStream os = outputMessage.getBody()){
            this.objectMapper.writeValue(os,methodReturnValue);
        }

    }
}
    1. HttpMessageConverter组件通过配置类放进容器中
@Configuration //这是一个配置类
public class MyConfig implements WebMvcConfigurer{
      @Override //配置一个能把对象转为yaml的messageConverter
      public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
          converters.add(new MyYamlHttpMessageConverter());
      }      
}

###2.4.3 内容协商原理

  1. 如果controller方法的返回值标注了 @ResponseBody 注解
    1.1. 请求进来先来到DispatcherServletdoDispatch()进行处理
    1.2. 找到一个 HandlerAdapter 适配器。利用适配器执行目标方法
    1.3. RequestMappingHandlerAdapter来执行,调用invokeHandlerMethod()来执行目标方法
    1.4. 目标方法执行之前,准备好两个东西
    1.4.1. HandlerMethodArgumentResolver:参数解析器,确定目标方法每个参数值
    1.4.2. HandlerMethodReturnValueHandler:返回值处理器,确定目标方法的返回值改怎么处理
    1.5. RequestMappingHandlerAdapter 里面的invokeAndHandle()真正执行目标方法
    1.6. 目标方法执行完成,会返回返回值对象
    1.7. 找到一个合适的返回值处理器 HandlerMethodReturnValueHandler(默认有多种,遍历选择)
    1.8. 最终找到 RequestResponseBodyMethodProcessor能处理 标注了 @ResponseBody注解的方法
    1.9. RequestResponseBodyMethodProcessor 调用writeWithMessageConverters ,利用MessageConverter把返回值写出去
  2. HttpMessageConverter 会先进行内容协商
    2.1. 遍历所有的MessageConverter看谁支持这种内容类型的数据(有默认的几种 MessageConverter,遍历选择)
    2.2 根据需求选择对应的MessageConverter
    2.3 通过MessageConverter中对应的方法把内容按要求写出去

2.5 模板引擎

现在多用前后端分离的架构, 模板引擎部分知识等需要了再补。

2.6 错误处理机制

错误处理的自动配置都在ErrorMvcAutoConfiguration中,两大核心机制:
● 1. SpringBoot 会自适应处理错误,响应页面或JSON数据
● 2. SpringMVC的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理

spring容器中有一个默认的名为 error 的 view; 提供了默认白页功能。

  • 在前后端分离的场景下,一般不返回视图信息,而返回json
    此时,后台发生的所有错误,使用 @ControllerAdvice + @ExceptionHandler进行统一异常处理。
//举例

@ResponseBody
@ControllerAdvice//标记为一个全局的异常处理组件
public class MyGlobalExceptionHandler 
{
    // 专门用来捕获和处理Controller层的异常
    @ExceptionHandler(Exception.class)
    public ModelAndView customException(Exception e) 
    {
        ModelAndView mv = new ModelAndView();
        mv.addObject("message", e.getMessage());
        mv.setViewName("myerror");
        return mv;
    }
 
    // 专门用来捕获和处理Controller层的空指针异常
    @ExceptionHandler(NullPointerException.class)
    public ModelAndView nullPointerExceptionHandler(NullPointerException e)
    {
        ModelAndView mv = new ModelAndView(new MappingJackson2JsonView());
        mv.addObject("success",false);
        mv.addObject("mesg","请求发生了空指针异常,请稍后再试");
        return mv;
   }
}

https://www.cnblogs.com/xd502djj/p/9873172.html

2.7 嵌入式容器

servlet容器,默认为tomcat
自动配置原理与之前类似,通过看源码得知,直接在配置文件中修改server下的相关配置就可以修改服务器参数

  • 通过给容器中放一个ServletWebServerFactory,来禁用掉SpringBoot默认放的服务器工厂,实现自定义嵌入任意服务器。
  • 全面接管SpringMVC,可在自定义的配置类上加入@EnableWebMvc,原理前面讲过。

2.8 Web新特性

1.Problemdetails
ProblemDetailsExceptionHandler 是一个 @ControllerAdvice,集中处理系统异常
处理以下异常。如果系统出现以下异常,会被SpringBoot支持以 RFC 7807规范方式返回错误数据.

2.函数式Web
将路由与业务分离。

  • 核心类
    ● RouterFunction 定义路由信息(发什么请求,谁来处理)
    ● RequestPredicate 定义请求方式
    ● ServerRequest 封装请求
    ● ServerResponse 封装响应
  • 示例
//写法固定
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
    //accept是RequestPredicates工具类的一个静态方法,传入MediaType,返回对应的 AcceptPredicate ;
    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
    @Bean
    public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }
}
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
//在handler中做具体实现,返回ServerResponse
@Component
public class MyUserHandler {

    public ServerResponse getUser(ServerRequest request) {
        ...//具体的处理语句(从request中取出所需参数做对应处理后,调用ServerResponse中的方法将响应封装入ServerResponse)
        return ServerResponse.ok().build();
    }
    public ServerResponse getUserCustomers(ServerRequest request) {
        ...
      //body中的对象会利用HttpMessageConvertor将对象转换为json写入(类似于@ResponceBody的原理)
        return ServerResponse.ok().body(Person);
    }
    public ServerResponse deleteUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }
}

3.数据访问

SpringBoot 整合 Spring、SpringMVC、MyBatis 进行数据访问场景开发。

3.1 项目创建

导入MyBatis、和mysql-connector相关的starter

<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.1</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>       
    <scope>runtime</scope>
</dependency>

3.2 配置数据源

spring.datasource.url=jdbc:mysql://192.168.200.100:3306/demo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.zaxxer.hikari.HikariDataSource

3.3 在配置文件中配置Mybatis

#指定mapper映射的xml文件位置
mybatis.mapper-locations=classpath:/mapper/*.xml
#开启驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true

3.4 CRUD编写

● 编写Bean
● 编写Mapper
● 使用mybatisx插件,快速生成MapperXML,在XML文件中编写具体的sql语句
● 配置类上使用注解 @MaperScan(“包名”),告诉springboot 接口的位置
● 测试CRUD

3.5 整合其他数据源

默认为HikariDataSource,也可整合其他数据源比如阿里的druid

  • 导入druid-starter,在配置文件中更改配置即可

4.基础特性

4.1 SpringApplication

  • 自定义 SpringApplication
    在application中调用相应的set方法也能实现底层配置,若与外部配置文件重复,以配置文件为准。
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }

}

4.2 Profiles

  • Spring Profiles 提供一种隔离配置的方式,使其仅在特定环境生效;
  • 任何@Component, @Configuration 或 @ConfigurationProperties 可以使用 @Profile{“环境名”} 标记,来指定在那种环境下被加载。【容器中的组件都可以被 @Profile标记】

环境激活

  • 在配置文件中加入spring.profiles.active=production,myenv
  • 也可以使用命令行激活。–spring.profiles.active=dev,myenv
  • 还可以配置默认环境; 不标注@Profile 的组件永远都存在。spring.profiles.default=test(spring.profiles.default默认值为default)

环境包含

spring.profiles.include[0]=common
spring.profiles.include[1]=local
  • 任何环境被激活,common和local都会存在

Profile分组

spring.profiles.group.prod[0]=db
spring.profiles.group.prod[1]=mq
  • 使用spring.profiles.active=prod激活prod就会激活prod,db,mq配置文件
最终效果
  • 生效的环境 = 激活的环境/默认环境 + 包含的环境
  • 基础的配置mybatis、log、xxx:写到包含环境中
  • 需要动态切换变化的 db、redis:写到激活的环境中

Profile 配置文件

  • application-{profile}.properties可以作为指定环境的配置文件(命名方式固定)
  • 激活这个环境,配置就会生效。
效果

application.properties:主配置文件,任意时候都生效
application-{profile}.properties:指定环境配置文件,激活指定环境生效
profile优先级 > application

4.3 外部化配置

SpringBoot 应用启动时会自动寻找application.properties和application.yaml位置,进行加载。顺序如下:

  1. 类路径: 内部
    a. 类根路径
    b. 类下/config包
  2. 当前路径(项目所在的位置)
    a. 当前路径
    b. 当前下/config子目录
    c. /config目录的直接子目录
    生效规律:最外层的最优先。
    命令行 > 所有,包外 > 包内,config目录 > 根目录,profile > application 。

4.4 导入配置

使用spring.config.import可以导入额外的配置文件

spring.config.import=my.properties

4.5 属性占位符

配置文件中可以使用 ${name:default}形式取出之前配置过的值。

app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}

4.6 单元测试

spring-boot-test提供核心测试能力,spring-boot-test-autoconfigure 提供测试的一些自动配置。
我们只需要导入spring-boot-starter-test 即可整合测试.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

注解

● @Test :表示方法是测试方法。
● @ParameterizedTest :表示方法是参数化测试,使得对于不同参数的同一方法不需重复编写测试的代码。
● @RepeatedTest :表示方法可重复执行
● @DisplayName :为测试类或者测试方法设置展示名称
● @BeforeEach :表示在每个单元测试之前执行
● @AfterEach :表示在每个单元测试之后执行
● @BeforeAll :表示在所有单元测试之前执行
● @AfterAll :表示在所有单元测试之后执行
● @Tag :表示单元测试类别,类似于JUnit4中的@Categories
● @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
● @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
● @ExtendWith :为测试类或测试方法提供扩展类引用

断言

assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null

参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
    System.out.println(string);
    Assertions.assertTrue(StringUtils.isNotBlank(string));
}


@ParameterizedTest
@MethodSource("method")    //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
    System.out.println(name);
    Assertions.assertNotNull(name);
}

static Stream<String> method() {
    return Stream.of("apple", "banana");
}

嵌套测试

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。

5.核心原理

5.1 事件和监听器

生命周期监听

####1.监听器-SpringApplicationRunListener,能够监听应用的生命周期

  • 监听器定义方法
    a.创建监听器类,实现SpringApplicationRunListener接口(可重写接口的七种方法,分别在不同的生命周期中调用)
    b.在 META-INF/spring.factories 中配置 org.springframework.boot.SpringApplicationRunListener=自己的Listener的全类名
/**
 * Listener先要从 META-INF/spring.factories 读到
 *
 * 1、引导: 利用 BootstrapContext 引导整个项目启动
 *      starting:              应用开始,SpringApplication的run方法一调用,只要有了 BootstrapContext 就执行
 *      environmentPrepared:   环境准备好(把启动参数等绑定到环境变量中),但是ioc还没有创建;【调一次】
 * 2、启动:
 *      contextPrepared:       ioc容器创建并准备好,但是sources(主配置类)没加载。并关闭引导上下文;组件都没创建  【调一次】
 *      contextLoaded:         ioc容器加载。主配置类加载进去了。但是ioc容器还没刷新(bean还没创建)。
 *      =======截止以前,ioc容器里面还没造bean=======
 *      started:               ioc容器刷新了(所有bean造好了),但是 runner 没调用。
 *      ready:                  ioc容器刷新了(所有bean造好了),所有 runner 调用完了。
 * 3、运行
 *     以前步骤都正确执行,代表容器running。
 */

####2.生命周期

###事件触发时机
####1.各种回调监听器

● BootstrapRegistryInitializer: 感知特定阶段:感知引导初始化
创建引导上下文bootstrapContext的时候触发。
创建方法:
META-INF/spring.factories
application.addBootstrapRegistryInitializer();
场景:进行密钥校对授权。


● ApplicationContextInitializer: 感知特定阶段: 感知ioc容器初始化
创建方法:
META-INF/spring.factories
application.addInitializers();


● ApplicationListener: 感知全阶段:基于事件机制,感知事件。 到了哪个阶段可以做自定义的事
@Bean或@EventListener: 事件驱动
创建方法:
SpringApplication.addListeners(…)或 SpringApplicationBuilder.listeners(…)
META-INF/spring.factories


● SpringApplicationRunListener: 感知全阶段生命周期 + 各种阶段都能自定义操作; 功能更完善。
创建方法:
META-INF/spring.factories


● ApplicationRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
创建方法:
@Bean (因为此时容器已经refresh完成,该类型的监听器就是直接从容器中取得的)


● CommandLineRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
创建方法:
@Bean(因为此时容器已经refresh完成,该类型的监听器就是直接从容器中取得的)

  • 最佳实战:
    ● 如果项目启动前做事: BootstrapRegistryInitializer 和 ApplicationContextInitializer
    ● 如果想要在项目启动完成后做事:ApplicationRunner和 CommandLineRunner
    ● 如果要干涉生命周期做事:SpringApplicationRunListener
    ● 如果想要用事件机制:ApplicationListener


    ####2.完整触发流程
    9大事件触发顺序&时机
  1. ApplicationStartingEvent:应用启动但未做任何事情, 除过注册listeners and initializers.
  2. ApplicationEnvironmentPreparedEvent: Environment 准备好,但context 未创建.
  3. ApplicationContextInitializedEvent: ApplicationContext 准备好,ApplicationContextInitializers 调用,但是任何bean未加载
  4. ApplicationPreparedEvent: 容器刷新之前,bean定义信息加载
  5. ApplicationStartedEvent: 容器刷新完成, runner未调用
    =以下就开始插入了探针机制====
  6. AvailabilityChangeEvent: LivenessState.CORRECT应用存活; 存活探针
  7. ApplicationReadyEvent: 任何runner被调用
  8. AvailabilityChangeEvent:ReadinessState.ACCEPTING_TRAFFIC就绪探针,可以接请求
  9. ApplicationFailedEvent :启动出错


  • 感知应用是否存活了:可能植物状态,虽然活着但是不能处理请求。
  • 应用是否就绪了:能响应请求,说明确实活的比较好。
  • 探针机制主要在后面在云上的部署有很大作用。


    ####3.SpringBoot事件驱动开发
    应用启动过程生命周期事件感知(9大事件)、应用运行中事件感知(自定义)。
    ● 事件发布:ApplicationEventPublisherAware或注入:ApplicationEventMulticaster
    ● 事件监听:组件 + @EventListener
  • 示例场景:
  • 示例代码:
    1)创建自定义的事件,继承ApplicationEvent,并可重写其有参构造方法,使事件可携带参数
class LoginSuccessEvent entends ApplicationEvent{
  public LoginSuccessEvent(User user){
    super(user);
  }
}

2)创建事件发布者

@Service
public class EventPublisher implements ApplicationEventPublisherAware {

    /**
     * 底层发送事件用的组件,SpringBoot会通过ApplicationEventPublisherAware接口自动注入给我们
     * 事件是广播出去的。所有监听这个事件的监听器都可以收到
     */
    ApplicationEventPublisher applicationEventPublisher;

    /**
     * 所有事件都可以发
     * @param event
     */
    public void sendEvent(ApplicationEvent event) {
        //调用底层API发送事件
        applicationEventPublisher.publishEvent(event);
    }

    /**
     * 会被自动调用,把真正发事件的底层组组件给我们注入进来
     * @param applicationEventPublisher event publisher to be used by this object
     */
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

3)事件订阅者

@Service
public class CouponService {

    @Order(1) //控制执行优先级(监听同一时间的事件订阅者方法,Order越小执行越晚)
    @EventListener //标记为事件监听器
    public void onEvent(LoginSuccessEvent loginSuccessEvent){ //传入要监听的事件,若全部监听,就传ApplicationEvent类型
        System.out.println("===== CouponService ====感知到事件"+loginSuccessEvent);
        UserEntity source = (UserEntity) loginSuccessEvent.getSource();  //取出事件中携带的数据
        sendCoupon(source.getUsername()); //调用服务方法
    }

    public void sendCoupon(String username){
        System.out.println(username + " 随机得到了一张优惠券");
    }
}

也可使Service类直接实现ApplicationListener接口,接口的泛型为我们要监听的事件(没有以上方法方便)

@Service
public class  CouponService implements ApplicationListener<LoginSuccessEvent>{
    public void sendCoupon(String username){
        System.out.println(username + " 随机得到了一张优惠券");
    }
    public void onApplicationEvent(LoginSuccessEvent loginSuccessEvent){ 
        System.out.println("===== CouponService ====感知到事件"+loginSuccessEvent);
        UserEntity source = (UserEntity) loginSuccessEvent.getSource();  
        sendCoupon(source.getUsername()); 
    }
}

5.2 自动配置原理

  • 前两章已介绍过了了,此处不再赘述

SPI机制

SPI机制
● Java中的SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI的思想是,定义一个接口或抽象类,然后通过在classpath中定义实现该接口的类来实现对组件的动态发现和加载。
● SPI的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件。通过使用SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类。
● 在Java中,SPI的实现方式是通过在META-INF/spring/services目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名。当应用程序启动时,Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定的类名来加载实现类。
● 通过使用SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。

5.3 自定义starter

1.定义自己的starter包,引入spring-boot-starter基础依赖
2.开发主要功能,并引入相关依赖
2.1 将属性类与配置文件前缀绑定

@ConfigurationProperties(prefix = "robot")  //此属性类和配置文件指定前缀绑定
@Component
@Data
public class RobotProperties {

    private String name;
    private String age;
    private String email;
}

2.2 导入配置处理器,配置文件自定义的properties配置都会有提示

<!--        导入配置处理器,配置文件自定义的properties配置都会有提示-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

3.编写xxxAutoConfiguration自动配置类,利用@Import导入这个模块需要的所有组件
4.利用使用@EnableXxx机制,定义@EnableXxx注解,@import(xxxAutoConfiguration.class),别人引入starter可以使用 @EnableRobot开启功能
5.利用SPI机制,完全自动配置
● META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中编写好我们自动配置类的全类名即可
● 项目启动,自动加载我们的自动配置类


这样别的项目只需要导入我们自定义的starter,就会导入该自动配置类从而import相关的所有组件进入容器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值