SpringBoot

本文深入探讨了SpringBoot的自动配置原理,包括依赖管理、自动配置、容器功能和开发小技巧。SpringBoot通过依赖管理和场景启动器简化了项目配置,自动配置功能涵盖了SpringMVC、Web开发等多个方面。此外,文章还介绍了配置文件类型、配置提示以及Web开发中的静态资源处理和请求映射。通过对自动配置的深入了解,开发者可以更高效地利用SpringBoot进行开发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、了解自动配置原理

1、SpringBoot的特点

1.1、依赖管理

  在传统Maven项目开发中,我们要实现什么功能就要导入相应的jar包。在我们的开发场景中可能要导入很多的jar包,引入的外部jar包间可能相互依赖,我们还要去查看不同的jar包版本是否存在冲突。SpringBoot使用父项目做依赖管理,在使用Spring Initializr创建一个新项目时,在pom文件中都会引入一个父依赖。在父依赖中定义了开发过程中几乎所有场景所需要jar包的版本(这些版本是相互兼容的)。这样在我们开发过程中,就不会因jar包间版本冲突给我们造成困扰。下面我们来看看SpringBoot是怎么做的吧。

使用父依赖进行依赖管理
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.0</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

spring-boot-starter-parent也引入了父依赖
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.7.0</version>
</parent>
这里面几乎声明了开发中常用jar包的版本号,自动版本仲裁机制
spring-boot-dependencies中的<dependencyManagement>标签几乎包含了我们开发所需要的所有依赖。<properties>标签定义了依赖的版本

dependencyManagement粗了解
  在Maven中的作用其实相当于一个对所依赖jar包进行版本管理的管理器。在==dependencyManagement下申明的dependencies,Maven并不会去实际下载所依赖的jar,而是在dependencyManagement中用一个Map记录了jar的三维坐标。==而被dependencies包裹的元素,Maven是会去仓库实际下载所需要的jar包的,而至于需要下载什么版本的jar包就有两种判断途径:
  1、如果dependencies里的dependency自己没有声明version元素,那么maven就会到里面去找有没有对该artifactId和groupId进行过版本声明,如果有,就继承它,如果没有就会报错,告诉你必须为dependency声明一个version
  2、如果dependencies中的dependency声明了version,那么无论dependencyManagement中有无对该jar的version声明,都以dependency里的version为准。
  所以在我们使用SpringBoot开发的时候,我们在引入外部依赖。没有定义版本,他会去里面去找有没有对该artifactId和groupId进行过版本声明,有的话就去继承它(版本)。
勿需关注版本号

1、引入依赖默认都可以不写版本(可以减少jar包冲突带来的麻烦)
2、引入非版本仲裁的jar,要写版本号。

版本号可以修改 (不推荐)
   SpringBoot的将所有依赖的版本定义在父项目的标签中,但是在MAVEN的版本仲裁机制会优先选择子项目下的版本,所以我们只需要在子项目(自己创建的项目)下的pom文件中定义所需要的版本即可。

版本一般定义在下面(spring-boot-dependencies)这个jar包中
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.7.0</version>
</parent>
以lombak 为例:
在spring-boot-dependencies的pom文件中,它的版本的定义为:
<dependencyManagement>
   <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok.version}</version>
    </dependency>
<dependencyManagement>
lombok.version定义在
<properties>
     <lombok.version>1.18.24</lombok.version>
</properties>
MAVEN的版本仲裁机制会优先选择子项目下的版本
所以我们只需要在子项目(自己创建的项目)下的pom文件中定义所需要的版本即可
<properties>
     <lombok.version>1.18.20</lombok.version>
</properties>

  另外SpringBoot也不像Maven一样以功能作为依赖进行导入(想要什么实现功能导入什么依赖),它是以开发的场景作为依赖进行导入,一个开发场景肯定需要实现很多功能,好处就是我们只要引入场景启动器,SpringBoot就会自动帮我们导各个功能所需要的依赖,就不用我们一个一个去导入了。
开发导入starter场景启动器

1、见到很多 spring-boot-starter-* : *就某种场景
2、只要引入starter,这个场景的所有常规需要的依赖我们都自动引入
3、SpringBoot所有支持的场景
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
4、见到的  *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器。
5、所有场景启动器最底层的依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>

总结
  Springboot通过父依赖的来做版本仲裁,通过各种场景启动器来加载开发所需要的jar包。

1.2、自动配置

  要想实现自动配置,需要完成两大难题,一是导入依赖、二是配置组件。导入依赖我们在前面已经了解过了,我们在导入场景启动器时,SpringBoot就会自动帮我们导入所需要的依赖。
  首先我们来看看SpringBoot帮我们做了那些自动配置(先了解帮我们做了什么,再了解怎么做到的)
SpringBoot先帮我们配置好了SpringMVC
  1、SpringBoot先帮我们导入了SpringMVC的依赖
  2、自动配置好了SpringMVC的常用组件(功能)
自动配置好web的常见功能
  Springboot帮我们配置好了所有web开发场景。 比如字符编码问题(characterEncodingFilter),文件上传(multipartResolver)
SpringBoot帮我们配置好了默认的包扫描路径
  SpringBoot存在默认的包扫描路径,它将主程序类(被@SpringBootApplication修饰的类)所在的包视为默认的包扫描路径,在程序加载时,会将扫描包及其子包下的组件加入到容器中。下面是Spring官网对默认的包扫描路径的一些解释。
官网默认包扫描规则
  官网上面明确表示,如果程序没有声明包扫描路径,会默认将主程序所在的包当作包扫描路径,但是不推荐使用默认的包扫描路径。我们可以通过@SpringBootApplication注解的scanBasePackages属性来指定包扫描路径。或者@ComponentScan 指定扫描路径,不指定的话默认从注解修饰类所在的包开始扫描。
  上面SprinBoot帮我们做的自动配置,它是怎么做到的呢?回顾到我们的SSM时代,我们配置组件一般是将组件的配置信息(比如数据源的一些信息啊)放在配置文件(.properties)上,然后在我们在创建组件时,引入配置文件,取出配置文件的值放到组件中。这样我们就为组件赋上了值。
SpringBoot的配置
各种配置都有默认值

  • 默认的配置都会映射到配置类中:比如RedisProperties。
  • 配置文件的值最终会绑定到对应配置类的属性中。
  • 配置类会创建对象并被加载到容器中去。

按需加载所有自动配置项

  • 引入 非常多的starte(场景启动器)
  • 引入了哪些场景(starte)这个场景的自动配置才会开启、创建并加载到容器中
  • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure包里面

2、容器功能

2.1、向容器添加组件

