SpringBoot2核心知识点

前言

  • 本文来源于【尚硅谷】SpringBoot2零基础入门教程(spring boot2干货满满)_哔哩哔哩_bilibili
  • 学习过雷神的朋友肯定知道,雷神的笔记非常的随性,所以本文对视频内容进行提炼并且二次整理
  • 由于作者做笔记时图片使用的是本地路径【MarkDown语法你懂的】所以本文博客中图片无法正常显示,如果有需求可以下载文章上方的pdf
  • ps:作者正在学习雷神的Spring Boot3,后续会更新相关的笔记

基础入门

基础概念

  • 简介

    • SpringBoot是整合Spring技术栈的一站式框架,是简化Spring技术栈的快速开发脚手架

  • 优点

    • 创建独立Spring应用

    • 内嵌web服务器

    • 自动starter依赖,简化构建配置

    • 自动配置Spring以及第三方功能

    • 提供生产级别的监控、健康检查及外部化配置

    • 无代码生成、无需编写XML

  • 缺点

    • 人称版本帝,迭代快,需要时刻关注变化

    • 封装太深,内部原理复杂,不容易精通(底层是spring)

时代背景

  • 微服务

    • 是一种架构风格

    • 一个应用拆分为一组小型服务

    • 每个服务运行在自己的进程内,也就是可独立部署和升级

    • 服务之间使用轻量级HTTP交互

    • 服务围绕业务功能拆分

    • 可以由全自动部署机制独立部署

    • 去中心化,服务自治(服务可以使用不同的语言、不同的存储技术

  • 分布式

    • 分布式的困难

      • 远程调用、服务发现、负载均衡、服务容错、配置管理

      • 服务监控、链路追踪、日志管理、任务调度....

    • 分布式解决:springboot+springcloud

  • 云原生:原生应用上云

    • 困难

      • 服务自愈、弹性伸缩、服务隔离

      • 自动化部署、灰度发布、流量治理...

入门

  • 官方文档大致目录结构

image-20230719140022767

image-20230719140534391

  • 使用步骤

    1. 导入依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId><!--web场景启动器-->
        </dependency>
    </dependencies>
    1. 创建主程序

      • @SpringBootApplication标识该类是一个SpringBoot应用,称为主程序类(所有启动的入口)

      • 命名无要求,其他的就是固定写法

    @SpringBootApplication
    public class MainApplication {
        public static void main(String[] args) {
            SpringApplication.run(MainApplication.class,args);
        }
    }
    1. 测试业务

      • @RestController包含@RestquestBody和@Controller

      • 业务要放在主程序对应的目录里面

    @RestController
    public class HelloController {
        @RequestMapping("/hello")
        public String handle01(){
            return "Hello, Spring Boot 2!";
        }
    }
    1. 简化配置,创建application.properties,修改相应配置

      • 文件名是固定的

      • 放在resources目录下

      • 如果pom.xml中的打包方式是pom会扫描不到

    2. 简化部署

      • 把项目打成jar包,直接在目标服务器执行即可

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

!!!自动配置原理

springboot的特点

依赖管理
  • 父项目做依赖管理:几乎声明了所有开发中常用的依赖的版本号,自动版本仲裁机制

  • 开发导入starter场景启动器

    • 作用:只要引入starter,这个场景的所有常规需要的依赖都自动引入(依赖传递原则)

      • spring-boot-starter-* :官方场景启动器的命名规范,*代表某种场景

      • *-spring-boot-starter:第三方提供的简化开发的场景启动器

    • SpringBoot所有支持的场景

    • 所有场景启动器最底层的依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>
  • 自动版本仲裁,无需关注版本号

    • 引入依赖默认可以不用写版本号

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    • 但如果所需要的版本不在spring-boot-dependencies声明,就需要写版本号

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.34</version>
    </dependency>
  • 可以修改默认版本号

    1. 查看spring-boot-dependencies里面规定当前依赖的版本,并找到标签名

      image-20230719160757854

      image-20230719160847860

      image-20230719161126312

      先进入spring-boot-starter-parent再进入spring-boot-dependencies

    2. 在当前项目里面重写配置(就近优先原则,当前项目配置了就用当前的,否则使用父项目)

    <properties>
        <mysql.version>5.1.43</mysql.version>
    </properties>
自动配置
  • 自动配好Tomcat

    • 引入Tomcat依赖

    • 配置Tomcat

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>
  • 自动配好SpringMVC

    • 引入SpringMVC全套组件

    • 自动配好SpringMVC常用组件(功能)

  • 自动配好Web常用功能(如字符编码功能)

  • 默认的包结构

  • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来

    • 无需配置包扫描

    • 想要改变扫描路径

      • 在主程序中加入@SpringBootApplication(scanBasePackages="扫描的目录")

      • 或者@ComponentScan 指定扫描路径

@SpringBootApplication("com.atguigu.boot")
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
  • 各种配置拥有默认值

    • 默认配置最终都是映射到某个类上

    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象

  • 按需加载所有自动配置项

    • 只有引入的开发场景才会开启相应的自动配置

    • SpringBoot所有的自动配置功能都在spring-boot-autoconfigure包里面

容器功能

组件添加
@Configuration
  • 作用:标识的类作为配置类,等同于配置文件

  • 配置类里面使用@Bean标注在方法上给容器注册组件,默认是单实例

  • 配置类本身也是组件

  • 属性

    • proxyBeanMethods:代理bean的方法

      • proxyBeanMethods = true【总会检查组件是否在容器中,保证每个@Bean方法返回的组件都是单实例的,full全配置】

      • proxyBeanMethods = false【每个@Bean方法返回的组件都是新创建的,不会保存代理对象,lite轻量级配置】

      • 应用场景:配置类组件之间

        • 无依赖关系用Lite模式加速容器启动过程,减少判断

        • 依赖关系,方法会被调用得到之前单实例组件,用Full模式

@bean
  • 作用:给容器中添加组件,标识在方法上

  • 默认以方法名作为组件的id返回类型就是组件类型返回的值就是组件在容器中的实例

  • 也可以自定义组件id,通过@Bean("id")设置

@import
  • 值是class类型的数组,作用:自动创建出对应类型的组件交给容器管理,默认组件的名字是全类名

  • 标识在组件上

@Conditional
  • 作用:条件装配,满足Conditional指定的条件,则进行组件注入

image.png

@ImportResource
  • 作用:引入原生spring配置文件,属性值声明配置文件的位置

配置绑定
  • 读取到properties文件中的内容,并且把它封装到JavaBean中

  • 方式

    1. @Component + @ConfigurationProperties

      • 标识到具体组件(JavaBean)上

      • @Component 将组件交给容器管理,只有在容器中的组件才会拥有SpringBoot提供的功能

      • @ConfigurationProperties中的prefix属性可以指定配置文件中的前缀,根据该前缀下的属性和组件中的属性一一绑定

        • 注意:!!!前缀不能有大写字母

    2. @EnableConfigurationProperties + @ConfigurationProperties

      • @EnableConfigurationProperties 要标识到配置类上,属性是需要绑定配置的class,该注解有两个作用

        1. 开启对应组件的配置绑定功能

        2. 将对应组件注册到容器中

自动配置原理入门

引导加载自动配置类
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}
  • @SpringBootConfiguration其中包含@Configuration,代表当前是一个配置类

  • @EnableAutoConfiguration包含

    • @AutoConfigurationPackage:指定了默认包规则

      • 利用Registrar给容器中导入一系列组件

      • 将指定的主程序所在包下的所有组件导入进来

      @Import(AutoConfigurationPackages.Registrar.class)
      public @interface AutoConfigurationPackage {}
    • @Import(AutoConfigurationImportSelector.class)

      1. 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

      2. 调用getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类

      3. 利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件

      4. 从META-INF/spring.factories位置来加载一个文件

        • 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件

        • spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories,里面包含了127个场景自动配置,所有自动配置启动的时候默认全部加载

按需开启自动配置项
  • 按照条件装配规则(@Conditional),最终会按需配置(只有条件生效的时候配置才会生效)

image-20230721183936227

  • 具体例子

@Bean
@ConditionalOnBean(MultipartResolver.class)  //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) 
//容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
    return resolver;
}
  • 该方法作用:给容器添加文件上传解析器,并保证名字为multipartResolver

    • 给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找

      • 防止有些用户配置的文件上传解析器不符合规范

      • Detect if the user has created a MultipartResolver but named it incorrectly

    • 由于@Bean标识的方法默认返回的组件是以方法名命名的,最后回向容器注入名为multipartResolver的文件上传解析器

  • SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先

修改默认配置
  • 找到对应组件所绑定的配置文件的值(前缀),然后在application.properties进行修改/配置

image-20230721234547722

==》

image-20230721234613011

==》

