SpringBoot 通过读取源码理解“自动配置类”的读取原理

678 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

一、背景

  springboot的出现无疑事我们java开发人员的一个福音,它不仅帮我们集成了各类的装配插件,同时也让我们在各个插件的配置文件变的简单很多。创建一个springboot不用任何的配置,我们就可以让项目启动起来,比如端口号,web服务器等都是springboot自动帮我们进行配置的,我们需要修改的时候,只需要在默认的application.properties中进行相关配置即可,比如修改端口号:server.port=8090,那么还有一些其他配置该如何进行配置呢?我们可以看下官网给的配置信息清单。

1-1、springboot的配置信息

首先打开spring官网,然后打开springboot的最新稳定版配置信息,如下: 1、打开官网

image.png 2、查看最新版本文档

image.png

3、打开配置文件信息页

image.png

4、官网提供了相关配置信息

image.png

5、搜索我们熟知的端口号

image.png

1-2、通过启动类逐步了解自动配置

1-2-1、springboot的@SpringBootApplication

之前的文章提到过,springboot启动类有一个注释@SpringBootApplication,标注了这个注解就代表这个类为springboot的主配置类,SpringBoot需要运行这个类的main方法来启动SpringBoot应用;我们进入这个注解

image.png 可以看到这个文件中有很一些其他的注解,这些注解各代表含义如下:

@Target({ElementType.TYPE}): 设置当前注解可以标记在哪,经常用到的如下:
TYPE:标记在类上面
FIELD:标记在属性字段上面
METHOD:标记在方法方面
CONSTRUCTOR:标记在构造函数
PARAMETER:标记在方法参数上面\

@Retention(RetentionPolicy.RUNTIME):当注解标注的类编译以什么方式保留
RUNTIME:会被jvm加载
CLASS :只保留原class文件,不会保留注解,就是在编译之后注解不会保留,通过反射获得注解的方式,是无法获取到的类对象的
SOURCE:

@Documented :java doc 会生成注解信息

@Inherited :是否会被继承

@SpringBootConfiguration :标注在某个类上,表示这是一个Spring Boot的配置类;进入这个类如下

image.png 其中
@Confifiguration:配置类上来标注这个注解;就是讲当前标注的类最为配置类。

@Indexed,它可以为 Spring 的模式注解添加索引,以提升应用启动性能。

@EnableAutoConfifiguration:开启自动配置功能;

以前我们需要配置的东西,Spring Boot帮我们自动配置;@EnableAutoConfifiguration告诉SpringBoot开启自动配置,会帮我们自动去加载自动配置类

@ComponentScan : 扫描包 相当于在spring.xml 配置中context:component-scan 但是并没有指定basepackage,如果没有指定,spring底层会自动扫描当前配置类所在的包,如我这边会自动扫描com.jony包下的所有包

image.png

excludeFilters:相当于之前spring文章中说的context:exclude-filter

TypeExcludeFilter:springboot对外提供的扩展类, 可以供我们去按照我们的方式进行排除,这个类我们可以进行进行实现,并重写里面的match方法,实现我们自己需要排除的相关操作。

image.png 通过以上操作,就可以将MyController排除

AutoConfigurationExcludeFilter 排除所有配置类并且是自动配置类中里面的其中一个

1-2-2、@EnableAutoConfiguration 自动配置类注解

这个注解里面,最主要的就是@EnableAutoConfiguration,这么直白的名字,一看就知道它要开启自动配置,我们进去@EnableAutoConfiguration的源码。

image.png

再次进入@AutoConfigurationPackage

image.png

再次进入Registrar

image.png

进入register方法,如下:

image.png

debug模式启动,可以看到packageNames=com.jony image.png

通过以上我们可以得知@AutoConfigurationPackage将当前配置类所在包保存在BasePackages的Bean中。供Spring内部使用

1-2-2-1、AutoConfigurationImportSelector

1、在EnableAutoConfiguration注解类中,通过@Import导入了AutoConfigurationImportSelector,如下:

image.png 2、可以看到其实现了DeferredImportSelector,在之前spring文章中提到可以通过ImportSelector,导入数组类的完整限定名进行bean的注入。

而这个地方的DeferredImportSelector是一个延迟的加载模式

image.png

3、再次进入DeferredImportSelector,可以看到其继承了ImportSelector,并且定了一个新的Group接口,同时有一个getImportGroup方法。

image.png

4、AutoConfigurationImportSelector注解类中就会查看是否实现了DeferredImportSelector中的getImportGroup方法,如果没有实现就会调用ImportSelect中的selectImports,如果有则需要返回一个自定义的实现了DeferredImportSelector中group接口的类,如下:

image.png

5、如果实现了DeferredImportSelector中的getImportGroup方法,就会同步调用Group接口里的process()方法(用于获得相关配置信息),以及通过Group接口里的selectImports()方法(用于对配置进行整理过滤)。

5-1、DeferredImportSelector中的子接口Group image.png

5-2、AutoConfigurationImportSelector中实现的process和selectImports() image.png

6、在process方法中核心调用了getAutoConfigurationEntry方法,然后在这个方法中调用了getCandidateConfigurations,加载了所有自动配置类(后缀都是以AutoConguration结尾)使用springboot2.6.7版本目前有133个,添加debug可以看到如下:

image.png

7、在getCandidateConfigurations中通过调用SpringFactoriesLoader.loadFactoryNames方法来获取所有自动配置文件

image.png

7-1、通过调用loadFactoryNames中的factoryType.getName(),获得到了完整限定名

image.png 7-2、同时调用了loadSpringFactories,用来读取所有存放配置类的文件,首先会从缓存中进行读取,如果缓存中没有则读取相关jar包中的配置文件,路径为:META-INF/spring.factories,如下:

image.png