1、@Configuration+@Bean
  @Configuration注解放在类上,表示这是一个配置类。它是一个复合注解,我们点进去会发现它发现它包含了@Component注解,说明它也会在程序启动时,创建出对象,将它作为组件加载到容器中去。

  • 基本使用:(配置类必须放包扫描路径下(它本身也是一个组件,会在程序加载时,放到容器中起),否则无法生效)
/**
*配置类使用@Bean给容器注入的组件,默认是单实例的(无论从容器中取多少次,取的都是同一个对象)的。
*
*/
@Configuration//告诉SpringBoot,这是一个配置类(配置类也会被加到容器中去)=====相当于SSM框架时代的配置文件
public class MyConfig {
    @Bean//给容器添加组件,返回类型是组件类型,方法名是组件名,返回值就是组件在容器中的实例
    public DogPojo MyDog() {
        return new DogPojo("花花", 3, "公");
    }
}
  • Full模式与Lite模式:@Configuration有一个属性proxyBeanMethods,默认值是true
proxyBeanMethods:代理bean的方法
-      Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
-      Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
-      组件依赖必须使用Full模式默认。其他默认是否Lite模式

区别:
  Full模式下, @Configuration注解下的类加入到容器中就不再是它本生这个类,而是经过代理之后的代理类。此时从代理类中通过配置类对象.方法()的形式获取对象,它会优先从容器获取。
区别:
Full模式下, @Configuration下的类加入到容器中就不再是它本生这个类,而是经过代理之后的代理类。此时从代理类中通过配置类对象.方法()的形式获取对象,它会优先从容器获取。代理类
Lite模式下,@Configuration下的类加入到容器中就是它原本的这个类从代理类中通过配置类对象.方法()的形式获取对象,就是普通的创建对象方式。获取到的对象和容器中的对象(组件)半毛钱关系都没有。
常用场景:在代理类中,向容器添加的组件之间没有相互依赖,那么采用Lite模式会加快程序加载的速度。但是配置类中,向容器添加的组件存在依赖关系,那么就采用Full模式确保组件之间的依赖。
2、包扫描(指定包扫描路径)+标识注解(@Component、@Controller、@Service、@Repository)
  这种是我们最常用的将组件加到Spring容器的方式,相信大家很熟悉了。SpringBoot存在默认的包扫描机制,所以我们只需要将需要加到容器中的类放到包扫描的路径下,并在这些类下面添加这些标识注解即可。
3、@Import
@Import的三种用法
  进入到@Import注解中,我们会发现文档中写了@Import可以导入@Configuration(配置类)、ImportSelector、ImportBeanDefinitionRegistrar和常规的组件。用法详见下面。
@Import

  • 通过class导入,@Import(要导入容器的组件),容器会自动注册这个组件,id默认是全类名:
   @Import({DogPojo.class})//只要在这里写上类名,都会被加载到容器中
   @Configuration(proxyBeanMethods = true)//告诉SpringBoot,这是一个配置类=====相当于SSM框架时代的配置文件
   public class MyConfig {
    }
 给容器中自动创建出这两个类型(通过无参构造)的组件、默认组件的名字就是全类名
 这样,就会向容器中注入DogPojo组件。当然啦,被Import修饰的类也要能作为组件扫描到Spring容器中去。
  • ImportSelector:通过全类名导入,ImportSelector的selectImports方法返回所有需要导入到容器的全类名数组
自定义一个类实现ImportSelector接口
public class MyImportSelector implements ImportSelector {
   //AnnotationMetadata包含了所有@Import("MyImportSelector.class")修饰的类的所有信息
   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
       return new String[]{"com.tellhow.pojo.People"};//这里的全类名会加到组件中
   }

   @Override
   public Predicate<String> getExclusionFilter() {
       return ImportSelector.super.getExclusionFilter();
   }
}
  • ImportBeanDefinitionRegistrar的registerBeanDefinitions方法直接注册到容器中。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  /**
    *
    * @param importingClassMetadata 当前类的所有信息
    * @param registry
    */
   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean myDog = registry.containsBeanDefinition("myDog");
       if (myDog){
          //"book":组件名,后面的对象是组件的定义信息
           registry.registerBeanDefinition("book", new RootBeanDefinition(BookPojo.class) );
       }
   }
}
4、原生配置文件引入

@ImportResource
原生的bean.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
  <bean name="dog02" class="com.tellhow.pojo.DogPojo">
      <property name="age" value="12"></property>
      <property name="name" value="花花"></property>
      <property name="toll" ref="mytool02"></property>
  </bean>
  <bean name="mytool02" class="com.tellhow.pojo.ToolPojo">
      <property name="toolName" value="铲子"></property>
      <property name="toolNo" value="123456789"></property>
  </bean>
</beans>

  @ImportResource注解用于导入Spring的配置文件,让配置文件(xml)里面的内容生效;(就是以前写的springmvc.xml、applicationContext.xml)
  Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件,它也不能自动识别;
  想让Spring的配置文件(xml)生效,加载到容器;@ImportResource标注在一个配置类上,将配置文件的路径引进来。

@ImportResource(locations="classpath:bean.xml")
@Configuration
public class MyConfig02 {
}
经过上面这两步,bean.xml下的组件就会被加载到容器中。

2.2 条件装配@Conditional

条件装配:在满足Conditional指定的条件,才会将组件加到容器中,下面的Conditional接口的一些实现类。
在这里插入图片描述
条件装配测试实例
  这里的 @ConditionalOnBean其实与加载的顺序也有关系,就像下面这个,ToolPojo放DogPojo的前面,DogPojo的条件装配条件会通过,因为在加载DogPojo时,容器中已经存在ToolPojo类型的组件了。如果他们位置发生变化,DogPojo的条件装配条件不会通过了。

@Configuration(proxyBeanMethods = true)//告诉SpringBoot,这是一个配置类=====相当于SSM框架时代的配置文件
public class MyConfig {
   @Bean
    public ToolPojo myTool() {
        return new ToolPojo("锤子", 1008612L);
    }

    @Bean//给容器添加组件,返回类型是组件类型,方法名默认首子母小写是组件名,返回值就是组件在容器中的实例
    @ConditionalOnBean(value = {ToolPojo.class})//容器中存在ToolPojo类型的组件,myDog才会注册到容器中。
    public DogPojo myDog() {
        DogPojo dogPojo = new DogPojo();
        return dogPojo;
    }
}

2.3配置绑定

  在SSM时代,想将.properties文件与javaBean绑定。需要先用IO流将配置文件读取到内存中,然后在解析读取到的配置文件内容绑定到JavaBean上

