SpringBoot3全篇学习笔记上篇

快速体验SpringBoot3

一些基本的配置

SpringBoot3基于Java17

maven版本3.5以上

tomcat版本10.0及以上

为什么SpringBoot?

SpringBoot与Spring的存在初衷是一样的:简化Java开发

SpringBoot简化了什么?

  1. 简化开发,

2. 简化配置,

3. 简化整合,

  1. 简化部署,

  2. 简化监控,

  3. 简化运维


代码实操

我们会通过下面的几段简单的代码,一面展示SpringBoot的简单使用方法 ,一面向展示SpringBoot是如何简化配置 以及如何简化整合


1. 创建一个普通的Maven项目(注意java版本)


2. 配置好自己的Maven设置


3.副设置Maven的xml文件父项目坐标

这一步有点特殊,所有的SpringBoot项目都需要继承一个统一的父项目。

xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.5</version>
</parent>

4. 引入web的场景启动器

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>3.0.5</version>
    </dependency>
</dependencies>

这是SpringBoot的一大特点,场景启动器

注意看上面依赖的ArtifactId,名为spring-boot-starter-web 。其中spring-boot-starter表示该场景启动器是由Springboot提供的,所有的Springboot启动器都会以这个开头

引入该启动器意味着什么?

  1. 意味着SpringBoot帮我们配置了SpringMvc相关的所有配置文件

  2. 意味着例如像Jackson之类的Web项目所需所有杂项依赖已经全部引入并配置好了

  3. 意味着帮我们内置并配置好了tomcat

  4. 意味着我们直接写SpringMvc的代码就行了


5. 配置Maven打包插件

xml 复制代码
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

在maven打包设置中加入的这个依赖意味着:Maven的打包会把tomcat直接打进包内 ,向外提供一个可执行的jar包 。只要有java环境,直接使用java -jar命令就可以运行该web项目,无需tomcat等其他环境支持


添加主启动类

Springboot项目需要一个项目启动的入口,我们随便写一个类。

typescript 复制代码
@SpringBootApplication

public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class,args);
    }
}

@SpringBootApplication注解

声明该项目是一个SpringBoot项目

SpringApplication.run

项目的启动方法,需要传入当前类的class对象 以及mian方法的参数(必填)


创建controller包并编写业务代码

  1. 我们在上面的启动 的同级目录下创建一个名为controller这是硬性要求,这是Springboot的约定(包的位置以及包名)

  2. 在controller里面编写一个简单的demo(这一步并没有硬性要求,)

kotlin 复制代码
@RestController
public class helloController {

    @GetMapping("hello")
    public String hello(){
        return "hello";
    }
}

贴一下项目结构

运行测试

我们直接运行我们上面写好的main方法

此时你会发现即便我们没有配置tomcat,但是依然能够运行

我们看一下日志

css 复制代码
INFO 18756 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)

运行在8080端口,访问测试一下http://localhost:8080/hello

如果你把helloController的返回内容换成中文,你会发现,并不会乱码。一切都配好了


神奇的外部配置

我们使用Maven打包该项目

把目标文件复制一份单独取出来

在同级目录下新建名为application.propertise的配置文件(如下图),内部写入

server.port=8086

保存

使用java -jar 运行项目


我们发现项目的端口在项目已经被完整打包的情况下,内部配置被外部的配置文件直接更改

这意味着我们可以在不改动源码的前提下更改项目配置。

快速生成SpringBoot项目与选择启动器

点击下一步后会发现有一大堆可选的启动器,甚至包含Mybatis


Springboot为什么这么方便

1. 依赖管理

我们所有的Springboot项目都继承了一个名为spring-boot-starter-parent的项目,而spring-boot-starter-parent又继承了 spring-boot-dependencies。我们点开它

它内部有着大量的依赖,以及依赖的版本控制 ,也就是说,我们所使用的的依赖,全部都是Springboot背后配好的,所以,当我们引入这些starter时,无需添加版本,只要是添加spring-boot-dependencies中的包含的依赖,都无需添添加版本号

更改版本号

我们根据Maven的就近原则,找到Springboot定义的版本号

bash 复制代码
<version>${mysql.version}</version>

我们只需要在自己的项目中定义相同的properties标签,更改它即可


2. 自动装配

typescript 复制代码
@SpringBootApplication
public class Pro02Application {

    public static void main(String[] args) {
       var ioc = SpringApplication.run(Pro02Application.class, args);
    }

}