7-3、通过上图可以看到,通过classLoader去类路径查找相关的配置文件,实际上就是jar包中的BOOT-INF/lib中的包去查找

image.png

7-4、如下可以看到通过while挨个读取本地仓库中的jar包中的META-INF/spring.factories

image.png

7-5、以springboot为例,可以看到jar包下,相关信息,通过读取spring.factories,就可以读取到key/val的值,这块value为list

image.png

7-5-1、最终实际读取了以下三个jar包中的spring.factories

spring-boot image.png

spring-autoconfigure image.png

spring-beans image.png

7-6、最终可以看到一共读取了19个key

image.png

7-7、需要注意的是,这19个key并不是全部都是自动配置类,会通过org.springframework.boot.autoconfigure.EnableAutoConfiguration进行过滤,这样就得到133个value,只有这个key下的value才是自动配置类

image.png

7-8、之后又根据pom中依赖的启动器,进行再次过滤,因为目前只依赖了springmvc,因此最终过滤出来,最终剩下25个,如下

image.png

8、通过以上的流程最终流程为

自动配置类运行流程.jpg

1-3、自动配置类的解析

上面介绍了自动配置类的读取加载过程,那一个自动配置类的原理到底是怎么样的呢?我们用HttpEncodingAutoConfiguration这个自动配置来来进行解析了解。

/*
 * Copyright 2012-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.web.servlet;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.server.Encoding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
 * in web applications.
 *
 * @author Stephane Nicoll
 * @author Brian Clozel
 * @since 2.0.0
 */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

   private final Encoding properties;

   public HttpEncodingAutoConfiguration(ServerProperties properties) {
      this.properties = properties.getServlet().getEncoding();
   }

   @Bean
   @ConditionalOnMissingBean
   public CharacterEncodingFilter characterEncodingFilter() {
      CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
      filter.setEncoding(this.properties.getCharset().name());
      filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
      filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
      return filter;
   }

   @Bean
   public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
      return new LocaleCharsetMappingsCustomizer(this.properties);
   }

   static class LocaleCharsetMappingsCustomizer
         implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {

      private final Encoding properties;

      LocaleCharsetMappingsCustomizer(Encoding properties) {
         this.properties = properties;
      }

      @Override
      public void customize(ConfigurableServletWebServerFactory factory) {
         if (this.properties.getMapping() != null) {
            factory.setLocaleCharsetMappings(this.properties.getMapping());
         }
      }

      @Override
      public int getOrder() {
         return 0;
      }

   }

}

1-3-1、@Configuration(proxyBeanMethods = false)注解

标记了@Configuration Spring底层会给配置创建cglib动态代理。 作用:就是防止每次调用本类的Bean方法而重新创建对象,Bean是默认单例的。而设置@Configuration(proxyBeanMethods = false)就是讲bean设置为多例,每次new都会重新创建。

1-3-2、@EnableConfigurationProperties(ServerProperties.class)

启用可以在配置类设置的属性对应的类,使用配置类可以使用的一些属性,我们进入到ServerProperties可以看到

image.png 这样就可以使用server.port/server.address 等相关配置信息了。

1-3-2、@Conditional派生注解(Spring注解版原生的@Conditional作用)

1-3-2-1、相关注解作用

@xxxConditional根据当前不同的条件判断,决定这个配置类是否生效 如:
1、@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)指定当前环境必须是一个servlet的环境,否则就不起作用
2、@ConditionalOnClass(CharacterEncodingFilter.class)当前类路径或者jar包中必须包含CharacterEncodingFilter这个类,否则就不起作用。
3、@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)指当配置server.servlet.encoding.enabled=true的时候才起作用,如果=false就不起作用,这时候如若需要让这个配置类起作用,就需要在application.properties中配置server.servlet.encoding.enabled=true如下,在项目中进行了配置,然后启动的时候就会进入构造函数的断点

image.png 当设置=false的时候,就不会起作用了。请注意默认设置的是true

1-3-2-2、characterEncodingFilter方法

1、之前在springmvc的文章中有介绍,解决中文乱码是通过Filter操作的,在web.xml中添加CharacterEncodingFilter,通过设置编码集和forceEncoding=true即可完成编码设置。

而sringboot会先查看是否有CharacterEncodingFilter这个Bean,如果没有则走下面的方法内容。 image.png

2、编码集的设置 1651651682(1).png 通过上图可以看到,实际上编码最终的设置为

properties.getServlet().getEncoding().getCharset().name()

properties为ServerProperties类,ServerProperties类设置的前缀为server,因此最终上面的代码在配置文件中的表现方式为:

server.servlet.encoding.charset 然后springboot底层机会拿到上面的编码设置到filter.setEncoding中,然后内嵌的tomcat拿到这个值就生效了。

同样可以通过设置server.servlet.encoding.force=true设置request/response的编码集

1-3-2-3、Conditional派生其他注解

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

注解作用
@Conditional扩展注解作用(判断是否满足当前指定条件)
@ConditionalOnJava系统的java版本是否符合要求
@ConditionalOnBean容器中存在指定Bean;
@ConditionalOnMissingBean容器中不存在指定Bean;
@ConditionalOnExpression满足SpEL表达式指定
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定资源文件
@ConditionalOnWebApplication当前是web环境
@ConditionalOnNotWebApplication当前不是web环境
@ConditionalOnJndiJNDI存在指定项

实际上上面说道从133个配置类过滤到了25个,就是通过Conditional进行过滤的。

1-4、通过在application.properties设置debug=true,查看加载的自动配置类及未加载的自动配置了

1-4-1、加载的自动配置类

image.png

1-4-2、未加载的自动配置类

image.png

1-4-3、可以在官网进行查看这些自动配置类的作用

image.png

下面这些即为相关的自动配置类 image.png