public static void main(String[] args) {
    SpringApplication.run(JkczpApplication.class, args);
    //printBeanmame(applicationContext);
    Properties properties = new Properties();
    FileInputStream fileInputStream = null;
    URL url = JkczpApplication.class.getClassLoader().getResource("config.properties");
    try {
        fileInputStream = new FileInputStream(url.getFile());
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");
        properties.load(inputStreamReader);
        Enumeration enumeration = properties.propertyNames();
        while (enumeration.hasMoreElements()){
            String key = (String)enumeration.nextElement();
            System.out.println("key为"+key+",值为"+properties.getProperty(key));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

  SpringBoot使用@ConfigurationProperties+@Component将配置文件与JavaBean绑定。默认绑定的是application.properties和application.yml配置文件。(需要被组件扫描器,扫描到组件中哦)
配置绑定
3、@EnableConfigurationProperties +@ConfigurationProperties

@Configuration(proxyBeanMethods = true)//告诉SpringBoot,这是一个配置类=====相当于SSM框架时代的配置文件
//1、开启CatProperties配置绑定功能
//2、把这个CatProperties这个组件自动注册到容器中
@EnableConfigurationProperties(value = CatProperties.class)//与Configuration一起,不然不会生效
public class MyConfig {


}

@ConfigurationProperties("mycat")//配置绑定,但是这个没加入到容器中不会生效。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CatProperties {
    private String catName;
    private String catSex;
    private Integer catAge;
}

3、自动配置原理入门

3.1、引导加载自动配置类

  被@SpringBootApplication标注的类是主启动类。@SpringBootApplication是一个复合注解,它由以下注解组成。

@Target(ElementType.TYPE)//元注解
@Retention(RetentionPolicy.RUNTIME)//元注解
@Documented//元注解
@Inherited//元注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

1、@SpringBootConfiguration。它也是一个复合注解(元注解我先去掉了)

@Configuration
@Indexed
public @interface SpringBootConfiguration {
    
}
@Configuration,被这个注解标注的类为配置类,所以主程序类也是一个配置类。
@Indexed:在项目中使用了@Indexed之后,编译打包的时候会在项目中自动生成META-INT/spring.components文件。当Spring应用上下文执行
ComponentScan扫描时,META-INT/spring.components将会被CandidateComponentsIndexLoader 读取并加载,转换为CandidateComponentsIndex对象,这样的话
@ComponentScan不在扫描指定的package,而是读取CandidateComponentsIndex对象,从而达到提升性能的目的。

2、@EnableAutoConfiguration。它也是一个复合注解,也是最重要的注解。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
  • 其中@AutoConfigurationPackage也是一个复合注解:主要功能就是将AutoConfigurationPackages组件注册到容器中
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
     //前面讲过import到组件的三种方式,这个是属于ImportBeanDefinitionRegistrar导入方式(直接注册bean的定义信息),因为Registrar 是内部类
	/**
	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
	 * configuration.
	 */
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}
		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}
	}

  上面做的事,就是向容器注入一个组件,组件名为org.springframework.boot.autoconfigure.AutoConfigurationPackages,组件为new BasePackagesBeanDefinition(packageNames));//packageNames是主程序所在包信息。
  关于自动配置包,注解给出的解释是Registers packages with AutoConfigurationPackages. When no base packages or base package classes are specified, the package of the annotated class is registered。(使用 AutoConfigurationPackages 注册包。当没有指定基包或基包类时,注解类的包被注册。)
总结:@Import(AutoConfigurationPackages.Registrar.class)就是向容器中注册了一个,有关主程序类所在包信息的组件。

  • @Import(AutoConfigurationImportSelector.class)这个是SpringBoot自动配置的核心
      这不用说了,又是在给容器导入组件。还是以全类名的方式导入的。让我们点进去瞧瞧吧。
/**
 *AnnotationMetadata annotationMetadata 注解的元信息
 * return 所有加入到容器中的组件全类名信息
 **/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   //获取自动配置实体
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   //从 AnnotationMetadata 返回适当的 AnnotationAttributes。默认情况下,此方法将返回 getAnnotationClass() 的属性。
   //获取属性信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //获取候选的配置
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   //删除重复项
   configurations = removeDuplicates(configurations);
   //获取(排除)不需要加到容器的组件
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   //检查排除类
   checkExcludedClasses(configurations, exclusions);
   //去掉不需要加到容器的组件
   configurations.removeAll(exclusions);
   configurations = getConfigurationClassFilter().filter(configurations);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}
  //获取候选的配置
  /**
  *AnnotationMetadata metadata:   注解所在类的元信息
  *AnnotationAttributes attributes 获取注解的attributes属性
  */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  //SpringFactoriesLoader在程序加载时会从META-INF/spring.factories路径加载组件到缓存中
    List<String> configurations = new ArrayList<>(

         SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
  //加载自动配置组件,从路径META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports读取
   ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
   Assert.notEmpty(configurations,
         "No auto configuration classes found in META-INF/spring.factories nor 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;
}

  查看源码我们会发现,他会先去加载"META-INF/spring.factories"文件,在实例化该文件下的类以后会把它放到集合中。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
		//FACTORIES_RESOURCE_LOCATION="META-INF/spring.factories"
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalzyer,\
org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

# DataSource initializer detectors
org.springframework.boot.sql.init.dependency.DatabaseInitializerDetector=\
org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializerDatabaseInitializerDetector

# Depends on database initialization detectors
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
org.springframework.boot.autoconfigure.batch.JobRepositoryDependsOnDatabaseInitializationDetector,\
org.springframework.boot.autoconfigure.quartz.SchedulerDependsOnDatabaseInitializationDetector,\
org.springframework.boot.autoconfigure.session.JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector

  1、在这个阶段,他会将这9个组件加到集合中。有些细心的同学会发现,此时集合中不止9个组件。这是因为这里的"META-INF/spring.factories"文件仅仅只是spring-boot-autoconfigure-xxx.jar下的,其他的包下的"META-INF/spring.factories"也会被扫描到。然后看一下,刚刚加载的集合中是不是存在org.springframework.boot.autoconfigure.EnableAutoConfiguration,存在的话返回它,不存在的话返回一个空集合(这里就是看一下META-INF/spring.factories下有没有自动配置类相关的信息)。
   2、然后用一个新的(List)集合去接返回的结果(这个集合保存的是自动配置类的信息)。所以此时集合要么是空的,要么存在一些"META-INF/spring.factories" 下以org.springframework.boot.autoconfigure.EnableAutoConfiguration为key的集合。看名字也知道是自动配置类的集合。所以此时集合中已经包含了"META-INF/spring.factories"下自动配置类的组件(前提是文件存在自动配置类的相关信息)。
   3、接下来SpringBoot就会去读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件并实例化对象到集合中。

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
		Assert.notNull(annotation, "'annotation' must not be null");
		ClassLoader classLoaderToUse = decideClassloader(classLoader);
// LOCATION = "META-INF/spring/%s.imports"
//location=META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
		String location = String.format(LOCATION, annotation.getName());
		Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
		List<String> autoConfigurations = new ArrayList<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			autoConfigurations.addAll(readAutoConfigurations(url));
		}
		return new ImportCandidates(autoConfigurations);
	}

   META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件内容

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveDataAutoConfiguration
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration
org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration
org.springframework.boot.autoconfigure.graphql.data.GraphQlReactiveQueryByExampleAutoConfiguration
org.springframework.boot.autoconfigure.graphql.data.GraphQlReactiveQuerydslAutoConfiguration
org.springframework.boot.autoconfigure.graphql.data.GraphQlQueryByExampleAutoConfiguration
org.springframework.boot.autoconfigure.graphql.data.GraphQlQuerydslAutoConfiguration
org.springframework.boot.autoconfigure.graphql.reactive.GraphQlWebFluxAutoConfiguration
org.springframework.boot.autoconfigure.graphql.rsocket.GraphQlRSocketAutoConfiguration
org.springframework.boot.autoconfigure.graphql.rsocket.RSocketGraphQlClientAutoConfiguration
org.springframework.boot.autoconfigure.graphql.security.GraphQlWebFluxSecurityAutoConfiguration
org.springframework.boot.autoconfigure.graphql.security.GraphQlWebMvcSecurityAutoConfiguration
org.springframework.boot.autoconfigure.graphql.servlet.GraphQlWebMvcAutoConfiguration
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration
org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration
org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration
org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration
org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration
org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration
org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration
org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration
org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
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
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