image-20230721235129440

总结
  • SpringBoot先加载所有的自动配置类(xxxxxAutoConfiguration)

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值(xxxxProperties中拿,xxxProperties和配置文件进行了绑定)

  • 生效的配置类就会给容器中装配很多组件(只要容器中有这些组件,相当于这些功能就有了)

    • xxxxxAutoConfiguration ---导入--> 组件 ---取值-->xxxxProperties ---绑定---> application.properties

  • 定制化配置的方式

    1. 直接使用@Bean替换底层的组件

    2. 在application.properties中进行修改/配置

最佳实践
  • 引入场景依赖

  • 查看自动配置

    • 自己分析,引入场景对应的自动配置一般都生效了

    • 配置文件中配置debug=true开启自动配置报告

      • Negative(不生效)

      • Positive(生效)

  • 是否需要修改

  • 自定义加入或者替换组件

    • @Bean、@Component

    • 使用自定义器 XXXXXCustomizer

开发技巧

lomback
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
  • 作用:帮类在编译的时候生成get、set、toString方法和构造器等等,简化javaBean开发

  • 使用(注解标识到类上)

    • @Data:生成已有属性的get、set方法

    • @ToString:生成toString方法

    • @AllArgsConstructor:生成所有属性的有参构造器

    • @NoArgsConstructor:生成无参构造器

  • 简化日志开发

    • @Slf4j:注入一个日志类,通过log日志记录器记录日志

@RestController
@Slf4j
public class HelloController {
@RequestMapping("/hello")
    public String sayHello(){
    log.error("测试日志记录器");
    return "hello";
    }
}

image-20230722161114307

dev-tools
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>
  • 作用:项目或者页面修改以后:使用

    image-20230722161903006

    ,就不用重启项目保留控制台信息

Spring Initailizr(项目初始化向导)
  • 作用

    1. 自动依赖引入

    2. 自动创建项目结构

      image-20230722162625000

    3. 自动编写好主配置类

  • 使用

    1. 创建项目时选择Spring Initailizr

    2. 选择需要用到的开发场景

      image-20230722163425443

!!!核心功能

image-20230722164435656

配置文件

  • 分类

    • properties,用法之前提过

    • yaml,下面展开

yaml

简介
  • YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)

  • 非常适合用来做以数据为中心的配置文件

基本语法
  • key: value; kv之间有空格

  • 大小写敏感

  • 使用缩进表示层级关系,缩进不允许使用tab,只允许空格,缩进的空格数不重要,只要相同层级的元素左对齐即可

  • #表示注释

  • 字符串无需加引号,如果要加,'单引号'与"双引号"

    • 表示字符串内容会被转义/不转义

    • 也可以理解为能/不能输出转义字符\(例如'\n'输出\n "\n"输出换行)

数据类型的表示
  • 字面量:单个的、不可再分的值。date、boolean、string、number、nullk: 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
  • 创建application.yaml文件,application.properties和application.yaml两个文件中的配置组合起来生效,优先properties

举例
  • 实体类

    @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;
    }
  • application.yaml

    # 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}]

配置提示

  • 自定义的类和配置文件绑定一般没有提示,为开发方便可以导入

  • 使用

    • 导入依赖,然后重新启动项目,之后就有自定义类中的属性的配置提示啦

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-configuration-processor</artifactId>
          <optional>true</optional>
      </dependency>

      image-20230723000227991

      • 在配置文件中,原属性名如果为驼峰命名,除首字母之外为大写字母的情况,其它大写字母会替代为 -小写字母

      image-20230723001559512

      ==>

      image-20230723001627875

    • 在plugin中配置该消息,作用是项目打包时不将配置提示的处理器打包

      <configuration>
          <excludes>
              <exclude>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-configuration-processor</artifactId>
              </exclude>
          </excludes>
      </configuration>

!!!web开发

image-20230724141227312

SpringMVC自动配置概览

  • 大多场景我们都无需自定义配置,springboot支持以下功能

    • 内容协商视图解析器和BeanName视图解析器

    • 静态资源(包括webjars)

    • 自动注册 Converter,GenericConverter,Formatter(转化器、格式化器)

    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)

    • 自动注册 MessageCodesResolver (国际化用,本课程不讲解)

    • 静态index.html 页支持

    • 自定义 Favicon(网站的小图标)

      image-20230724141449245

    • 自动使用 ConfigurableWebBindingInitializer (DataBinder负责将请求数据绑定到JavaBean上

  • 定制化的建议

    • 不用@EnableWebMvc注解。使用@Configuration+WebMvcConfigurer自定义规则

    • 声明WebMvcRegistrations 改变默认底层组件

    • 使用@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

简单功能分析

静态资源访问
静态资源目录
  • 只要静态资源放在类路径下/static or /public or /resources or /META-INF/resources目录下(这四个目录时默认的静态资源文件夹)

    就可以通过当前项目根路径/ + 静态资源名访问到对应的静态资源

  • 原理: 静态映射/**,请求进来,先去找Controller看能不能处理。不能处理的所有请求都交给静态资源处理器。静态资源也找不到则响应404页面

  • 改变默认的静态资源文件夹

resources:
    static-locations: [classpath:/haha/] #可以配置多个
静态资源访问前缀
  • 默认无前缀

  • 改变默认的静态资源路径

    spring:
      mvc:
        static-path-pattern: /res/**
    • 访问方式:当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

    • 作用:指定前缀,防止被拦截器拦截

webjar
欢迎页支持
  1. 静态资源路径下放index.html,就会作为欢迎页使用

    • 可以配置静态资源路径

    • 不可以配置静态资源的访问前缀,否则导致 index.html不能被默认访问

  2. 使用controlller处理/index

自定义 Favicon

image-20230724141449245

  • favicon.ico放在静态资源目录下即可

    • 命名是固定的

    • 不可以配置静态资源的访问前缀

静态资源配置原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)

  • SpringMVC功能的自动配置类大多集中在WebMvcAutoConfiguration

    • 生效条件

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    		ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {}
    • 容器中包含的组件

    • 配置类中的属性的绑定

      • 配置类只有一个有参构造器,有参构造器中所有参数的值都会从容器中确定

      //ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
      //WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
      //ListableBeanFactory beanFactory Spring的beanFactory
      //HttpMessageConverters 找到所有的HttpMessageConverters
      //ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
      //DispatcherServletPath  处理路径相关
      //ServletRegistrationBean   给应用注册Servlet、Filter....
      public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, 
      WebMvcProperties mvcProperties,ListableBeanFactory beanFactory, 
      ObjectProvider<HttpMessageConverters> messageConvertersProvider,
      ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
      ObjectProvider<DispatcherServletPath> dispatcherServletPath,
      ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
              this.resourceProperties = resourceProperties;
              this.mvcProperties = mvcProperties;
              this.beanFactory = beanFactory;
              this.messageConvertersProvider = messageConvertersProvider;
              this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
              this.dispatcherServletPath = dispatcherServletPath;
              this.servletRegistrations = servletRegistrations;
          }
资源处理的默认规则
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        //判断是否禁用的静态资源,配置add-mappings等于false为禁用,默认不禁用
        logger.debug("Default resource handling disabled");
        return;
    }
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();//配置缓存策略
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();		if (!registry.hasMappingForPattern("/webjars/**")) {
        //webjars访问规则
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/")
                .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();//静态资源路径配置规则
    if (!registry.hasMappingForPattern(staticPathPattern)) {		
       customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)									.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
/*rivate static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
			"classpath:/resources/", "classpath:/static/", "classpath:/public/" };*/
            	.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}
欢迎页的处理规则
//HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求
//WelcomePageHandlerMapping:即欢迎页能处理的请求映射规则
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
        FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
            new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
            this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
    return welcomePageHandlerMapping;
}

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
    ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
    //要用欢迎页功能,必须是/**,所以不能有前缀
    logger.info("Adding welcome page: " + welcomePage.get());
    setRootViewName("forward:index.html");
	}
else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
    // 调用Controller  /index
    logger.info("Adding welcome page template: index");
    setRootViewName("index");
	}
}

请求参数处理