我们的主启动类其实是有返回值的,他会把构建出的ioc容器返回给我们

我们打印一下内部的对象 ,看看他有没有帮我们自动配置所需的环境 ,就以requestMappingHandlerMapping为例

css 复制代码
for (String beanDefinitionName : ioc.getBeanDefinitionNames()) {
    System.out.println(beanDefinitionName);
}

对打印结果搜索:

requestMappingHandlerMapping

Springboot自动装配的核心

点开我们的任意一个starter,会发现每个starter都会包含一个名为spring-boot-starter的starter他是所有starter的starter

而这个starter内部包含了一个名为spring-boot-autoconfigure的依赖。这就是Springboot自动装配的核心

找到他所在的包,他里面包含了所有Springboot能做到的自动装配设置,但他们不是全部都生效的只有我们使用了的场景所需的才会生效


Springboot自动装配的实现基石(重点)

springBoot提供了新的注解,名为@Conditionxxxx

我们重点学习以下四个

@ConditionalOnClass:如果类路径中存在这个类,则触发指定行为

@ConditionalOnMissingClass:如果类路径中不存在这个类,则触发指定行为

@ConditionalOnBean:如果容器中存在这个Bean(组件),则触发指定行为

@ConditionalOnMissingBean:如果容器中不存在这个Bean(组件),则触发指定行为

下面是一段简单的代码演示

less 复制代码
@Configuration
public class testIoc {

    @ConditionalOnClass(name = "com.atguigu.aaxx.class")
    @Bean("lisa")
    @Scope()
    public User user(){
        User user = new User();
        user.setUserName("lisa");
        return user;
    }

}

我们设置aaxx类不存在,就会发现内部ioc容器内部没有User的Bean对象

SpringBoot依赖于这些注解,来判断是否进行装配

@ConditionalOnRepositoryType (org.springframework.boot.autoconfigure.data)

@ConditionalOnDefaultWebSecurity (org.springframework.boot.autoconfigure.security)

@ConditionalOnSingleCandidate (org.springframework.boot.autoconfigure.condition)

@ConditionalOnWebApplication (org.springframework.boot.autoconfigure.condition)

@ConditionalOnWarDeployment (org.springframework.boot.autoconfigure.condition)

@ConditionalOnJndi (org.springframework.boot.autoconfigure.condition)

@ConditionalOnResource (org.springframework.boot.autoconfigure.condition)

@ConditionalOnExpression (org.springframework.boot.autoconfigure.condition)
@ConditionalOnClass (org.springframework.boot.autoconfigure.condition)

@ConditionalOnEnabledResourceChain (org.springframework.boot.autoconfigure.web)
@ConditionalOnMissingClass (org.springframework.boot.autoconfigure.condition)

@ConditionalOnNotWebApplication (org.springframework.boot.autoconfigure.condition)

@ConditionalOnProperty (org.springframework.boot.autoconfigure.condition)

@ConditionalOnCloudPlatform (org.springframework.boot.autoconfigure.condition)
@ConditionalOnBean (org.springframework.boot.autoconfigure.condition)
@ConditionalOnMissingBean (org.springframework.boot.autoconfigure.condition)

@ConditionalOnMissingFilterBean (org.springframework.boot.autoconfigure.web.servlet)

@Profile (org.springframework.context.annotation)

@ConditionalOnInitializedRestarter (org.springframework.boot.devtools.restart)

@ConditionalOnGraphQlSchema (org.springframework.boot.autoconfigure.graphql)

@ConditionalOnJava (org.springframework.boot.autoconfigure.condition)

Springboot的一些杂项知识

Springboot配置包扫描

less 复制代码
@SpringBootApplication(scanBasePackages = "???.???.???")

或者我们的@ComponentScan注解

SpringBoot的新的Bean注入

less 复制代码
@Import(User.class)
@Configuration
public class testIoc {

}

@Import注解相当于引入这个类的对象,当然也会把他自动放到IOC中

我们常用的@Bean,或者xmlBean对象依然生效


ConfigurationProperties注解

该注解用于将Properties中的属性绑定到指定的对象中,要求属性名一一对应,提供一个prefix,用于填入Properties中的前缀。

Properties

ini 复制代码
pig.id=1
pig.name=汤姆
pig.age=12

代码:

less 复制代码
@Data
@Component
@ConfigurationProperties(prefix = "pig")
public class pig {
    private Integer id;
    private String name;
    private Integer age;
}