3.2、按需开启自动配置项

  虽然我们上面144个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
但是它还要按照条件装配规则(@Conditional),最终按需配置。

     //这里是获取到自动配置项,就是上面的那些
	   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	  //去除重复项
		configurations = removeDuplicates(configurations);
	  //获取到不需要加载的配置项
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		//移除不需要加载的配置项
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);

3.3、修改默认配置(以视图解析器为例)

  从下面的代码我们可以看出,视图解析器的属性是从mvcProperties中取的,而MvcProperties与配置文件又是绑定的,所以我们只需要在配置文件修改MvcProperties的配置,就可以成功改变默认视图解析器的属性。

  @Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })//该注解功能1、开启配置绑定功能
                                                                                  //        2、把这个指定组件自动注册到容器中
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
	    private final Resources resourceProperties;
       //由于WebMvcProperties 开启了配置绑定功能,它可与通过修改配置文件,修改对应的属性值
		private final WebMvcProperties mvcProperties;
		private final ListableBeanFactory beanFactory;
//有参构造器的参数都是从容器中确定的
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.resourceProperties = webProperties.getResources();
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
			this.mvcProperties.checkConfiguration();
		}
		@Bean
		@ConditionalOnMissingBean
		public InternalResourceViewResolver defaultViewResolver() {
			InternalResourceViewResolver resolver = new InternalResourceViewResolver();
			//在配置试图解析器时,它的属性由mvcProperties中来,mvcProperties又和配置文件绑定了,所以我们只要修改配置文件的值,就可以修改试图解析器的属性。
			resolver.setPrefix(this.mvcProperties.getView().getPrefix());
			resolver.setSuffix(this.mvcProperties.getView().getSuffix());
			return resolver;
		}
	}
  1. SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  2. 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  3. 生效的配置类就会给容器中装配很多组件
  4. 只要容器中有这些组件,相当于这些功能就有了
  5. 定制化配置
      1) 用户直接自己@Bean替换底层的组件
      2) 用户去看这个组件是获取的配置文件什么值就去修改
    流程:xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties

3.4、 定制化配置(以视图解析器为例)

  以文件上传解析器为例,容器中没有MultipartResolver类型的组件时,SpringBoot才会向容器中加入默认的文件上传解析器组件。我们不想用SpringBoot配的文件上传解析器,只需要自己定义一个新的文件上传解析器,然后丢容器中就行了。

    @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)//DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME=multipartResolver
	@ConditionalOnMissingBean(MultipartResolver.class)
	public StandardServletMultipartResolver multipartResolver() {
		StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
		multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
		return multipartResolver;
	}

3.4、最佳实践

  1. 引入场景依赖
    https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
  2. 查看自动配置了哪些(选做)
      1) 自己分析,引入场景对应的自动配置一般都生效了
      2) 配置文件中debug=true开启自动配置报告。Negative(不生效) Positive(生效)
  3. 是否需要修改
      1) 参照文档修改配置项
    https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
      2) 自己分析。xxxxProperties绑定了配置文件的哪些。
  4. 自定义加入或者替换组件
       @Bean、@Component
  5. 自定义器
       XXXXXCustomizer

4、开发小技巧

4.1、Lombok

简化JavaBean开发
导入lombak依赖
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        lombak版本,由于SpringBoot的依赖管理特性,可以不指定。 
        
        idea中搜索安装lombok插件

Lombok作用:用注解代替get、set、toString方法。让代码看起来简洁不少。

===============================简化JavaBean开发===================================
@NoArgsConstructor
//@AllArgsConstructor
@Data
@ToString
@EqualsAndHashCode
public class User {

    private String name;
    private Integer age;

    private Pet pet;

    public User(String name,Integer age){
        this.name = name;
        this.age = age;
    }


}



================================简化日志开发===================================
@Slf4j
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String handle01(@RequestParam("name") String name){
        
        log.info("请求进来了....");
        
        return "Hello, Spring Boot 2!"+"你好:"+name;
    }
}

4.2 dev-tools 检测项目发生变动能快速自动重启(非热部署)、ctrl+f9手动触发快速重启

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

二、配置文件

1、文件类型

1.1、properties(key=value)

server.port=8087
mycar.name=byd
mycar.price=170000
mycat.cat-age=12
mycat.cat-name=花花
mycat.cat-sex=公
#开启自动配置报告Negative(不生效)、Positive(生效)
debug=true

1.2、YAML

1.2.1、简介

  YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。
  非常适合用来做以数据为中心的配置文件

1.2.2、基本语法
  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,’ '与" "表示字符串内容中的特殊字符会被转义/不转义
       ’ ':单引号则会对字符串里面的特殊字符进行转义,也就是对转义字符进行转义,使其失去转义功能,始终是一个普通的字符串。
      " ":双引号不会对字符串里面的特殊字符进行转义,也就是转义字符该是什么就是什么。
1.2.3、数据类型(写法)
  1. 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v     # k: 空格 v
  1. 对象:键值对的集合。map、hash、set、object
行内写法: 
k: {k1:v1,k2:v2,k3:v3}
#或
k: 
  k1: v1
  k2: v2
  k3: v3
  1. 数组:一组按次序排列的值。array、list、queue
行内写法:  k: [v1,v2,v3]
#或者
k:
 - v1
 - v2
 - v3

示例:

@Data
@ConfigurationProperties("school")
@Component
public class SchoolPojo {
    private String name;//学校名称
    private List<GradePojo> grade;//年级名称
}