请求映射
rest使用与原理
  • 可以使用合成注解@xxxMapping(xxx=Get、Post、Delete、Put)

  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)

    • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户

    • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户

    • 使用(原生表单)

      • 配置核心Filter:HiddenHttpMethodFilter,要在SpringBoot中手动开启页面表单的Rest功能

      spring:
        mvc:
          hiddenmethod:
            filter:
              enabled: true  
      • 表单设置method=post设置隐藏域 _method=具体的请求方式put/delete...

    image-20230725145322023

    • 扩展:自定义_method的名称

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
  • Rest原理(表单提交要使用REST的时候)

    • 假设表单提交带上_method=PUT

    • 请求过来被HiddenHttpMethodFilter拦截

    • 检查请求正常,并且是POST

      • 获取到_method的值,然后将其全部转换成大写(说明_method的值大小写不敏感

        • 兼容这些请求:PUTDELETEPATCH

      • 原生表单发送request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值

        • 过滤器链放行的时候用wrapper,以后的方法调用getMethod是requesWrapper的

  • Rest使用客户端工具,如PostMan直接发送Put、delete等方式请求,无需Filter

请求映射原理

image-20230725152709036

  • SpringMVC功能都从 org.springframework.web.servlet.DispatcherServlet---》doDispatch()进行分析

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		HttpServletRequest processedRequest = request;
    		HandlerExecutionChain mappedHandler = null;
    		boolean multipartRequestParsed = false;
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    		try {
    			ModelAndView mv = null;
    			Exception dispatchException = null;
    			try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                mappedHandler = getHandler(processedRequest);//找到当前请求使用哪个Handler(Controller的方法)处理 
  • SpringBoot自动配置以下处理器映射

    image-20230725233837980

    • 欢迎页的 WelcomePageHandlerMapping,访问 /能访问到index.html

    • 默认的RequestMappingHandlerMapping,保存了所有@RequestMapping 和handler的映射规则

    • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping(自定义 HandlerMapping)

image-20230725233943559

  • 所有的请求映射都在HandlerMapping

    • 请求进来,遍历所有的HandlerMapping看是否有请求信息

      • 如果有就找到这个请求对应的handler

      • 如果没有就继续遍历下一个 HandlerMapping

  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

普通参数与基本注解
注解

image-20230726001719949

  • @PathVariable、@RequestHeader、@RequestAttribute

  • @RequestParam、@MatrixVariable、@RequestBody、@CookieValue

  • @CookieValue标识在字符串类型参数可以获取Cookie的值,标识在Cookie类型参数可以获取Cookie对象

image-20230726002504691

  • 获取请求域属性两种方式

    1. 使用原生HttpServletRequest对象,调用getAttribute()方法

    2. 使用@RequestAttribute绑定属性

  • 地址发送参数的方式

    1. queryString(查询字符串):/car/{path}?xxx=xxx&aaa=ccc

    2. 矩阵变量:/cars/sell;low=34;brand=byd,audi,yd或者/cars/sell;low=34;brand=byd;brand=audi;brand=yd

      • 矩阵变量必须有url路径变量才能被解析

      • 控制器方法接收请求路径 /car/{path}

        • path代表分号前的路径

        • 矩阵变量通过@MatrixVariable进行绑定

        image-20230726151945676

      • 多个路径变量的相同参数的使用,如/boss/1;age=20/2;age=10

        • @MatrixVariable指定pathVar匹配路径变量,如果不指定则绑定第一个同名参数

        image-20230726151754873

      • SpringBoot默认是禁用了矩阵变量的功能,手动开启

        1. 配置类中实现WebMvcConfigurer接口,重写configurePathMatch方法

        image-20230726150800205

        1. 使用@Bean配置组件,返回WebMvcConfigurer接口对象

        • 原理:对于路径的处理是使用UrlPathHelper进行解析,其中有个removeSemicolonContent属性(移除分号内容)默认为true,即不支持矩阵变量的

视图解析与模板引擎

视图解析

image-20230728113418950

  • 视图解析:

    • 返回值以 forward: 开始: new InternalResourceView(forwardUrl);

      • request.getRequestDispatcher(path).forward(request, response); 底层是原生的转发

      • 返回值以 redirect: 开始: new RedirectView()

        • response.sendRedirect(encodedURL)底层是原生的重定向

      • 返回值是普通字符串: new ThymeleafView()

  • 视图解析原理流程

    1. 目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面(包括数据和视图地址)

    2. 方法的参数是一个自定义类型对象(用于接收数据的实体类,其值从请求参数中确定的),会放入ModelAndViewContainer

      • 请求过程中产生的值都会放到该容器对象中

  1. 任何目标方法执行完成以后都会返回ModelAndView(数据和视图地址)

  2. processDispatchResult处理派发结果(页面改如何响应)

  • 调用render(mv, request, response) 进行页面渲染逻辑

  • 根据方法的String返回值得到View对象【定义了页面的渲染逻辑】

  1. 遍历所有的视图解析器,尝试当前解析器是否能根据当前返回值得到View对象

image-20230728115248468

  1. ContentNegotiationViewResolver包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象

image-20230728120009074

  1. 根据 redirect:/main.html 得到了RedirectView对象(Thymeleaf new RedirectView())

  2. view.render(mv.getModelInternal(), request, response)视图对象调用自定义的render进行页面渲染工作

image-20230728120127071

 5. RedirectView重定向到一个页面
    1. 获取目标url地址
    2. 底层调用原生response.sendRedirect(encodedURL)进行重定向
模板引擎
Thymeleaf基础知识
  • SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染

  • Thymeleaf是现代化、服务端Java模板引擎

  • 基本语法

    • 表达式

    表达式名字语法用途
    变量取值${...}获取请求域、session域、对象等值
    选择变量*{...}获取上下文对象值
    消息#{...}获取国际化等值
    链接@{...}生成链接,括号里面是跳转的地址,使用绝对路径的时候会拼接当前项目路径
    片段表达式~{...}jsp:include 作用,引入公共页面片段
thymeleaf使用
  • 引入Starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • 自动配置策略

    • 所有thymeleaf的配置值都在 ThymeleafProperties

    • 配置了

      • SpringTemplateEngine模板解析器

      • ThymeleafViewResolver视图解析器

      public static final String DEFAULT_PREFIX = "classpath:/templates/";//视图文件放置的位置
      public static final String DEFAULT_SUFFIX = ".html";  //xxx.html
  • 页面开发

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"> 导入thymeleaf的名称空间,之后使用thymeleaf语法就会有提示
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1 th:text="${msg}">哈哈</h1>
    <h2>
        <a href="www.atguigu.com" th:href="${link}">去百度</a>  <br/>
        <a href="www.atguigu.com" th:href="@{link}">去百度2</a>
    </h2>
    </body>
    </html>
构建后台管理系统
  • 防止表单重复提交可以使用重定向,因为转发的话地址栏不变(地址还在登录页面,刷新就会重复提交)

  • 检查登录状态,只有登录成功才可以访问

    • 表单提交用post,发送login请求,表单提交用户名和密码的name与实体类中的属性一致

    <form class="form-signin" th:action="@{/login}" method="post">表单提交的地址
        <div class="form-signin-heading text-center">
            <h1 class="sign-title">Sign In</h1>
            <img src="images/login-logo.png" alt=""/>
        </div>
        <div class="login-wrap">
            <label style="color: red" th:text="${msg}"></label>错误信息显示
            <input type="text" name="username" class="form-control" placeholder="User ID" autofocus>用户名
            <input type="password" name="password" class="form-control" placeholder="Password">密码
    • 通过检查session域中的是否有用户数据判断登录状态

@Controller
public class controller {
    @RequestMapping({"/","login"})
    public String login(){
        return "login";
    }
    @PostMapping("login")
    public String checkStatus(User user, HttpSession session, Model model){
        if(StringUtils.hasLength(user.getUsername())&&"123456".equals(user.getPassword())){
            session.setAttribute("loginUser",user);
            return "redirect:/index.html";//登录成功重定向到index.html,防止重复提交
        }else {
            model.addAttribute("msg","账号或者密码错误");
            return "login";
        }
    }
    @RequestMapping("index.html")
    public String toIndex(HttpSession session,Model model){
        if(session.getAttribute("loginUser")!=null){
        return "index";
        }else {
            model.addAttribute("msg","请重新登录");
            return "login";
        }
    }
}
  • 登录成功显示当前用户信息

    • 使用thymeleaf行内写法

    <a href="#" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
        <img src="images/photos/user-avatar.png" alt="" />
        [[${session.loginUser.username}]]
        <span class="caret"></span>
    </a>
  • 模板抽取

    • 公共的代码可以抽取出来放在一个html中

    • 所有资源涉及到公共代码的部分通过th:insert/replace/include引入公共代码块

    • th:insert/replace/include区别

    <body>
      <div th:insert="footer :: copy"></div>
      <div th:replace="footer :: copy"></div>
      <div th:include="footer :: copy"></div>
    </body>
    效果如下
    <body>
      <div>
        <footer>
          &copy; 2011 The Good Thymes Virtual Grocery
        </footer>
      </div>
    
      <footer>
        &copy; 2011 The Good Thymes Virtual Grocery
      </footer>
        
      <div>
        &copy; 2011 The Good Thymes Virtual Grocery
      </div>  
    </body>

拓展功能

拦截器
创建拦截器
  • 实现HandlerInterceptor接口

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    //目标方法执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        log.info("preHandle拦截的请求路径是{}",requestURI);
        //登录检查逻辑
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");
        if(loginUser != null){            
            return true;//放行
        }
        //拦截住未登录,跳转到登录页
        request.setAttribute("msg","请先登录");
        request.getRequestDispatcher("/").forward(request,response);
        //这里使用转发是因为:重定向不能在request域中存错误信息,如果在session存错误信息,未登录的时候无法得到session中数据
        return false;
    }
}
配置拦截器
  • 拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)

  • 指定拦截规则

  • 如果是拦截所有,静态资源也会被拦截,解决方式

    • 配置静态资源访问前缀

    • 放行静态资源的相关文件夹