与其相关的@EableConfigurationProperties注解

这个注解一般用于对第三方的对象进行注入,我们假设一下,一个第三方包的类标注了上面的@ConfigurationProperties注解,但是因为它并不在Springboot所指定的扫描位置,但我们还是希望可以对它进行注入,就需要使用@EableConfigurationProperties注解来将它引入IOC并进行注入。

  1. @EableConfigurationProperties代替了@Component,无需指定的对象自己放入IOC

  2. 它可以放入一个Class对象,表示对指定的类进行注入

  3. 它可以放在任意位置,只要能被Springboot扫描到

less 复制代码
@Data
@ConfigurationProperties(prefix = "pig")
public class pig {
    private Integer id;
    private String name;
    private Integer age;
}
less 复制代码
@SpringBootApplication()
@EnableConfigurationProperties(pig.class)
public class Pro02Application {

    public static void main(String[] args) {
       var ioc = SpringApplication.run(Pro02Application.class, args);
       pig bean = ioc.getBean(pig.class);
       System.out.println(bean);
    }

}

Springboot自动装配原理

Springboot是如何完成对相关场景所需环境的装配的?

首先我们需要找到程序的入口,启动的初始位置。当然就是@SpringBootApplication,我们打开该注解的源码,会看到一个名为@EnableAutoConfiguration的注解,其含义为开启自动配置。它就是自动装配的真正启动类,下面是它的源码。

less 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

我们可以看到,它使用@Import注解引入了一个名为AutoConfigurationImportSelector.class的类,首先@Import注解的作用是将外部的类放入IOC中,其次AutoConfigurationImportSelector.class的含义为自动配置导入选择器,所以自动装配的关在就在此处。

在它的源码中我们可以看到:

arduino 复制代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

获得候选者配置,他有一个断言报错信息:判断某个文件不存在。可以猜到,这个文件是自动装配的候选名单,我们在依赖中找到它

这是一个Properties文件,内部定义了大量的类名(多达146个),均以AutoConfiguration 结尾。我们发现内部甚至包含了我们用不到的一些配置,例如redis的配置。这个文件其实是Springboot所能够自动装配的所有配置的名单

但我们其实只需要让他们中的一部分生效即可,例如我们刚导入的web相关的场景,我们从其中可以找到相关的,比如DispatcherServletAutoConfiguration,我们通过它继续往下找,看看它是否真的生效,以及为什么会生效。

kotlin 复制代码
@ConditionalOnClass({DispatcherServlet.class})
public class DispatcherServletAutoConfiguration {

这个注解表示,如果想要DispatcherServletAutoConfiguration生效,则必须有DispatcherServlet.class这个类的存在(我们的场景启动器包含这个jar包,它当然存在)。

到此处,算是破案了,Springboot通过将一个候选人名单导入进来,然后通过Condition注解来决定哪些东西生效,进行相关的装配(也可以说通过Condition将导入的jar包进行配置)


其实到目前为止,我们还有一个疑惑点,就是如果Springboot是自动装配的,如果我们没有写另外的配置文件,那么他也应该有自己的默认配置规则(这个规则会根据我们写的配置文件自动更改)。

这个东西应该跟这个DispatcherServletAutoConfiguration放在一起,没猜错的话他应该是一个配置文件类 ,使用@ConfigurationProperties注解跟配置文件关联在一起(但是有默认值,保证没有配置文件时也能够保证程序运行)。

找了一下,原来加在DispatcherServletAutoConfiguration的一个静态内部类上面,

less 复制代码
@Configuration(
    proxyBeanMethods = false
)
@Conditional({DispatcherServletRegistrationCondition.class})
@ConditionalOnClass({ServletRegistration.class})
@EnableConfigurationProperties({WebMvcProperties.class})
@Import({DispatcherServletConfiguration.class})
protected static class DispatcherServletRegistrationConfiguration {

我们打开WebMvcProperties.class,发现就是我们要找的默认配置,内部代码太多,就不完整展示了

kotlin 复制代码
@ConfigurationProperties(
    prefix = "spring.mvc"
)
public class WebMvcProperties {

这里有个前缀,也就是我们在修改默认配置时要加的前缀


配置文件与yaml

Springboot支持yaml类型的配置文件,yaml的优点在于

  1. 结构清晰
  2. 可配置多种环境的专项配置

yaml的注意点:

  1. yaml类型的配置文件使用.yaml或者.yml结尾。
  2. yaml大小写敏感
  3. yaml以kv键值对存储数据,k与v之间使用空格分隔
  4. 层级之间无区分,在同一竖线即可
  5. 禁止使用tab
  6. #作为注释

示例

typescript 复制代码
@Component
@ConfigurationProperties(prefix = "person") //和配置文件person前缀的所有配置进行绑定
@Data //自动生成JavaBean属性的getter/setter
//@NoArgsConstructor //自动生成无参构造器
//@AllArgsConstructor //自动生成全参构造器
public class Person {
    private String name;
    private Integer age;
    private Date birthDay;
    private Boolean like;
    private Child child; //嵌套对象
    private List<Dog> dogs; //数组(里面是对象)
    private Map<String,Cat> cats; //表示Map
}

@Data
public class Dog {
    private String name;
    private Integer age;
}

@Data
public class Child {
    private String name;
    private Integer age;
    private Date birthDay;
    private List<String> text; //数组
}

@Data
public class Cat {
    private String name;
    private Integer age;
}

Properties的写法:

ini 复制代码
person.name=张三
person.age=18
person.birthDay=2010/10/12 12:12:12
person.like=true
person.child.name=李四
person.child.age=12
person.child.birthDay=2018/10/12
person.child.text[0]=abc
person.child.text[1]=def
person.dogs[0].name=小黑
person.dogs[0].age=3
person.dogs[1].name=小白
person.dogs[1].age=2
person.cats.c1.name=小蓝
person.cats.c1.age=3
person.cats.c2.name=小灰
person.cats.c2.age=2

yaml的写法:

yaml 复制代码
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} #对象也可用{}表示

Springboot的日志框架

Springboot自带了logback作为日志的默认实现。 具体打开任意Starter,打开核心spring-boot-starter,会发现内部有以下依赖

xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-logging</artifactId>
  <version>3.1.5</version>
  <scope>compile</scope>
</dependency>

其内部是由logback实现的。


这里推荐大家使用log4j2作为日志框架。

spring非常人性化的为市面上常用的日志框架提供了相应的starter,当然也包括log4j2

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>3.1.4</version>
</dependency>

但现在有一个问题,我们需要清除掉Spring默认的日志框架才能够实现替换,否则会导致冲突。具体实现方法如下,我们使用exclude标签过滤掉核心starter中的日志框架,再使用就近原则贴上我们的依赖

xml 复制代码
<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>

配置文件的写法依然沿用我们之前的名称即可log4j2.xml,不过也可以使用log4j2-spring.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <loggers>
        <!--
            level指定日志级别,从低到高的优先级:
                TRACE < DEBUG < INFO < WARN < ERROR < FATAL
                trace:追踪,是最低的日志级别,相当于追踪程序的执行
                debug:调试,一般在开发中,都将其设置为最低的日志级别
                info:信息,输出重要的信息,使用较多
                warn:警告,输出警告的信息
                error:错误,输出错误信息
                fatal:严重错误
        -->
        <root level="INFO">
            <appender-ref ref="spring6log"/>
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="log"/>
        </root>
    </loggers>

    <appenders>
        <!--输出日志信息到控制台-->
        <console name="spring6log" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
        </console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
        <File name="log" fileName="d:/spring6_log/test.log" append="false">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
        </File>