@Data
public class GradePojo {
    String gName;
    Map<String, List<ClassPojo>>classification;//班级分类
}
@Data
public class ClassPojo {
    String name;
}
school:
  name: 实验中学
  grade:
    - gName: 高一
      classification: {good:[{name: 一班},{name: 二班}],bad:[{name: 3班}]}
    - gName: 高二
      classification: {good:[{name: 1班},{name: 2班}],bad:[{name: 3班}]}

2、配置提示

  自定义的类和配置文件绑定一般没有提示。如果想要提示可以引入一个插件。

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

                    <!--不需要将这个插件打包到项目中-->
 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

三、Web开发

在这里插入图片描述

1.Spring MVC 自动配置

  Spring Boot 为 Spring MVC 提供了自动配置,适用于大多数应用程序。

自动配置在 Spring 的默认值之上添加了以下特性

  1. 包括ContentNegotiatingViewResolver和BeanNameViewResolver(bean)。

  2. 支持提供静态资源,包括对 WebJars 的支持(官网有介绍)。

  3. 自动注册Converter、GenericConverter和Formatterbean。

  4. 支持HttpMessageConverters(官网有介绍)。

  5. 自动注册MessageCodesResolver(官网有介绍)。

  6. 静态index.html支持。

  7. ConfigurableWebBindingInitializerbean的自动使用(官网有介绍)。

  If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
  如果你想保留那些Spring Boot MVC自定义基础上,并做更多的MVC自定义(拦截器、格式化器、视图控制器和其他特性),你可以将你自己的自定义的WebMvcConfigurer类的组件加到容器中,不要加@EnableWebMvc注解。

  If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.
  如果你想提供RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义实例,并且仍然保持Spring Boot MVC自定义,你可以声明一个WebMvcRegistrations类型的bean,并使用它来提供这些组件的自定义实例。

  If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.
  如果你想完全控制Spring MVC,你可以添加你自己的@Configuration注释@EnableWebMvc,或者添加你自己的@Configuration注释DelegatingWebMvcConfiguration,如@EnableWebMvc的Javadoc中描述的那样。
使用 @EnableWebMvc+@Configuration或者@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

2.简单功能分析

2.1、静态资源访问

1、默认的静态资源目录

  By default, Spring Boot serves static content from a directory called /static (or /public or /resources or /META-INF/resources) in the classpath or from the root of the ServletContext. It uses the ResourceHttpRequestHandler from Spring MVC so that you can modify that behavior by adding your own WebMvcConfigurer and overriding the addResourceHandlers method.
  默认情况下,Spring Boot服务的静态资源来自类路径中的/static(或/public或/resources或/META-INF/resources)目录,或者来自ServletContext的根目录。它使用了Spring MVC中的ResourceHttpRequestHandler,这样您就可以通过添加自己的webmvcconfigururer和覆盖addResourceHandlers方法来修改该行为。
  In a stand-alone web application, the default servlet from the container is also enabled and acts as a fallback, serving content from the root of the ServletContext if Spring decides not to handle it. Most of the time, this does not happen (unless you modify the default MVC configuration), because Spring can always handle requests through the DispatcherServlet.
  在独立的web应用程序中,来自容器的默认servlet也被启用,并充当回退,如果Spring决定不处理它,则从ServletContext的根目录提供内容。大多数时候,这种情况不会发生(除非您修改默认的MVC配置),因为Spring总是可以通过DispatcherServlet处理请求。

1.1.改变默认的静态资源路径

  By default, resources are mapped on /, but you can tune that with the spring.mvc.static-path-pattern property. For instance, relocating all resources to /resources/ can be achieved as follows:
  默认情况下,资源映射在/上,但是你可以用spring.mvc.static-path-pattern属性将所有资源重新分配到/resources/,可以实现如下操作:

spring:
  mvc:
    static-path-pattern: /res/**
1.2、静态资源访问前缀

  我们还可以使用该spring.web.resources.static-locations属性自定义静态资源位置(将默认值替换为目录位置列表)。根 servlet 上下文路径"/"也会自动添加为位置。

##设置静态资源存放的路径(静态资源存在这指定的路径下才能被访问)   
## properties 
spring.resources.static-locations = classpath:/static,classpath:/public,classpath:/resources,classpath:/META-INF/resources
##yml
spring:
   web:
     resources:
      static-locations: classpath:/public/,classpath:/static/

  只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources就能被访问。
访问路径为 : 当前项目根路径/ + 静态资源名
原理:静态映射/**。
  请求进来,先去找Controller看能不能处理,不能处理的所有请求又都交给静态资源处理器,静态资源也找不到则响应404页面。

1.3、webjar(以Maven依赖的方式引入静态资源)

  In addition to the “standard” static resource locations mentioned earlier, a special case is made for Webjars content. Any resources with a path in /webjars/** are served from jar files if they are packaged in the Webjars format。
  除了前面提到的“标准”静态资源位置之外,Webjars内容还有一种特殊情况。任何路径在/webjars/**的资源都是从jar文件中提供的,如果它们被打包成webjars格式的话。
提示:如果您的应用程序打包为 jar,请不要使用src/main/webapp目录。虽然这个目录是一个通用的标准,但它只适用于war 打包,如果你生成一个 jar,它会被大多数构建工具默认忽略。
https://www.webjars.org/

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.5.1</version>
        </dependency>

webjars

2.2、欢迎页支持

  SpringBoot支持静态和模板欢迎页面。它首先在配置的静态内容位置中查找index.html文件。如果没有找到,它将查找(index template)索引模板。如果找到其中一个,它将自动用作应用程序的欢迎页面。

2.3、自定义 Favicon

  与其他静态资源一样,Spring Boot在配置的静态资源路径位置中检查,如果存在favicon.ico文件,它会自动用作应用程序的图标。

2.4、静态资源配置原理

1.回顾及概述

  在SpringBoot的自动配置原理中讲了,SpringBoot启动时会加载很多自动配置类。与静态资源配置相关的就是WebMvcAutoConfiguration。
自定义组件比自动配置的组件会先加到容器中
  SpringBoot全部加载,按需配置。所以要想WebMvcAutoConfiguration生效需要满足到条件有:

  1. @ConditionalOnWebApplication(type = Type.SERVLET):程序是个web应用
  2. @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }):类路径下有Servlet、DispatcherServlet、WebMvcConfigurer这些类。
  3. @ConditionalOnMissingBean(WebMvcConfigurationSupport.class):此时。当 BeanFactory 中不包含指定的WebMvcConfigurationSupport类型的类。
      注意:关于第三条,只要在加载WebMvcAutoConfiguration组件时,BeanFactory 中不包含指定的WebMvcConfigurationSupport类型的就可以。在加载完WebMvcAutoConfiguration后,WebMvcConfigurationSupport仍可以被加到容器中。
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebMvcAutoConfiguration {
}

  给容器中配了什么。首先WebMvcAutoConfigurationAdapter 是WebMvcAutoConfiguration 的内部类,只有WebMvcAutoConfiguration 生效了它才会生效。
  @EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class }):开启配置绑定,其中WebMvcProperties(类)与spring.mvc(配置文件)绑定在一起,WebProperties和spring.web绑定在一起。

    @Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {
		private final Resources resourceProperties;
		private final WebMvcProperties mvcProperties;
		private final WebProperties webProperties;
		private final ListableBeanFactory beanFactory;
		private final WebMvcRegistrations mvcRegistrations;
		private ResourceLoader resourceLoader;
      //有参构造器所有参数的值都会从容器中确定
      //WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
	    	public EnableWebMvcConfiguration(WebMvcProperties mvcProperties, WebProperties webProperties,
				ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ListableBeanFactory beanFactory) {
			this.resourceProperties = webProperties.getResources();
			this.mvcProperties = mvcProperties;
			this.webProperties = webProperties;
			this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
			this.beanFactory = beanFactory;
		}
	}
2、资源处理的默认规则

  从下面的方法我们可以知道,对于静态资源请求路径,SpringBoot会先将请求路径/webjars/** 与类路径classpath:/META-INF/resources/webjars/做映射。然后再将/**与{“classpath:/META-INF/resources/”,“classpath:/resources/”, “classpath:/static/”, “classpath:/public/” }做映射。所以我们将静态资源放{“classpath:/META-INF/resources/”,“classpath:/resources/”, “classpath:/static/”, “classpath:/public/” }可以访问,这也解释了为啥能访问webjar导入的静态文件。

	@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
		  //是否启用默认的静态资源处理,默认是true
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			//添加资源处理规则,将请求路径/webjars/**与路径/META-INF/resources/webjars/做映射,对请求路径/webjars/**下的请求,去类路径下的/META-  INF/resources/webjars/去找,这是对webjars资源的处理
			addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
			//添加静态资源影视 this.mvcProperties.getStaticPathPattern()="/**";
			/**(registration) -> {
				registration.addResourceLocations(this.resourceProperties.getStaticLocations());
				if (this.servletContext != null) {
					ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
					registration.addResourceLocations(resource);
				}
			})是一个消费接口,程序在加载到这里时,方法不会被执行。只有这个接口调用accept方法时,里面的方法才会被执行。
			**/
			addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
			//this.resourceProperties.getStaticLocations()={"classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };
				registration.addResourceLocations(this.resourceProperties.getStaticLocations());
				if (this.servletContext != null) {
					ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
					registration.addResourceLocations(resource);
				}
			});
		}
    //将可变参数String... locations包装成Consumer接口:只有在程序调用Consumer的accept,函数的内容才会被执行
    private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
			addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations));
		}
     //添加资源映射规则,使用jdk自带的函数式接口作为方法的参数customizer
		private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
				Consumer<ResourceHandlerRegistration> customizer) {
			//容器中这个类路径已经被其他的请求路径映射了
			if (registry.hasMappingForPattern(pattern)) {
				return;
			}
			//添加一个资源处理程序来处理静态资源。对于匹配指定的URL路径模式之一的请求,将调用处理程序。
			ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
			//将请求路径与静态资源路径映射(绑定)
			customizer.accept(registration);//函数式接口调用抽象方法,该抽象方法在传参的时已经被重写了。
			registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
			registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
			registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
			customizeResourceHandlerRegistration(registration);
		}
3、欢迎页的处理配置
  1. 在默认静态资源路径下查找index.html文件
	HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。
	/**
	*FormattingConversionService mvcConversionService:数据格式化
	*ResourceUrlProvider mvcResourceUrlProvider:用于获取公共URL路径的中心组件,客户机应该使用该路径访问静态资源。
	*
	*/
    //像容器添加组件方法的参数,是从容器中取的
     @Bean
		public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
				FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
				//this.mvcProperties.getStaticPathPattern()=/**
			WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
					new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
					this.mvcProperties.getStaticPathPattern());
					
			welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
			welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
			return welcomePageHandlerMapping;
		}
  //获取欢迎页:查看类路径下有没有默认的静态资源文件夹
	private Resource getWelcomePage() {
	     //this.resourceProperties.getStaticLocations()= { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" }
			for (String location : this.resourceProperties.getStaticLocations()) {
				Resource indexHtml = getIndexHtml(location);
				if (indexHtml != null) {
					return indexHtml;
				}
			}
			ServletContext servletContext = getServletContext();
			if (servletContext != null) {
				return getIndexHtml(new ServletContextResource(servletContext, SERVLET_LOCATION));
			}
			return null;
		}
     
    private Resource getIndexHtml(String location) {
			return getIndexHtml(this.resourceLoader.getResource(location));
		}

		private Resource getIndexHtml(Resource location) {
			try {
          //在资源的相对路径创建新资源index.html
				Resource resource = location.createRelative("index.html");
				//resource.exists(),确定此资源是否以物理形式实际存在(当前资源路径是不是真的存在index.html资源)。
				if (resource.exists() && (resource.getURL() != null)) {
					return resource;
				}
			}
			catch (Exception ex) {
			}
			return null;
		}