@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
}
拦截器原理
  1. 根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有拦截器】

    • 此图中拦截器链中的LonginInterceptor为我们自己定义的拦截器

image-20230728191907041

  1. 顺序执行 所有拦截器的preHandle方法

    • 返回为true。则执行下一个拦截器的preHandle

    • 返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion

  2. 如果任何一个拦截器返回false,直接跳出不执行目标方法

  3. 所有拦截器都返回True,执行目标方法

    • 倒序执行所有拦截器的postHandle方法

  4. 前面的步骤有任何异常都会直接倒序触发afterCompletion

  5. 页面成功渲染完成以后,也会倒序触发afterCompletion

文件上传
使用
  • 表单

<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>
  • 配置文件上传参数,前缀为spring.servlet.multipart

spring.servlet.multipart.max-file-size=10MB 单个文件最大大小
spring.servlet.multipart.max-request-size=100MB 最大请求大小(总共能传多大的文件)
  • 上传代码

    • MutipartFile自动封装上传过来的文件

    • transferTo底层使用了FileCopyUtils工具类(文件复制工具类)

@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
                     @RequestParam("username") String username,
                     @RequestParam("headerImg") MultipartFile headerImg,
                     @RequestParam("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("D:\\Study\\test\\"+originalFilename));
    }
    if(photos.length > 0){
        for (MultipartFile photo : photos) {
            if(!photo.isEmpty()){
                String originalFilename = photo.getOriginalFilename();
                photo.transferTo(new File("D:\\Study\\test\\"+originalFilename));
            }
        }
    }
    return "index";
}
上传原理
  • 文件上传自动配置类MultipartAutoConfiguration

    • 自动配置好了 StandardServletMultipartResolver【文件上传解析器】

  • 步骤

    1. 请求进来使用文件上传解析器判断(isMultipart)请求的内容类型是否为multipat,是的话通过resolveMultipart方法将原请求封装为MultipartHttpServletRequest【文件上传请求对象】并返回,

    1. 参数解析器来解析请求中的文件内容封装成MultipartFile

    1. 将request中文件信息封装为一个Map(MultiValueMap<String, MultipartFile>)

异常处理
默认规则
  • Spring Boot提供/error处理所有错误的映射

  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息

image-20230802162623254

  • 对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

image-20230802162638675

  • 自定义异常界面

    • 添加error的视图组件view

    • 要完全替换默认行为,可以

      • 实现 ErrorController并注册该类型的Bean定义

    • 添加ErrorAttributes类型的组件以使用现有机制但替换其内容

    • !!!静态资源/模板引擎目录下的error目录下的4xx,5xx页面会被自动解析!!!

      • 404匹配404页面(精确匹配)

      • 5xx匹配所有状态码为5开头的页面,4xx同理

image-20230802163816519

定制错误处理逻辑
  • 自定义错误页(error/404.html error/5xx.html)

    • 有精确的错误状态码页面就匹配精确

    • 没有就找4xx.html(小拓展:不带请求参数或者请求参数类型不对就报400错误)

    • 如果都没有就触发白页

  • @ControllerAdvice+@ExceptionHandler处理全局异常,底层是 ExceptionHandlerExceptionResolver支持的

    image-20230802231634099

    • @ExceptionHandler值为处理的异常的类型

      @ControllerAdvice
      public class Exception {
      	@ExceptionHandler(ArithmeticException.class)
          public String toLogin(HttpSession session){
          	return "login";
      	}
      }
  • @ResponseStatus标识在自定义异常类,底层是ResponseStatusExceptionResolver支持的,底层调用response.sendError(statusCode, resolvedReason)发送responsestatus注解的信息,由tomcat发送到/error

    • @ResponseStatus属性有状态码和原因,标识在自定义异常类上

    • 在需要的时候抛出自定义异常

    @ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户太多")
    public class UserToMany extends RuntimeException {
        public UserToMany() {
        }
        public UserToMany(String message) {
            super(message);
        }
    }
  • Spring底层处理异常,如参数类型转换异常,由DefaultHandlerExceptionResolver处理框架底层的异常

    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage())

  • 自定义实现HandlerExceptionResolver处理异常,配置最高优先级则可以作为默认的全局异常处理规则

@Order(value = Ordered.HIGHEST_PRECEDENCE)//值越小优先级越高
@Component
public class CustomerHanderException implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, java.lang.Exception ex) {
        try {
            response.sendError(511, "自定义错误");
        } catch (java.lang.Exception e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

image-20230803224206418

  • ErrorViewResolver实现自定义处理异常

    • 显式调用response.sendError(...) ,error请求就会转给basicErrorController

    • 异常没有任何解析器能处理,则tomcat底层调用response.sendError(..),error请求就会转给basicErrorController

    • basicErrorController 要去的页面地址是由ErrorViewResolver指定的规则决定的(根据状态码跳转)

异常处理自动配置原理
  • ErrorMvcAutoConfiguration 自动配置异常处理规则

  • ErrorMvcAutoConfiguration容器中的组件

    • 类型:DefaultErrorAttributes -> id:errorAttributes

      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver

      • 定义错误页面中可以包含哪些数据

    image-20230802172409065

    image-20230802172603631

    • 类型:BasicErrorController --> id:basicErrorController

      • 适配响应json和白页

        • 响应json数据

        img

        • 响应html错误页

        image-20230802171857790

      • 默认处理/error路径的请求(可以根据server.error.path修改默认路径),页面响应 new ModelAndView("error", model)

    • 类型:View->id:error

      • 响应默认错误页【StaticView,白页】

    • 类型: BeanNameViewResolver

      • 视图解析器

      • 按照返回的视图名作为组件的id去容器中找View对象(配合view组件使用)

    • 类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver

      • 如果发生错误,会以HTTP的状态码作为视图页地址(viewName),找到真正的页面

      • error/404、5xx.html

异常处理流程
  1. 目标方法运行期间有任何异常都会被catch、而且标志当前请求结束,并且用dispatchException封装所有异常

  2. 进入视图解析流程 processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException)

    • 目标方法正常执行mv才有值,所以此处mv值为null

  3. mv = processHandlerException(.....)处理handler发生的异常,处理完成返回ModelAndView

    1. 遍历handlerExceptionResolvers【是HandlerExceptionResolver处理器异常解析器构成的list】,看谁能处理当前异常

    img

    1. 系统默认的异常解析器;

    image-20230802224048519

    1. DefaultErrorAttributes先来处理异常,把异常信息保存到request域,并且返回null

    2. 默认没有任何解析器能处理异常,所以异常会被抛出

      1. 如果没有处理,最终底层会发送 /error请求,被底层的BasicErrorController处理

      2. 解析错误视图,遍历所有的ErrorViewResolver 看谁能解析

      img

      1. 默认的DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址凭借到默认异常路径error,error/500.html

      2. 模板引擎最终响应这个页面 error/500.html

web原生组件(Servlet、Filter、Listener)注入

使用servletapi
  • @ServletComponentScan(basePackages =.....) 配置类中指定原生Servlet组件的位置

  • @WebServlet(urlPatterns = "/my")标识在原生servlet,用于路径匹配,不会被spring中的拦截器拦截,原因见拓展

  • @WebFilter(urlPatterns={"拦截路径"})标识在原生Filter

  • @WebListener标识在原生Listener

  • 扩展

    • DispatchServlet如何注册进来

      • 容器中自动配置了DispatcherServlet ,属性绑定到 WebMvcProperties,对应的配置文件配置项是spring.mvc

      • 通过 ServletRegistrationBean<DispatcherServlet> 把 DispatcherServlet配置进来

      • 默认映射的是 / 路径

    • Tomcat中多个Servlet都能处理到同一层路径,采取精确优选原则

使用RegistrationBean
  • 原生web组件分别对应ServletRegistrationBean, FilterRegistrationBean,ServletListenerRegistrationBean

  • @Configuration的值不用设置,这样就可以实现单实例,防止冗余

@Configuration
public class MyRegistConfig {
    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();
        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }
    @Bean
    public FilterRegistrationBean myFilter(){
        MyFilter myFilter = new MyFilter();
		//return new FilterRegistrationBean(myFilter,myServlet());拦截myServlet匹配的所有路径
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }
    @Bean
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}