        <!-- 这个会打印出所有的信息,
            每次大小超过size,
            则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
            作为存档-->
        <RollingFile name="RollingFile" fileName="d:/spring6_log/app.log"
                     filePattern="log/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <SizeBasedTriggeringPolicy size="50MB"/>
            <!-- DefaultRolloverStrategy属性如不设置,
            则默认为最多同一文件夹下7个文件,这里设置了20 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </appenders>
</configuration>
kotlin 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class helloController {

    Logger logger = LoggerFactory.getLogger(getClass());

    @RequestMapping("hi")
    public String hello(){


        logger.info("hahahahahahahahah~~~~");

        return "hello";
    }
}

web开发相关

web开发相关的自动装配

下面是Springboot自动装配中关于web开发相关的所有可选项

vbnet 复制代码
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

我们首先排除掉一些关于响应式编程的自动装配(带有reactive的) ,会剩下以下的自动装配

go 复制代码
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

我们简化一下,看起来更方便

go 复制代码
client.RestTemplateAutoConfiguration
为程序访问第三方http接口提供的自动装配,暂时用不到

embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
为所有类型web服务器提供的自动装配,用不到

servlet.DispatcherServletAutoConfiguration
DispatcherServlet相关的自动装配

servlet.ServletWebServerFactoryAutoConfiguration
为不同类型Servlet容器做的自动装配

servlet.error.ErrorMvcAutoConfiguration
异常处理相关的自动装配

servlet.HttpEncodingAutoConfiguration
字符转换相关的自动装配

servlet.MultipartAutoConfiguration
文件上传相关的自动装配

servlet.WebMvcAutoConfiguration
web服务全局的自动装配

因为我们用的是tomcat作为web服务器同时作为Servlet容器,不需要考虑其他情况,我们能用到的自动装配主要还是下面几个,

go 复制代码
servlet.DispatcherServletAutoConfiguration
DispatcherServlet相关的自动装配

servlet.error.ErrorMvcAutoConfiguration
异常处理相关的自动装配

servlet.HttpEncodingAutoConfiguration
字符转换相关的自动装配

servlet.MultipartAutoConfiguration
文件上传相关的自动装配

servlet.WebMvcAutoConfiguration
web服务全局的自动装配

我们尝试找一下上面会用到的自动装配的配置文件前缀,方便我们后续使用时在application配置文件中去调用更改他们 首先第一个DispatcherServletAutoConfiguration会用到的我们之前分析过

kotlin 复制代码
@ConfigurationProperties(
    prefix = "spring.mvc"
)
public class WebMvcProperties {

他的配置方法以spring.mvc开头


第二个ErrorMvcAutoConfiguration会用到

ini 复制代码
@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {

他的配置方法以server开头


第三个HttpEncodingAutoConfiguration比较有意思

less 复制代码
@AutoConfiguration
@EnableConfigurationProperties({ServerProperties.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
    prefix = "server.servlet.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {

注意@ConditionalOnProperty中的内容,它以server.servlet.encoding开头,但是它依旧使用的是ServerProperties.class作为配置文件


下面是文件上传相关的,它有新的配置文件以及前缀spring.servlet.multipart

ini 复制代码
@ConfigurationProperties(
    prefix = "spring.servlet.multipart",
    ignoreUnknownFields = false
)
public class MultipartProperties {

最后一个WebMvcAutoConfiguration作为全局的配置,它相当复杂,里面包含大量配置,用到了ServerProperties.class

kotlin 复制代码
@ConfigurationProperties("spring.web")
public class WebProperties {

他的配置方法以spring.web开头,


总结一下:

  1. DispatcherServletspring.mvc
  2. 异常处理server
  3. 字符编码server.servlet.encoding
  4. 文件上传spring.servlet.multipart
  5. 全局配置spring.web

Springboot中的mvc与SpringMvc的不同点

从一开始我们就知道,SpringBoot其实相较于一般的SpringMvc,会方便很多,但通过上面的分析,似乎SpringBoot只是帮我们装配了SpringMvc的普通功能,其实远远不止这些

回想一下,SpringMvc中想要使用JSON传参,需要专门引入对应到依赖 ,并使用@EableWebMvc注解。而在SpringBoot则不需要。

Springboot对于Web项目中配置的所有相关项

默认配置:

  1. 包含了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 组件,方便视图解析

  2. 默认的静态资源处理机制: 静态资源放在 static 文件夹下即可直接访问

  3. 自动注册Converter ,GenericConverter,Formatter 组件,适配常见数据类型转换格式化需求

  4. 支持 HttpMessageConverters ,可以方便返回 json等数据类型

  5. 注册 MessageCodesResolver,方便国际化及错误消息处理

  6. 支持 静态 index.html

  7. 自动使用ConfigurableWebBindingInitializer,实现消息处理、数据绑定、类型转化、数据校验等功能


实操上的不同(重点)

重要:

  • 如果想保持 boot mvc 的默认配置 ,并且自定义更多的 mvc 配置,如: interceptors , formatters , view controllers 等。可以使用 @Configuration 注解添加一个 WebMvcConfigurer 类型的配置类,并不要标注 @EnableWebMvc
  • 如果想保持 boot mvc 的默认配置,但要自定义核心组件实例,比如: RequestMappingHandlerMapping , RequestMappingHandlerAdapter , 或 ExceptionHandlerExceptionResolver ,给容器中放一个 WebMvcRegistrations 组件即可
  • 如果想全面接管 Spring MVC, @Configuration 标注一个配置类,并加上 @EnableWebMvc 注解,实现 WebMvcConfigurer 接口

其实归根结底就2种配置方式 ,区别在于是否使用@EableWebMvc注解

  1. 使用@Configuration注解标注配置类,并继承WebMvcConfiguration配合Springboot自动装配,自己写需要的配置

  2. 使用@Configuration注解标注配置类,并继承WebMvcConfiguration,而且标注@EableWebMvc禁用Springboot自动装配,全手动配置所有内容


深入理解web相关的自动装配

现在我们将进入源码分析,通过对WebMvcAutoConfiguration的深度解析,搞清楚Springboot对于web相关到底做了哪些配置,明白它装配的逻辑。

我们先打开源码看看基本结构

分析欢迎页的映射规则

我们先查看其中的一个匿名内部类

scala 复制代码
@Configuration(
    proxyBeanMethods = false
)
@EnableConfigurationProperties({WebProperties.class})
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {

我们可以看到它让名为WebProperties的类与配置文件进行了绑定 ,也就表明该匿名内部类里面用到的任何有关WebProperties相关属性都将与外部的Properties文件相关联

我们看一下这个匿名内部类中相对比较简单的一些方法,看看他们都做了什么, (这些方法是用来配置index.html的,也就是网站主页

typescript 复制代码
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    return (WelcomePageHandlerMapping)this.createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider, WelcomePageHandlerMapping::new);
}

@Bean
public WelcomePageNotAcceptableHandlerMapping welcomePageNotAcceptableHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    return (WelcomePageNotAcceptableHandlerMapping)this.createWelcomePageHandlerMapping(applicationContext, mvcConversionService, mvcResourceUrlProvider, WelcomePageNotAcceptableHandlerMapping::new);
}

private <T extends AbstractUrlHandlerMapping> T createWelcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider, WelcomePageHandlerMappingFactory<T> factory) {
    TemplateAvailabilityProviders templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    T handlerMapping = factory.create(templateAvailabilityProviders, applicationContext, this.getIndexHtmlResource(), staticPathPattern);
    handlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    handlerMapping.setCorsConfigurations(this.getCorsConfigurations());
    return handlerMapping;
}

其中第一个方法和第二个方法都放入了IOC容器中,而且2个方法都调用了第三个方法。

  1. 方法1属于正常情况的处理方法,调用方法3
  2. 方法2属于异常情况的处理方法,也调用了方法3
  3. 方法3属于关键方法

我们并不想过于具体的了解他们是如何配置这些主页以及过滤器的杂项配置,我们只想了解这些方法到底选择去什么地方找这个index.htm静态文件。因为这个东西才是我们需要关心而且实操中可能需要自己去配置的东西,从这种角度来看,下面这句代码就十分可疑了

ini 复制代码
String staticPathPattern = this.mvcProperties.getStaticPathPattern();

我们点开它,果然,该方法存在的类是与外部Properties文件关联的类。

kotlin 复制代码
@ConfigurationProperties(
    prefix = "spring.mvc"
)
public class WebMvcProperties {

上面的注解告诉了我们,使用前缀spring.mvc可以配置它。

回到我们的方法本身

typescript 复制代码
public String getStaticPathPattern() {
    return this.staticPathPattern;
}

去找这个this.staticPathPattern

ini 复制代码
private String staticPathPattern = "/**";

破案了,这就是index.html所应该放的位置,在资源文件夹下的任意多层路径 ,同时也说明,如果我们改变staticPathPattern,也就会改变扫描index.html的位置。


静态资源的配置分析

找到下面这个匿名内部类

less 复制代码
@Configuration(
    proxyBeanMethods = false
)
@Import({EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

自动装配适配器,他实现了一个非常重要的接口WebMvcConfigurer,这个类在SpringMvc中 是一个至关重要的类,它几乎包含了SpringMvc所有重要配置项的默认配置,而且自己默认实现了他们。 ,也就是说我们如果想要改变Mvc的配置,如全局异常处理,拦截器,HandlerMapping,包括页面解析器 ,都可以通过重写该接口的默认方法来完成修改。

言尽于此,如果大家忘了这东西是什么,可以参考我写的SpringMvc全篇学习笔记复习,SpringMvc快速学习笔记下篇 - 掘金 (juejin.cn)

总之看到这个接口就说明Springboot一定是帮我们写了一些Mvc的配置。,我们找一找有关静态资源处理的,拿出来分析一下

kotlin 复制代码
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                registration.addResourceLocations(new Resource[]{resource});
            }

        });
    }
}

private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
    this.addResourceHandler(registry, pattern, (registration) -> {
        registration.addResourceLocations(locations);
    });
}

private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
    if (!registry.hasMappingForPattern(pattern)) {
        ResourceHandlerRegistration registration = registry.addResourceHandler(new String[]{pattern});
        customizer.accept(registration);
        registration.setCachePeriod(this.getSeconds(this.resourceProperties.getCache().getPeriod()));
        registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
        registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
        this.customizeResourceHandlerRegistration(registration);
    }
}

先看第一部分代码

kotlin 复制代码
if (!this.resourceProperties.isAddMappings()) {
    logger.debug("Default resource handling disabled");
} 

如果没有开启相关资源的映射,直接打印资源禁止访问。大家可以打开看一下这个isAddMapping,这东西默认是true,也就是说,默认是允许访问静态资源的。

typescript 复制代码
public boolean isAddMappings() {
    return this.addMappings;
}

去找addMappings

ini 复制代码
private boolean addMappings;
private boolean customized;
private final Chain chain;
private final Cache cache;

public Resources() {
    this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
    this.addMappings = true;
    this.customized = false;
    this.chain = new Chain();
    this.cache = new Cache();
}

一个Boolean值,默认是true。看一下这个类

kotlin 复制代码
@ConfigurationProperties("spring.web")
public class WebProperties {

依旧是外部配置文件绑定,由此可知,spring.web.addMappings能调整是否让外部访问静态文件。


继续往下看代码

kotlin 复制代码
else {
    this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");

这个方法很关键,传了一个注册器,一个请求路径getWebjarsPathPattern()大家自己找,

ini 复制代码
private String webjarsPathPattern = "/webjars/**";

还传了一个字符串classpath:/META-INF/resources/webjars/点一下addResourceHandler方法

还是调用了另一个方法,

scss 复制代码
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
    if (!registry.hasMappingForPattern(pattern)) {
        ResourceHandlerRegistration registration = registry.addResourceHandler(new String[]{pattern});
        customizer.accept(registration);
        registration.setCachePeriod(this.getSeconds(this.resourceProperties.getCache().getPeriod()));
        registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
        registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
        this.customizeResourceHandlerRegistration(registration);
    }
}

这个方法里面的if表示,如果没有对该映射的处理,则执行下面的内容。 再往下看为指定的请求路径创建了一个映射 (也就是上面传的/webjars/**),执行了消费者方法,接着就是加一些缓存,最后把配好的映射信息放进customizeResourceHandlerRegistration配置成HandlerMapping让外部访问。

好的,我们1不关心乱七八糟的缓存是怎么添加的,2不关心它怎么把配好的映射信息放进customizeResourceHandlerRegistration配置成HandlerMapping。

我们只关心它怎么配好了/webjars/**的映射,也就是说,上面的代码中只有消费者方法的执行可能完成了最重要的工作

ini 复制代码
customizer.accept(registration);

这个东西是需要自己实现的,会到我们的调用方法。

typescript 复制代码
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
    this.addResourceHandler(registry, pattern, (registration) -> {
        registration.addResourceLocations(locations);
    });
}

这句代码很重要registration.addResourceLocations(locations);,添加了资源地址locations,在回到最初的方法,

kotlin 复制代码
this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");

字符串:classpath:/META-INF/resources/webjars/,破案了,所谓/webjars/**请求,是在/META-INF/resources/webjars/文件夹下存放的!

也就是说,任何前缀为webjars的请求,都会去类路径下的/META-INF/resources/webjars/文件夹里找。

我们再看下面这句代码

kotlin 复制代码
this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
    if (this.servletContext != null) {
        ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
        registration.addResourceLocations(new Resource[]{resource});
    }

});

同样套路,找一下this.mvcProperties.getStaticPathPattern()

ini 复制代码
private String staticPathPattern = "/**";

这表示任何静态请求,接着看资源在哪

kotlin 复制代码
registration.addResourceLocations(this.resourceProperties.getStaticLocations());

去找这个getStaticLocations()

arduino 复制代码
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

破案了,这表示在这些文件夹下的静态资源,都可以被直接访问!


配置类形式装配Mvc

其实还是SpringMvc那一套,但是区别在于@EnableWebMvc注解要慎重添加。

java 复制代码
package com.atguigu.pro02.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.concurrent.TimeUnit;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new GlobeInterceptors())
                .addPathPatterns("/user/**")
                .excludePathPatterns("/user/ex/**");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        WebMvcConfigurer.super.addResourceHandlers(registry);
        registry.addResourceHandler("/images/**")
                .addResourceLocations("classpath:/images/")
                .setCacheControl(CacheControl.maxAge(3600, TimeUnit.MINUTES));
    }
}

直接放入IOC形式配置MVC

typescript 复制代码
@Configuration
public class WebMvcConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new GlobeInterceptors())
                        .addPathPatterns("/user/**")
                        .excludePathPatterns("/user/ex/**");
            }

            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/images/**")
                        .addResourceLocations("classpath:/images/")
                        .setCacheControl(CacheControl.maxAge(3600, TimeUnit.MINUTES));
            }
        };

    }


}

杂项之新的PathPatternParser路径匹配规则

在Springboot3中,它默认 使用了一个新的路径匹配规则,它兼容了旧的路径匹配规则,但是有一点不同,它不允许在路径中间添加**,比如/user/**/login,也就是说:它不支持这种形式的任意层级 。这就有可能给我们的RequestMapping造成问题,但是Springboot支持更换回老版配置

scss 复制代码
public void configurePathMatch(PathMatchConfigurer configurer) {
    if (this.mvcProperties.getPathmatch().getMatchingStrategy() == MatchingStrategy.ANT_PATH_MATCHER) {
        configurer.setPathMatcher(new AntPathMatcher());
        this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
            String servletUrlMapping = dispatcherPath.getServletUrlMapping();
            if (servletUrlMapping.equals("/") && this.singleDispatcherServlet()) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                urlPathHelper.setAlwaysUseFullPath(true);
                configurer.setUrlPathHelper(urlPathHelper);
            }

        });
    }

}

这里可以看到,我们可以将它切换为旧版的ANT_PATH_MATCHER,我们点开这个常量所在的类

kotlin 复制代码
@ConfigurationProperties(
    prefix = "spring.mvc"
)
public class WebMvcProperties {

破案,外部配置文件里找一下

spring.mvc.pathmatch.matching-strategy

旧版
ant_path_matcher

新版为默认,不用调

1. Ant风格路径用法

Ant 风格的路径模式语法具有以下规则:

  • *:表示任意数量的字符。
  • ?:表示任意一个字符
  • **:表示任意数量的目录
  • {}:表示一个命名的模式占位符
  • []:表示字符集合,例如[a-z]表示小写字母。

例如:

  • *.html 匹配任意名称,扩展名为.html的文件。
  • /folder1/*/*.java 匹配在folder1目录下的任意两级目录下的.java文件。
  • /folder2/**/*.jsp 匹配在folder2目录下任意目录深度的.jsp文件。
  • /{type}/{id}.html 匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。

注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:

  • 要匹配文件路径中的星号,则需要转义为\\*
  • 要匹配文件路径中的问号,则需要转义为\\?

相关推荐
java_heartLake7 分钟前
微服务中间件之Nacos
后端·中间件·nacos·架构
GoFly开发者1 小时前
GoFly快速开发框架/Go语言封装的图像相似性比较插件使用说明
开发语言·后端·golang
苹果酱05671 小时前
通过springcloud gateway优雅的进行springcloud oauth2认证和权限控制
java·开发语言·spring boot·后端·中间件
豌豆花下猫2 小时前
Python 潮流周刊#70:微软 Excel 中的 Python 正式发布!(摘要)
后端·python·ai
芯冰乐3 小时前
综合时如何计算net delay?
后端·fpga开发
用户673559885615 小时前
数据驱动,实时监控显威力 —— 淘宝商品详情API助力商家精准营销
后端·api·fastapi
lucifer3115 小时前
线程池与最佳实践
java·后端
用户673559885615 小时前
天猫店铺商品列表API返回值中的商品视频与图文详情
前端·javascript·后端
程序员大金6 小时前
基于SSM+Vue+MySQL的酒店管理系统
前端·vue.js·后端·mysql·spring·tomcat·mybatis
程序员大金6 小时前
基于SpringBoot的旅游管理系统
java·vue.js·spring boot·后端·mysql·spring·旅游