2.看静态资源路径下是不是存在index.html文件,并且与静态资源文件夹映射请求路径是/**(即不能设置静态资源的访问前缀)。两个条件都满足,这个index.html就是欢迎页。不存在的话就看是不是存在欢迎页模板(template)

    @Bean
		public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
				FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
				//this.mvcProperties.getStaticPathPattern()=/**
			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, Resource welcomePage, String staticPathPattern) {
		//如果静态资源目录存在index文件,用于静态资源的路径模式是/**;就是静态资源没设置访问前缀(spring.mvc.staticPathPattern可以设置静态资源访问前缀),所以设置了这个,欢迎页会失效。
		if (welcomePage != null && "/**".equals(staticPathPattern)) {
			logger.info("Adding welcome page: " + welcomePage);
			setRootViewName("forward:index.html");
		}
		//如果不满足上面的条件,就看看有没有欢迎页的模板引擎
		else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
			logger.info("Adding welcome page template: index");
			setRootViewName("index");
		}
	}
	//查看是不是存在欢迎页模板
private boolean welcomeTemplateExists(TemplateAvailabilityProviders templateAvailabilityProviders,
			ApplicationContext applicationContext) {
		return templateAvailabilityProviders.getProvider("index", applicationContext) != null;
	}
   private void setRootViewName(String viewName) {
   	 //新建一个处理器(handler),来处理对根路径的请求
		ParameterizableViewController controller = new ParameterizableViewController();
		controller.setViewName(viewName);
		setRootHandler(controller);
		setOrder(2);
	}

3、请求参数处理

0、请求映射

  对于web开发时,我们要做的主要就是接收浏览器发出的请求,然后经过后台一系列的处理将得到的结果返回给前端。这里将浏览器的请求与我们的处理方法进行映射(绑定),就是所谓的请求映射了。

1、rest使用与原理

  在传统的请求映射中,我们在方法中声明这个方法能够处理浏览器对某个路径的请求,当对这个路径的请求到来时,我们就映射到这个方法中去处理。

 @RequestMapping("/addUser") //声明我可以处理对/addUser的请求
    public void addUser(){
        System.out.println("添加用户");
    }

    @RequestMapping("/deleteUser")
    public void deleteUser(){
        System.out.println("删除用户");
    }
    @RequestMapping("/updateUser")
    public void updateUser(){
        System.out.println("修改用户");
    }
    @RequestMapping("/selectUser")
    public void selectUser(){
        System.out.println("查询用户");
    }

   而所谓的rest请求就是不仅要根据请求路径来找能够处理这个请求的方法,还要根据请求的方式(比如:get、post)来进一步确定由哪个方法来处理请求。即使用HTTP请求方式动词来表示对资源的操作。从下面的方法中可以看到@RequestMapping的vaule是一样的,但是它们处理的是不同的请求,此时他们就是根据请求方式来确定由那个方法来处理这个请求。可以将请求关系映射和请求方式类比为一级标题和二级标题,先根据请求映射找,找到多个的话在根据请求方式来区分。

  @RequestMapping(value="/user",method = RequestMethod.POST)
    public String addUserRest(){
        return "post--保存用户" ;
    }

    @RequestMapping(value="/user",method = RequestMethod.DELETE)
    public String deleteUserRest(){
        return "delete--删除用户" ;
    }
    @RequestMapping(value="/user",method = RequestMethod.PUT)
    public String updateUserRest(){
        return "put--修改用户" ;
    }
    @RequestMapping(value="/user",method = RequestMethod.GET)
    public String  selectUserRest(){
      return "get--查询用户" ;
    }

rest原理:实现rest请求的核心Filter是HiddenHttpMethodFilter
1、组件加到容器的时机
  请求方式大致可分为4种(get、post、put、delete),但是表单提交只支持(post、和get)两种提交方式,那么对于put和delete请求它是怎么处理的呢?
  首先我们知道,对于浏览器发出的请求,我们如果想在用程序处理某个请求之前,先对请求进行一些操作,我们会使用过滤器(filter)拦截这个请求,在进行一些操作以后,在对请求进行放行。这里对于put和delete的请求方式,SpringBoot就是通过过滤器来实现的(这个过滤器是OrderedHiddenHttpMethodFilter)。接下来我们来看看它是怎么做的。
  要想使用这个组件,容器中必须要有这个组件。查看源码我们会发现要想OrderedHiddenHttpMethodFilter加到容器中,需要满足2个条件(1)加载这个组件时容器中不能出现HiddenHttpMethodFilter类型的组件(2)配置文件中配置spring.mvc.hiddenmethod.filter.enabled=true

//这个组件SpringBoot加载自动配置类的时侯会自动加载到容器中
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebMvcAutoConfiguration {
   @Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	//关于@ConditionalOnProperty注解:如果该属性根本不包含在 Environment 中,则查询 matchIfMissing() 属性。默认情况下,缺少的属性不匹配。
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}
}

2、Rest原理(表单提交要使用REST的时候)
(1)、首先这个拦截器会拦截所有的post请求。
(2)、如果非post请求,直接放行。如果是post请求,查看post请求有没有带上name为_method参数
(3)、将name为_method参数值大写。
(4)、看大写后的参数值是不是在集合[PUT,DELETE,PATCH]( 兼容以下请求;PUT.DELETE.PATCH)
(5)、原生请求是request(post),包装模式requesWrapper重写了getMethod方法,所以此时请求方式就变为了_method的值。
Rest使用客户端工具,
● 如PostMan直接发送Put、delete等方式请求,无需Filter。

注意:使用表单提交的方式,只要提交方式不是post,默认就是get请求。就比如 ,但实际上发出的还是get请求。

public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {
	/**
	 * The default order is high to ensure the filter is applied before Spring Security.
	 */
	public static final int DEFAULT_ORDER = REQUEST_WRAPPER_FILTER_MAX_ORDER - 10000;
	private int order = DEFAULT_ORDER;
	@Override
	public int getOrder() {
		return this.order;
	}
	/**
	 * Set the order for this filter.
	 * @param order the order to set
	 */
	public void setOrder(int order) {
		this.order = order;
	}
}
//这个看不出来什么,我们看看它的子类

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

		public static final String DEFAULT_METHOD_PARAM = "_method";

	    private String methodParam = DEFAULT_METHOD_PARAM;

		HttpServletRequest requestToUse = request;
		//如果请求是Post请求,且请求没有报错
		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
			//获取表单name为_method的变量值
			String paramValue = request.getParameter(this.methodParam);
			//如果表单提交的变量中存在_method变量,且值不为空
			if (StringUtils.hasLength(paramValue)) {
			//查看表单提交的_method变量值转为大写
				String method = paramValue.toUpperCase(Locale.ENGLISH);
				//查看表单提交的_method变量值转为大写后的值是不是在集合[PUT,DELETE,PATCH中]
				if (ALLOWED_METHODS.contains(method)) {
				//存在的话,就将请求的请求方法改为_method的值
					requestToUse = new HttpMethodRequestWrapper(request, method);
				}
			}
		}
		//将请求放行
		filterChain.doFilter(requestToUse, response);
	}
}

3 扩展:修改HiddenHttpMethodFilter的属性methodParam的默认值。

   //因为我们向容器中加入了这个类型的组件,自动配置类就不会向容器添加默认的OrderedHiddenHttpMethodFilter组件了,它会优先用我们自定义的组件。
    @Bean
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        OrderedHiddenHttpMethodFilter orderedHiddenHttpMethodFilter = new OrderedHiddenHttpMethodFilter();
        orderedHiddenHttpMethodFilter.setMethodParam("_m");
        return orderedHiddenHttpMethodFilter;
    }
2、请求映射原理

  请求映射原理就是将我们在浏览器发出的请求,程序是怎么知道由哪个方法去处理的呢,接下来让我们去揭晓自动映射原理吧。
DispatcherServlet的继承树结构
在这里插入图片描述