嵌入式web容器

切换和原理
  • 内嵌服务器,就是手动把启动服务器的代码调用(使用tomcat服务器前提是tomcat核心jar包存在)

  • 切换服务器

    • 先移除默认的tomcat服务器

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    • 然后导入对应的服务器

    image-20230804171705168

  • 原理

    • SpringBoot应用启动发现当前是Web应用(web场景包中导入tomcat)

    • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext`

    • 该容器启动的时候寻找 ServletWebServerFactory(Servlet的web服务器工厂)

      • SpringBoot底层提供的WebServer工厂:TomcatServletWebServerFactory、JettyServletWebServerFactory、UndertowServletWebServerFactory..

      image-20230804171158277

    • 底层有自动配置类:ServletWebServerFactoryAutoConfiguration,其中导入

      • ServletWebServerFactoryConfiguration配置类,根据动态判断系统中导入的Web服务器的包(默认web-starter导入tomcat包),创建对应的容器web服务器工厂(即默认创建TomcatServletWebServerFactory)

    • TomcatServletWebServerFactory创建出Tomcat服务器并启动,TomcatWebServer的构造器拥有初始化方法initialize()调用this.tomcat.start();

切换嵌入式Servlet容器
  • 实现 WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>

    • 把配置文件的值和ServletWebServerFactory进行绑定

    • xxxxxCustomizer:定制化器,可以改变xxxx的默认规则

  • 修改配置文件server.xxx

  • 直接自定义ConfigurableServletWebServerFactory

定制化原理

  • 定制化的常见方式

    • 修改配置文件

    • xxxxxCustomizer;

    • 编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件

    • Web应用编写配置类实现WebMvcConfigurer+@Bean即可定制化web功能并且给容器扩展组件

      @Configuration
      public class AdminWebConfig implements WebMvcConfigurer
    • @EnableWebMvc + WebMvcConfigurer+@Bean可以全面接管SpringMVC,所有规则需要重新配置

      • 原理

        1. WebMvcAutoConfiguration是默认的SpringMVC的自动配置功能类

        2. 一旦使用@EnableWebMvc ,会@Import(DelegatingWebMvcConfiguration.class)

          • DelegatingWebMvcConfiguration只保证SpringMVC最基本的使用

        • 把系统中的所有WebMvcConfigurer拿过来(所有功能的定制都是这些WebMvcConfigurer合起来一起生效

          • 自动配置了一些底层的组件:RequestMappingHandlerMapping等并且组件所依赖的组件都是从容器中获取

        1. DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport

          • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport

        2. WebMvcAutoConfiguration里面的配置要能生效要求容器中没有WebMvcConfigurationSupport

          • @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

        3. @EnableWebMvc最终会导入WebMvcConfigurationSupport,从而导致了WebMvcAutoConfiguratio失效

  • 定制化套路

    • 场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties -- 绑定配置文件项

数据访问

SQL

导入依赖
  • 导入jdbc场景

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

image-20230804180256002

  • 导入相应数据库驱动

    • jdbc场景中没有导入数据驱动,是因为官方不知道我们要操作的数据库

    • 数据库版本要和驱动版本对应默认版本:<mysql.version>8.0.22</mysql.version>,想要修改版本

      1. 依赖引入具体版本(maven的就近依赖原则)

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        	<version>5.1.49</version>
        </dependency>
      2. 重新声明版本(maven的属性就近优先原则)

        <properties>
            <java.version>1.8</java.version>
            <mysql.version>5.1.49</mysql.version>
        </properties>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
自动配置
  • DataSourceAutoConfiguration:数据源的自动配置

    • 修改数据源相关的配置:spring.datasource,没有配置或者未正确配置都会导致项目无法启动

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
        username: root
        password: 1212go12
    • 默认的数据源是容器中没有自定义的DataSource才自动配置的

      • 底层配置好的连接池是HikariDataSource

    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
            DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
            DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
    protected static class PooledDataSourceConfiguration
  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置

  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud(了解就行)

    • 可以修改这个配置项@ConfigurationProperties(prefix = "spring.jdbc") 来修改JdbcTemplate

    • 容器中有 JdbcTemplate这个组件,可以通过自动装配注入

    @Slf4j
    @SpringBootTest
    class Boot05WebAdminApplicationTests {
        @Autowired
        JdbcTemplate jdbcTemplate;
        @Test
        void contextLoads() {
            Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class);
            log.info("记录总数:{}",aLong);
        }
    }
    • JndiDataSourceAutoConfiguration: jndi的自动配置

  • XADataSourceAutoConfiguration: 分布式事务相关的

使用Druid数据源
自定义的方式
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.17</version>
</dependency>
  • 自定义数据源配置类

    • 由于默认的数据源是判断容器中没有自定义数据源才会配置【@ConditionalOnMissingBean(DataSource.class) 】,所以配置自定义数据源会使默认数据源失效

    • 使用@ConfigurationProperties注解与配置文件的属性绑定

@Configuration
public class MyDataSourceConfig {
    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }
}
  • 配置druid的监控页功能

    • 要在数据源配置中先开启监控功能和防火墙功能

      • druidDataSource.setFilters("stat,wall");

      • 在配置文件中配置filter: stat,wall

@Bean
public ServletRegistrationBean statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();
    ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
    registrationBean.addInitParameter("loginUsername","admin");//监控页的访问账号和密码
    registrationBean.addInitParameter("loginPassword","123456");
    return registrationBean;
}
  • 配置WebStatFilter,用于采集web-jdbc关联监控的数据

@Bean
public FilterRegistrationBean webStatFilter(){
    WebStatFilter webStatFilter = new WebStatFilter();
    FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(webStatFilter);
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
    filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
    //不拦截的内容
    return filterRegistrationBean;
}
引入场景依赖的方式
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>
  • 自动配置

    • 扩展配置项 spring.datasource.druid

    • 组件

      • DruidSpringAopConfiguration.class, 监控SpringBean【组件】,配置项:spring.datasource.druid.aop-patterns

      • DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet(默认开启)

      • DruidWebStatFilterConfiguration.class, web监控配置:spring.datasource.druid.web-stat-filter(默认开启)

      • DruidFilterConfiguration.class,Druid的所有filter的配置

      private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
      private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
      private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
      private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
      private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
      private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
      private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
      private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
  • 配置示例

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 1212go12
    driver-class-name: com.mysql.cj.jdbc.Driver

    druid:
      aop-patterns: com.atguigu.admin.*  #监控SpringBean
      filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)

      stat-view-servlet:   # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin
        resetEnable: false

      web-stat-filter:  # 监控web
        enabled: true
        urlPattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'

      filter:
        stat:    # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          logSlowSql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false # 不允许删除表操作
!!!整合mybatis
  • 官方文档https://github.com/mybatis

  • 导入场景依赖

    • SpringBoot官方的Starter:spring-boot-starter-*

    • 第三方的Starter: *-spring-boot-starter

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>

image-20230806160737435

  • mybatis配置类

    @EnableConfigurationProperties(MybatisProperties.class) : 
    @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
    public class MybatisAutoConfiguration{}
    • SqlSessionFactory: 自动配置好了

    • SqlSession:自动配置SqlSessionTemplate,组合了SqlSession

    • Mapper:mapper接口标识了@Mapper就会被自动扫描

  • 配置mybatis规则

    • MyBatis配置项绑定类

    @ConfigurationProperties(prefix = "mybatis")
    public class MybatisProperties
    • 扫描全局配置文件和mapper.xml文件

    mybatis:
      config-location: classpath:mybatis/mybatis-config.xml  #全局配置文件位置
      mapper-locations: classpath:mybatis/mapper/*.xml  #sql映射文件位置
    • 配置mybatis.configuration,就可以修改mybatis全局配置文件中的值

      • 导入全局配置文件和使用配置项不能同时存在

    mybatis:
    # config-location: classpath:mybatis/mybatis-config.xml 
      mapper-locations: classpath:mybatis/mapper/*.xml
      configuration: # 指定mybatis全局配置相关项
        map-underscore-to-camel-case: true # 开启下划线转驼峰
  • 使用步骤

    • 导入mybatis官方starter

    • 编写mapper接口,标识@Mapper注解

      • 在配置类上使用@MapperScan("com.atguigu.admin.mapper") 扫描mapper接口所在包,包中的接口就可以不用标注@Mapper注解

    • 编写sql映射文件并绑定mapper接口

    • 在application.yaml中指定Mapper配置文件的位置,以及指定全局配置文件信息 (建议:使用mybatis.configuration配置项)

整合mybatis-plus
基础操作
  • 背景

  • 导入场景依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
  • 自动配置

    • 配置类:MybatisPlusAutoConfiguration,配置项绑定类:MybatisPlusProperties,前缀:mybatis-plus

      • SqlSessionFactory 自动配置好,底层是容器中默认的数据源

    • mapperLocations自动配置好,默认值:classpath*:/mapper/**/*.xml(任意包的类路径下的mapper文件夹任意路径的所有xml)

    • 容器中自动配置好SqlSessionTemplate

    • @Mapper标注的接口会被自动扫描,建议直接 @MapperScan("mapper接口所在包") 批量扫描就行

  • 优点:Mapper接口只需要继承BaseMapper<Pojo类> 就可以实现基本的crud能力

crud实战
数据访问
  • pojo

    • mybatis-plus要求映射的属性必须都在表中有对应(有点鸡肋)

      • 使用@TableField标识属性并设置值为false,即声明该属性在表中不存在

      image-20230806180041930

    • 类名和表名一致就可以匹配

      • 类上标识@TableName,值为数据库中实际的表名,也可以进行匹配

      image-20230807144628186

  • service接口继承IService<pojo类>

public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService 
  • service的实现类继承ServiceImpl<Mapper接口,pojo类>

public interface UserService extends IService<User>
分页功能
  • 配置分页插件

@Bean
public MybatisPlusInterceptor paginationInnerInterceptor() {
 MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
 PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();//分页拦截器
 mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
 return mybatisPlusInterceptor;
}
  • 控制器方法

    • 使用page对象获取分页信息

    • 接收浏览器发送的当前页码(此参数要设置为非必须,如果没有当前页码信息就默认为第一页)

  @RequestMapping("/dynamic_table")
    public String toDT(Model model,@RequestParam(value = "pn",required = false) Integer pn) {
        List<User> users = userService.list();
        model.addAttribute("users",users);
        if(pn==null){
            pn=1;
        }
        Page<User> userPage = new Page<>(pn, 2);//pn为浏览器发送过来的当前页码,size为每页的条数
        IPage<User> page = userService.page(userPage);
        model.addAttribute("page",page);
        return "table/dynamic_table";
    }
  • 前端代码

    • 遍历分页数据:page.records封装分页数据

    <tr class="gradeX" th:each="user,stat: ${page.records}">
        <td th:text="${stat.index}"></td>
        <td th:text="${user.username}"></td>
        <td th:text="${user.age}"></td>
        <td th:text="${user.email}"></td>
        <td></td>
    </tr>
    • 分页导航栏

    <ul>
        <li class="prev disabled"><a href="#">← 前一页</a></li>
        <li th:class="${num == page.current?'active':''}"  			#三目运算判断是否为当前页,是就渲染 th:each="num:${#numbers.sequence(1,page.pages)}" > 			   #工具类提供的方法,作用是生成一串连续整数
            <a th:href="@{/table/dynamic_table(pn=${num})}">[[${num}]]</a>
        </li>
        <li class="next disabled"><a href="#">下一页 → </a></li>
    </ul>
删除功能
  • 前端代码

    • {路径变量}(请求参数1,参数2....)

<td>
    	<a th:href="@{/user/delete/{id}(id=${user.id},pn=${page.current})}" class="btn btn-danger btn-sm" 		type="button">删除</a>
</td>
  • 控制器方法

    • 使用重定向,接收页面发送过来的当前页码,以保证删除之后还在同一页

    • 接收id用于删除单行记录

@Controller
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserServiceImpl userService;
    @GetMapping("delete/{id}")
    public  String delete(@PathVariable Integer id,
                          @RequestParam(value = "pn",defaultValue = "1") Integer page
            ,RedirectAttributes redirectAttributes){
        userService.removeById(id);
        redirectAttributes.addAttribute("pn",page);
        return "redirect:/table/dynamic_table";
    }
}

NoSql

Redis自动配置
  • 简介

    • Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件

    • 它支持多种类型的数据结构,如字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sorted sets) 与范围查询,bitmaps,hyperloglogs和地理空间(geospatial) 索引半径查询

    • Redis 内置了复制(replication),LUA脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)

  • 导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

image-20230807175811005

  • 自动配置

    • 自动配置类:RedisAutoConfiguration,绑定属性类:RedisProperties -->前缀: spring.redis

    • 连接工厂是准备好的:LettuceConnectionConfiguration、JedisConnectionConfiguration

    • 自动注入RedisTemplate<Object, Object> ,用于操作Redis

    • 自动注入StringRedisTemplate(k:v都是String)

    • 底层只要我们使用StringRedisTemplate、RedisTemplate就可以操作redis

  • redis环境搭建

    1. 阿里云按量付费redis。经典网络

    2. 申请redis的公网连接地址

    3. 修改白名单 ,允许0.0.0.0/0 访问

    image-20230808161044630

    1. 使用完一定要释放(不然钱没了)

    image-20230807183338697

切换至jedis
  • 导入jedis

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
  • 配置文件声明使用jedis

spring:
  redis:
      host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
      port: 6379
      password: lfy:Lfy123456
      client-type: jedis
      jedis:
流量统计功能
  • 拦截器

@Component
public class RedisUrlCountInterceptor implements HandlerInterceptor {
	@Autowired
    StringRedisTemplate redisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        //默认每次访问当前uri就会计数+1
        redisTemplate.opsForValue().increment(uri);
        return true;
    }
}
  • 在拦截器配置类中,自动装配流量统计的拦截器,将其加入到其进行路径配置

@Autowired
RedisUrlCountInterceptor redisUrlCountInterceptor;    

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor())
            .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
            .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
                    "/js/**","/aa/**"); //放行的请求
//添加流量统计拦截器
    registry.addInterceptor(redisUrlCountInterceptor)
            .addPathPatterns("/**")
            .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
                    "/js/**","/aa/**");
}
  • 控制器方法

    • 自动装配RedisTemplate用以操作redis

@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
    ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
    String s = opsForValue.get("/main.html");
    String s1 = opsForValue.get("/sql");
    model.addAttribute("mainCount",s);
    model.addAttribute("sqlCount",s1);
    return "main";
}
面试题
  • Filter、Interceptor 几平拥有相同的功能?

    • Filter是servlet定义的原生组件,好处是脱离Spring应用也能使用

    • Interceptor是spring定义的接口,只能在spring中使用,但是可以使用spring的自动装配功能

单元测试

JUnit5

相关背景
  • SpringBoot2.2.0版本开始引入 JUnit5作为单元测试默认库

  • 作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同,由三个不同子项目的几个不同模块组成

    image-20230807234401970

    • JUnit Platform: 是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入

    • JUnit Jupiter:提供了JUnit5的新的编程模型,是JUnit5新特性的核心,内部包含了一个测试引擎,用于在Junit Platform上运行

    • JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎

      • SpringBoot 2.4以上版本移除了默认对 Vintage 的依赖,如果需要兼容junit4需要自行引入(不能使用junit4的功能@Test)

      • 如果需要继续兼容junit4需要自行引入vintage

      <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>
  • SpringBoot整合Junit以后

    • 编写测试方法:使用@Test标注(注意需要使用junit5版本的注解)

    • Junit类具有Spring的功能(前提是测试类要标识@SpringBootTest),例如

      • @Autowired进行装配

      • @Transactional标注测试方法,测试完成后自动回滚

常用注解
  • JUnit5的注解与JUnit4的注解有所变化,参照文档JUnit 5 User Guide

  • @Test :表示方法是测试方法。与JUnit4的@Test不同,他的职责单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试

  • @ParameterizedTest :表示方法是参数化测试

  • @RepeatedTest :表示方法可重复执行,值为重复测试的次数

  • @DisplayName :为测试类或者测试方法设置展示名称

    image-20230807235821107

    ==》

    image-20230807235813365

  • @BeforeEach :表示在每个单元测试之前执行

  • @AfterEach :表示在每个单元测试之后执行

  • @BeforeAll :表示在所有单元测试之前执行,必须标识在静态方法上

  • @AfterAll :表示在所有单元测试之后执行,必须标识在静态方法上

  • @Tag :表示单元测试类别,类似于JUnit4中的@Categories

  • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore

  • @Timeout :表示测试方法运行超过了指定时间将会返回错误,单位秒

    • 可以指定unit属性来设置单位

image-20230808000445855

  • @ExtendWith :为测试类或测试方法提供扩展类引用

断言
定义
  • 断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证,即先断定结果,如果运行结果一致才为合法

  • 优点

    • 检查业务逻辑返回的数据是否合理

    • 所有测试运行结束以后,会有一个详细的测试报告

  • 单元测试方法内部出现断言失败,后面代码不会执行

  • 断言方法都是org.junit.jupiter.api.Assertions静态方法,可以通过Asserttions.方法() 调用

JUnit5内置的断言类别
简单断言
  • 用来对单个值进行简单的验证

方法说明
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null
数组断言
  • 通过assertArrayEquals方法来判断两个对象或原始类型的数组是否相等

@Test
@DisplayName("array assertion")
public void array() {
 assertArrayEquals(new int[]{1, 2}, new int[] {1, 2}); 相等
 assertArrayEquals(new int[]{2,1}, new int[] {1, 2}); 不相等
}

image-20230808002959276

组合断言
  • 组合中的全部断言都成功才为成功

  • assertAll方法接受多个函数式接口的实例作为要验证的断言,可以通过 lambda 表达式提供这些断言

@Test
public void all() {
 assertAll("组合名",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
 );
}
异常断言
  • JUnit5提供了一种新的断言方式Assertions.assertThrows() ,可以配合函数式编程进行使用

  • 抛出的异常和断言的一致才会成功,即我赌你会抛这个异常

@Test
public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
           //扔出断言异常
            ArithmeticException.class, () -> System.out.println(1 % 0));
}
超时断言
  • Junit5提供Assertions.assertTimeout() 为测试方法设置了超时时间

@Test
@DisplayName("超时测试")
public void timeoutTest() {
    //如果测试方法时间超过1s将会失败
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
快速失败
  • 通过fail方法直接使得当前测试失败

@Test
public void shouldFail() {
 	fail("This should fail");
}
前置条件
  • JUnit 5中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止,不会使方法失败

image-20230808164803045

  • 前置条件可以看成是测试方法执行的前提(当前提不满足时就没有继续执行的必要)

@DisplayName("前置条件")
public class AssumptionsTest {
     private final String environment = "DEV";
     @Test
     @DisplayName("simple")
     public void simpleAssume() {
        assumeTrue(Objects.equals(this.environment, "DEV"));
        assumeFalse(() -> Objects.equals(this.environment, "PROD"));
     }
     @Test
     @DisplayName("assume then do")
     public void assumeThenDo() {
        assumingThat(
           Objects.equals(this.environment, "DEV"),
           () -> System.out.println("In DEV")
        );
     }
}
  • assumeTrue和assumFalse 确保给定的条件为true或false,不满足条件会使得测试执行终止,即后续代码不执行

image-20230808164922567

  • assumingThat的参数是表示条件的布尔值和对应的Executable接口的实现对象

    • 只有条件满足时,Executable对象才会被执行

    • 当条件不满足时,测试执行并不会终止

    image-20230808164002045

嵌套测试
  • JUnit 5可以通过 Java 中的内部类@Nested注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起

  • 在内部类中可以使用@BeforeEach和@AfterEach 注解,而且嵌套层次没有限制

  • @Nested标识在内部类上,表示整个类是嵌套测试

  • 外层的测试方法不能驱动内层的@BeforeEach/@AfterEach等方法,但是内层的test可以驱动外层的

image-20230808165023495

@DisplayName("A stack")
class TestingAStackDemo {
    Stack<Object> stack;
    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }
    
    @Nested //内层
    @DisplayName("when new")
    class WhenNew {
        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }
        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }
        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }
        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }
        
        @Nested //内内层
        @DisplayName("after pushing an element")
        class AfterPushing {
            String anElement = "an element";
            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }
            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }
            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }
            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}
!!!参数化测试
  • 参数化测试是JUnit5很重要的一个新特性

    • 利用@ValueSource等注解,指定入参,可以使用不同的参数进行多次单元测试

    • 不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码

    • 支持外部的各类入参(CSV,YML,JSON文件甚至方法的返回值)也可以,只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参

  • 相关注解

    • @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");
}

迁移指南

  • 在进行迁移的时候需要注意如下的变化:

    • 注解在 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场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能

  • 不同版本的不同

image-20230808170538057

使用
  • 引入依赖

image-20230808170453652

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

Actuator Endpoint

管理Endpoints
  • management是所有actuator的配置前缀,其中endpoints属性用于配置端点

  • 默认除shutdown之外的所有端点都开启

    • 开启或者禁用某个Endpoint的配置 management.endpoint.endpointName.enabled = true

      • 对某个端点进行配置 :management.endpoint.具体端点.xxx

    management:
      endpoint:
        beans:
          enabled: true
    • 配置开启所有监控端点(默认开启)

    management:
      endpoints:
        enabled-by-default: true #暴露所有端点信息
  • spring底层有两种监控模式

    • JMX:默认暴露所有Endpoint

    • HTTP:默认只暴露healthinfoEndpoint

      • 配置以http方式暴露所有端点

    management:
      endpoints:
        enabled-by-default: true #暴露所有端点信息
        web:
          exposure:
            include: '*'  #以web方式暴露
    • 除过health和info,剩下的Endpoint都应该进行保护访问,如果引入SpringSecurity,则会默认配置安全访问规则

常用端点
  • 最常用的Endpoint

    • Health:监控状况

    • Metrics:运行时指标

    • Loggers:日志记录

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.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus
Health Endpoint
  • 健康检查端点,一般用于在云平台(平台会定时的检查应用的健康状况),可以为平台返回当前应用的一系列组件健康状况的集合

  • 重要的几点:

    • health endpoint返回的结果是一系列健康检查后的一个汇总报告

      • 所有组件全健康才为健康

    • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等

    • 可以很容易的添加自定义的健康检查机制

image-20230808181055063

  • 配置健康端点显示详细信息(默认不启用)

management:
  endpoint:
  	health:
  		show-details: always
Metrics Endpoint
  • 提供详细的、层级的、空间指标信息,这些信息可以通过pull(主动推送)或者push(被动获取)方式得到

    • 通过Metrics对接多种监控系统

    • 简化核心Metrics开发

    • 添加自定义Metrics或者扩展已有Metrics

  • 可以查看每一级具体的信息http://localhost:8080/actuator/metrics/jvm.gc.pause

img

定制Endpoint
定制 Health 信息
  • 实现HealthIndicator接口,重写检查方法

    @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();
  • 也可以继承AbstractHealthIndicator类

    @Component
    public class MyComHealthIndicator extends AbstractHealthIndicator {
        //真实的检查方法
        @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);
            }else {
        //builder.down();
                builder.status(Status.OUT_OF_SERVICE);
            }
            builder.withDetail("code",100)
                    .withDetails(map);	//保存详细的健康详细信息
        }
    }
定制info信息
  • 有两种方式

    1. 编写配置文件

    info:
      appName: boot-admin
      version: 2.0.1
      mavenProjectName: @project.artifactId@  #使用@@可以获取maven的pom文件值
      mavenProjectVersion: @project.version@
    1. 编写InfoContributor

      • 使用withDetail编写key-value信息

    @Component
    public class ExampleInfoContributor implements InfoContributor {
        @Override
        public void contribute(Info.Builder builder) {
            builder.withDetail("msg","你好")
                .withDetail("hello","world")
                   .withDetail(Collections.singletonMap("key", "value"));
        }
    }
定制Metrics信息
  1. 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

  2. 增加定制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....");
    }
}
  • @ReadOperation标识读方法,即访问相应端点会展示的内容

  • @WriteOperation标识操作方法

  • 场景:开发ReadinessEndpoint来管理程序是否就绪,或者LivenessEndpoint来管理程序是否存活

定制可视化界面

  • 创建一个新应用专门用于监控

    • 只需要选中web开发场景

    image-20230808232953116

    • 引入应用场景

    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-server</artifactId>
        <version>2.3.1</version>
    </dependency>
    • 在主配置类上使用@EnableAdminServer,开启admin监控功能

    • 修改默认端口号(假设改为8888)

  • 需要监控的项目

    • 引入监控客户端

    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>2.3.1</version>
    </dependency>
    • 配置监控信息

    boot:
    	admin:
    	client:
    		url: http://localhost:8888 #监控客户端
                instance:
                    prefer-ip: true #使用ip注册进来 	
    application:
        name: boot-05-web-admin	

原理解析

Profile功能

  • 为了方便多环境适配,springboot简化了profile功能

  • application-profile功能

    • 默认配置文件 application.yaml/properties(不带标识的),任何时候都会加载

    • 指定环境配置文件 application-指定环境.yaml

image-20230808235243595

  • 激活指定环境

    • 在默认配置文件中激活

      • spring.profiles.active=指定环境

image-20230808235305529

  • 命令行激活:java -jar 项目.jar --spring.profiles.active=prod --person.name=haha

    image-20230808235909982

    • 修改配置文件的任意值,命令行优先

    • 可以在部署jar包时修改环境

  • 默认配置与环境配置同时生效

  • 同名配置项,profile配置优先(精确优先)

  • @Profile条件装配功能

    • @Profile("具体环境")

    • 即所标识的类/方法在什么环境下生效

    @Configuration(proxyBeanMethods = false)
    @Profile("production")
    public class ProductionConfiguration {
    }
  • profile分组

    • 配置spring.profiles.group.组名[0]=成员1,spring.profiles.group.组名[1]=成员2.....

    • 激活一个组,组内的所有环境都会生效

    spring.profiles.group.production[0]=proddb
    spring.profiles.group.production[1]=prodmq
    
    spring.profiles.active=production  激活

外部化配置

基本信息
  • 即把配置信息放在外部统一管理,配置信息属性值可以使用注释直接注入到您的 bean

  • 外部配置源,常用

    • Java属性文件(properties)

    • YAML文件

    • 环境变量

      • 可以在主程序类获取系统环境变量和属性相关信息

      ConfigurableApplicationContext run = SpringApplication.run(WebApplication.class, args);
      ConfigurableEnvironment environment = run.getEnvironment();
      Map<String, Object> systemEnvironment = environment.getSystemEnvironment();//获取系统环境变量
      Map<String, Object> systemProperties = environment.getSystemProperties();//获取系统属性
      System.out.println(systemEnvironment);
      System.out.println(systemProperties);
      • 使用环境变量/系统属性给属性赋值

      @Value("${MAVEN_HOME}")
      String msg;
      @Value("${os.name}")
      String os;
      @RequestMapping("/msg")
      @ResponseBody
      public String msg(){
          return msg+"\n"+os;
      }

      image-20230809140205345

    • 命令行参数

    image-20230809134223246

重写规则
  • Spring Boot使用一个特别的配置源顺序,旨在允许合理地重写值。 后面的配置源可以覆盖前面配置源源中定义的值。 按以下顺序考虑。

    1. 默认属性(通过 SpringApplication.setDefaultProperties 指定)。

    2. @Configuration 类上的 @PropertySource 注解。请注意,这样的属性源直到application context被刷新时才会被添加到环境中。这对于配置某些属性来说已经太晚了,比如 logging.*spring.main.* ,它们在刷新开始前就已经被读取了

    3. 配置数据(如 application.properties 文件)

    4. RandomValuePropertySource,它只有 random.* 属性

    5. 操作系统环境变量

    6. Java System properties (System.getProperties())

    7. java:comp/env 中的 JNDI 属性

    8. ServletContext init parameters

    9. ServletConfig init parameters

    10. 来自 SPRING_APPLICATION_JSON 的属性(嵌入环境变量或系统属性中的内联JSON)

    11. 命令行参数

    12. 测试中的 properties 属性。在 @SpringBootTest 和测试注解中可用,用于测试你的应用程序的一个特定片断

    13. @DynamicPropertySource 注解在你的测试中

    14. 你测试中的TestPropertySource (Spring Framework 6.1.0-M1 API)[@TestPropertySource] 注解.

    15. 当devtools处于活动状态时,$HOME/.config/spring-boot 目录下的Devtools全局设置属性

配置文件查找位置
  1. classpath根路径

  2. classpath根路径下config目录

  3. 项目打包后jar包当前目录

image-20230809142713117

  1. jar包当前目录的config目录

image-20230809142805063

  1. /config子目录的直接子目录(一层子目录下,只能在linux系统使用 )

image-20230809142936243

image-20230809143005673

  • 同名配置属性也是后面的配置源覆盖前面的配置源

配置文件加载顺序
  1. 当前jar包内部的application.properties和application.yml

  2. 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml

  3. 引用的外部jar包的application.properties和application.yml

  4. 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

  • 后面覆盖前面,即指定环境优先,外部优先

自定义starter
starter启动原理

image-20230809143444645

  • starter-pom引入 autoconfigurer 包

  • autoconfigure包中配置使用META-INF/spring.factoriesEnableAutoConfiguration的值,使得项目启动加载指定的自动配置类

  • 编写自动配置类 xxxAutoConfiguration -> xxxxProperties

    • @Configuration

    • @Conditional

    • @EnableConfigurationProperties

    • @Bean

引入starter --- xxxAutoConfiguration --- 容器中放入组件 ---- 绑定xxxProperties ---- 配置项

自定义starter
  • 创建场景启动器

  • 创建自动配置模块,所有自定义配置都放在该模块

  • 场景启动器要引入自动配置模块

<dependency>
    <groupId>com.example</groupId>
    <artifactId>mystarter-autoconfiguration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
  • 在自动配置模块中

    • 编写业务类HelloService,默认不放在容器中

    public class HelloService {
        @Autowired
        HelloProperties properties;
        public String sayHello(String username){
            return properties.getPrefix()+username+properties.getSuffix();
        }
    }
    • 编写配置类属性绑定类HelloProperties,设置配置项hello

    @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;
        }
    }
    • 编写配置类,在容器中注入组件,并且开启属性文件绑定功能(绑定HelloProperties)

    @Configuration
    @EnableConfigurationProperties(HelloProperties.class)
    @ConditionalOnMissingBean(HelloService.class)//即容器中没有相应的业务类就使用默认的业务类
    public class HelloAutoConfiguration {
        @Bean
        public HelloService service(){
            return new HelloService();
        }
    }
    • 创建META-INF/spring.factories文件,指定项目启动要默认启动的配置类

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.example.config.HelloAutoConfiguration
  • 使用clean,install对启动器和自动配置包进行打包

  • 引用自定义场景启动器

    • 引入启动器依赖

    <groupId>com.example</groupId>
    <artifactId>mystarter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    • 根据配置项配置属性

    hello:
      suffix: 45
      prefix: 66
    • 获取组件,编写控制器方法

    @Autowired
    HelloService service;
    @RequestMapping("/service")
    @ResponseBody
    public String servie(){
        return service.sayHello("liao");
    }
    • 测试结果

    image-20230809152155968

原理解析

springboot启动过程
创建 SpringApplication
  1. 使用ClassUtils工具类来判定当前应用的类型(一般是servlet),然后设置项目类型

  2. 从spring.factories文件中的org.springframework.boot.Bootstrapper获取所有初始启动引导器Bootstrapper,然后通过(List<Bootstrapper>)bootstrappers封装

  3. 从spring.factories找所有ApplicationContextInitializer(应用初始化器),保存到(List<ApplicationContextInitializer<?>)initializers

image-20230809182345177

  1. 从spring.factories找ApplicationListener(应用监听器),保存到(List<ApplicationListener<?>)listeners

  • image-20230809173542590

    方法都是去spring.factories找相关配置

运行 SpringApplication
  1. StopWatch【监听整个应用程序启动停止的监听器】记录应用的启动时间

  2. createBootstrapContext()创建引导上下文(Context环境)

    • 获取到之前保存好信息的bootstrappers,挨个执行intitialize() 来设置引导启动器上下文环境

  3. 让当前应用进入headless模式(java.awt.headless,自力更生模式)

  4. 获取所有SpringApplicationRunListener(运行监听器)

    • 调用getSpringFactoriesInstances() 从spring.factories找 SpringApplicationRunListener

image-20230809175837917

  1. 调用所有SpringApplicationRunListener的starting方法

    • 通知所有Listener当前项目正在启动,便与所有监听器进行感知

  2. 使用ApplicationArguments保存命令行参数

  3. 调用prepareEnvironment()准备环境

    1. 返回或者创建基础环境信息对象StandardServletEnvironment

    2. 配置环境信息对象

      • 读取所有的配置源的配置属性值

    3. 绑定环境信息

    4. 监听器调用 listener.environmentPrepared()

      • 通知所有的监听器当前环境准备完成

  4. createApplicationContext()创建IOC容器

    • 根据项目类型创建容器

    • 项目类型是Servlet会创建AnnotationConfigServletWebServerApplicationContext()

  5. prepareContext()准备ApplicationContext IOC容器的基本信息

    • 保存环境信息

    • IOC容器的后置处理流程

    • 应用初始化器applyInitializers

      • 遍历所有的 ApplicationContextInitializer,调用 initialize()来对ioc容器进行初始化扩展功能

      • 遍历所有的 listener,调用contextPrepared(),通知所有的监听器容器上下文环境准备好了

        image-20230809182656389

    • 所有的监听器调用contextLoaded(),通知所有的监听器ioc容器已加载

  6. refreshContext()刷新IOC容器

  • 创建容器中的所有组件

  1. 容器刷新完成后调用afterRefresh()

    • listeners.started(context)所有监听器调用started(),通知所有的监听器当前项目已经启动

  2. callRunners()调用所有runner

    1. 获取容器中

      • ApplicationRunner

      • CommandLineRunner

    2. 合并所有runner并且按照@Order进行排序

    3. 遍历所有的runner,调用 run()方法

  • 如果以上有异常,调用Listener的failed(),通知所有的监听器当前失败

  1. 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running

  • running如果有问题, 调用所有Listener的failed()

image-20230809232512109

image-20230809232524227

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值