2.1、浏览器发出一个请求,它的处理流程。

  (1)、SpringBoot对浏览器请求的处理的底层是SpringMVC,SpringMVC使用前端控制器(DispatcherServlet)管理各种请求。
  (2)一个请求过来,对于一个servlet而言(DispatcherServlet本质也是一个Servlet)必定先是由service()方法去处理它。然后service()根据请求方式调用doGet()或doPost()等方法去处理请求。DispatcherServlet没有doget()方法,所以调用它的子类FrameworkServlet 的doget()方法。
  (3)FrameworkServlet 的doget()方法调用了processRequest()方法。
  (4)FrameworkServlet 的processRequest()方法调用了doService()方法。但是FrameworkServlet的doService方法是一个抽象方法,所以此时,它调用的是FrameworkServlet 父类DispatcherServlet的doService()方法。
  (5)DispatcherServlet的doService()方法又调用doDispatcher()方法,请求映射原理核心就在doDispatcher()方法

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
//1.DispatcherServlet没有doGet方法所以请求进到子类的doGet方法
  @Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		processRequest(request, response);
	}
//2.doGet调用processRequest方法
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		LocaleContext localeContext = buildLocaleContext(request);
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
		initContextHolders(request, localeContext, requestAttributes);
	 try {
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}
		finally {
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}
			logResult(request, response, failureCause, asyncManager);
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}
//3.processRequest调用doService方法,但是在FrameworkServlet 中它是一个抽象方法,所以我们去DispatcherServlet 找这个方法
   protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
			throws Exception;
   }
}


public class DispatcherServlet extends FrameworkServlet {
   //4.processRequest调用doService方法,但是FrameworkServlet的doService方法是抽象方法,所以调用DispatcherServlet的doService方法
   @Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);
		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}
		// Make framework objects available to handlers and view objects.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
		if (this.flashMapManager != null) {
			FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
			if (inputFlashMap != null) {
				request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
			}
			request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
			request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
		}
		RequestPath previousRequestPath = null;
		if (this.parseRequestPath) {
			previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
			ServletRequestPathUtils.parseAndCache(request);
		}
		try {
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
			if (this.parseRequestPath) {
				ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
			}
		}
	}
//5、doService方法调用doDispatch方法,请求映射原理的核心就在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);
				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}	
				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}
}
2.2 请求映射原理解析

** (1)DispatcheSrevlet的初始化**
  在了解请求映射原理之前我们先来看DispatcherServlet 做了哪些初始化操作,我们以前学习Servlet的时侯知道SerVlet的生命周期是init()初始化----->service()处理请求------>destroy()销毁。DispatcherServlet本质上也是一个Servlet,所以它的初始化如下图所示:
DispatcherServlet 初始化
  在上面的初始化过程中,调用了一个方法initHandlerMappings()方法。这个方法就初始化了程序中请求映射的所有规则。
** (2)原理解析**
1、doDispatch(HttpServletRequest request, HttpServletResponse response)方法中有个getHandler(HttpServletRequest request)方法,它是用来确定由哪个Handler来处理当前请求。
2、进入getHandler(HttpServletRequest request)方法,发现它在循环便利this.handlerMappings属性,看有没有能处理当前请求的handlerMapping。我们还发现this.handlerMappings在DispatcherServlet的initHandlerMappings方法初始化时,会将容器中所有handlerMapping类型的组件放到this.handlerMappings集合中。


/**  初始化handlerMappings属性
	 * Initialize the HandlerMappings used by this class.
	 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
	 * we default to BeanNameUrlHandlerMapping.
	 */
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
		for (HandlerMapping mapping : this.handlerMappings) {
			if (mapping.usesPathPatterns()) {
				this.parseRequestPath = true;
				break;
			}
		}
	}


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);
				// Determine handler for the current request.决定当前的请求由哪个处理器来处理
				mappedHandler = getHandler(processedRequest);
	}
}
	/**
	 * Return the HandlerExecutionChain for this request.
	 * <p>Tries all handler mappings in order.
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
	 */
	 //获取处理器执行链
	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//容器中是不是存在HandlerMappings类
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

处理器2、发现程序从容器中取出所有的xxxHandlerMapping组件并遍历。看哪个组件能处理当前请求。
  注意:它是有序的,先看@RequestMappingHandlerMapping能不能处理,不能处理就交给WelcomePageHandlerMapping处理器来处理,依此类推。这也就解释对假如我们将静态文件名作为请求路径时,它就不会被当静态文件来处理的原因。因为静态资源的处理优先级比普通的请求优先级要低。

1、普通参数与基本注解

1.1、注解:

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

@RestController
public class ParamTestController {
    @RequestMapping(value = "/pet/{pName}/owner/{uName}")
    //@PathVariable将映射路径变量的值与方法参数进行,如果用@PathVariable修饰如果方法参数是Map,那么方法参数将映射所有路径变量名和值。
    //@RequestHeader浏览器发出请求时,将请求头的信息与方法参数进行映射。如果用@RequestHeader修饰如果方法参数是Map,那么方法参数将映射所有请求头的信息。
    //将@ModelAttribute放在方法的参数上将请求参数与被注解所修饰的方法参数进行绑定(请求的参数与实体类的属性名要相同)
    //将@ModelAttribute放在方法上,那么这个Controller在处理请求前都会先执行这个方法
    //@RequestParam指示方法参数应绑定到 Web 请求参数的注释。
    //@CookieValue指示方法参数绑定到 HTTP cookie 的注释。
    //@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的),所以只能发送POST请求。后端@RequestBody注解对应的类在将HTTP的输入流(含请求体)装配到目标类(即:@RequestBody后面的类)时,会根据json字符串中的key来匹配对应实体类的属性,如果匹配一致且json中的该key对应的值符合(或可转换为)实体类的对应属性的类型要求时,会调用实体类的setter方法将值赋给该属性。
    public Map<String, Object> getPet(@PathVariable("pName") String pName,
                                     @PathVariable("uName") String uName,
                                     @PathVariable Map pathMap,
                                     @RequestHeader("Accept-Encoding")String acceptEncoding,
                                     @RequestHeader Map headerMap,
                                     @ModelAttribute PetPojo pet,
                                     @RequestParam("pName")String p,
                                     @RequestBody(required = false) String q) {
        return new HashMap<String, Object>(){{
            put("pName",pName);
            put("map",pathMap);
            put("acceptEncoding",acceptEncoding);
            put("headerMap",headerMap);
        }};
    }
		@Data
		public class PetPojo {
  		  String pName;
  		  String uName;
		}
     //矩阵变量
    @RequestMapping({"/param/{boss}/{emp}"})
    public Map<String, Object> MatrixVariable(@MatrixVariable(pathVar = "boss",value = "id")              	String bossId, @MatrixVariable(pathVar = "emp",value = "id") String empId) {
        return new HashMap<String, Object>() {
            {
                this.put("bossId", bossId);
                this.put("empId", empId);
            }
        };
    }
1.2、Servlet API:

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver参数处理器能够处理的参数类型

@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

1.3、复杂参数:

  对于前端发出的请求参数,我们在后台能够用什么样类型的数据去接

持续更新中。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值