天天看点

Springboot -自动配置分析

学习视频: https://www.bilibili.com/video/BV1PE411i7CV?p=6

1.1 相关依赖配置

Pom.xml

它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!

<!--父依赖-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.5</version>
</parent>
           

点进去,发现还有一个父依赖。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.4.5</version>
</parent>
           

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心,以后导入依赖默认是不需要写版本。但是如果导入的包没有在依赖中管理着就需要手动配置版本了。

启动器 spring-boot-starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
           
  • springboot-boot-starter-xxx:就是spring-boot的场景启动器。
  • spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件。
  • SpringBoot将所有的功能场景都抽取出来,做成一个个的starter 启动器,只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来。
  • 要用什么功能就导入什么样的场景启动器即可,我们未来也可以自己自定义

    starter

1.2 主启动类自动装配原理

默认启动类
package cn.guardwhy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // 标注了一个主程序类,说明这是一个Spring Boot应用.
public class SpringbootDemo01Application {

    public static void main(String[] args) {
        // 启动了一个服务
        SpringApplication.run(SpringbootDemo01Application.class, args);
    }
}
           
@SpringBootApplication

进入这个注解,可以看到上面还有很多其他注解。

Springboot -自动配置分析

作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用。

Springboot -自动配置分析
public @interface SpringBootApplication {
  // 源码示例
}
           

1.2.1 @ComponentScan

这个注解在Spring中很重要 ,它对应XML配置中的元素,扫描当前启动类同级的包。

自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中。

1.2.2 @SpringBootConfiguration

标注在某个类上 , 表示这是一个SpringBoot的配置类,继续点击注解查看。

Springboot -自动配置分析
@Configuration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
           

这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件。继续点击下去

@Component
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;
}
           

里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!!!

1.2.3 @EnableAutoConfiguration

单体应用架构需要自己配置文件,而现在SpringBoot可以自动帮我们配置 。

@EnableAutoConfiguration(自动导入包)告诉SpringBoot开启自动配置功能,这样自动配置才能生效,点击注解继续查看。

@AutoConfigurationPackage

具体作用

自动配置包,继续点击下去。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	/**
	 * Environment property that can be used to override when auto-configuration is
	 * enabled.
	 */
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}
           
@Import(AutoConfigurationPackages.Registrar.class)
基本作用

Spring底层注解

@import

,给容器中导入一个组件。

Registrar.class

将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 。自动配置,自动注册包。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

	/**
	 * Base packages that should be registered with {@link AutoConfigurationPackages}.
	 * <p>
	 * Use {@link #basePackageClasses} for a type-safe alternative to String-based package
	 * names.
	 * @return the back package names
	 * @since 2.3.0
	 */
	String[] basePackages() default {};

	/**
	 * Type-safe alternative to {@link #basePackages} for specifying the packages to be
	 * registered with {@link AutoConfigurationPackages}.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 * @return the base package classes
	 * @since 2.3.0
	 */
	Class<?>[] basePackageClasses() default {};
}
           
@Import(AutoConfigurationImportSelector.class)

给容器导入组件,自动导入包的核心!!!

@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 {};
}
           
AutoConfigurationImportSelector

自动导入选择器,选择了啥?

获取所有的配置,继续点击。

获取候选的配置

// 方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                         getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}
           
Springboot -自动配置分析
getAutoConfigurationEntry( )

获得自动配置的实体!!!

Springboot -自动配置分析
getCandidateConfigurations( )

获取候选的配置!!!

Springboot -自动配置分析
public static List loadFactoryNames( )

获取所有的加载配置!!!

Springboot -自动配置分析

1.2.4 loadSpringFactories

继续点击源码,调用了 SpringFactoriesLoader 类的静态方法!进入SpringFactoriesLoader类loadFactoryNames( )方法

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

     String factoryTypeName = factoryType.getName();
     return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
           
Springboot -自动配置分析
继续点击查看 loadSpringFactories 方法​
Springboot -自动配置分析

获取项目资源:

classLoader.getResources(FACTORIES_RESOURCE_LOCATION);

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = cache.get(classLoader);
	// 获得classLoader,返回可以看到这里得到的EnableAutoConfiguration标注的类本身
    if (result != null) {
        return result;
    }

    result = new HashMap<>();
    try {
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        // 判断是否存在更多的元素
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 将读取到的资源遍历,封装成一个Properties,所有的资源加载到配置类中
            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;
}
           

spring.factories

从这里获取系统文件(自动配置核心):

"META-INF/spring.factories";

根据源码打开spring.factories , 看到了很多自动配置的文件,这就是自动配置根源所在!

Springboot -自动配置分析
通过该文件找到相对应的jar包,从jar包中找到Springboot自动配置核心文件!!!
Springboot -自动配置分析

1.2.5 WebMvcAutoConfiguration

随机在自动配置类打开看看,比如选择WebMvcAutoConfiguration类。

Springboot -自动配置分析

可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,自动配置真正实现是从classpath中搜寻所有的

META-INF/spring.factories

配置文件 ,并将其中对应的

org.springframework.boot.autoconfigure

包下的配置项。

通过反射实例化为对应标注了

@Configuration的JavaConfig

形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。

注意事项:有些自动配置为啥有的没有生效,需要导入相应的start才能有作用?

核心注解:@ConditionalOnXXX,如果这里面的条件都满足,才会生效!!!

1.2.6 小结

SpringBoot所自动配置都是在启动的时候扫描并且加载

spring.factories

所有的自动配置类都在这里面,但是不一定生效。要判断条件是否生效,只要导入了对应的start,就有对应的启动器了就有对应的启动器了,自动装配就会生效,然后就配置成功!!!

  • Springboot在启动的时候,从类路径之下

    /META-INF/spring.factories

    获取指定的值,将这些自动配置的类导入容器,自动配置就会生效,帮我们进行自动配置!
  • 以前我们需要自动配置的东西,现在由Springboot帮助我们做了,整合JavaEE,解决方案和自动配置的东西都在

    spring-boot-autoconfigure-2.4.5.jar

    这个包下面。它会把所有的需要导入的组件,以类名的方式返回,这些组件就会被添加到容器。
  • 容器中也会存在非常多的

    xxxAutoConfiguration

    的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件,并且自动配置

    (@Configuration,JavaConfig)!!

    有了自动配置类,免去了手动编写配置文件的工作。

1.3 主启动类运行

最初以为就是运行了一个main方法,但是真实的情况是开启了一个服务。

package cn.guardwhy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootDemo01Application {

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

主启动类运行主要分为两个方面:一部分是SpringApplication的实例化,二是run方法的执行。

1.3.1 SpringApplication实例化

  • 推断应用的类型是普通的项目还是Web项目。

    查找并加载所有可用初始化器 , 设置到initializers属性中。

  • 找出所有的应用程序监听器,设置到listeners属性中。
  • 推断并设置main方法的定义类,找到运行的主类。

查看SpringApplication的构造器

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = Collections.emptySet();
    this.isCustomEnvironment = false;
    this.lazyInitialization = false;
    this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = this.deduceMainApplicationClass();
}
           

1.3.2 run方法的执行

Springboot -自动配置分析

1.4 分析自动配置原理

HttpEncodingAutoConfiguration(Http编码自动配置)

为例解释自动配置原理

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;

// 表示这是一个配置类, 也可以给容器中添加组件。
@Configuration(proxyBeanMethods = false)
// 将配置文件中对应的值和ServerProperties绑定起来,并把ServerProperties加入到ioc容器中
@EnableConfigurationProperties(ServerProperties.class)

// Spring底层@Conditional注解:根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效。

// 判断当前应用是否是web应用, 如果是当前配置类生效。
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)

//判断当前项目有没有这个类CharacterEncodingFilter,SpringMVC中进行乱码解决的过滤器。
@ConditionalOnClass(CharacterEncodingFilter.class)

/*
	判断配置文件中是否存在某个配置: server.servlet.encoding
	如果不存在,判断也是成立的,即使我们配置文件中不配置server.servlet.encoding = true, 也是默认生效的。
*/
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
	//它已经和SpringBoot的配置文件映射了
	private final Encoding properties;
	//只有一个有参构造器的情况下,参数的值就会从容器中拿
	public HttpEncodingAutoConfiguration(ServerProperties properties) {
		this.properties = properties.getServlet().getEncoding();
	}
	
    //给容器中添加一个组件,这个组件的某些值需要从properties中获取
	@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;
		}
	}
}
           
根据当前不同的条件判断,决定这个配置类是否生效。

Springboot -自动配置分析
  • 当这个配置类生效,这个配置类就会给容器中添加各种组件。
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性是和配置文件进行深度绑定的。
  • 所有在配置文件中能配置的属性都是在

    (?)Properties

    类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类。
    Springboot -自动配置分析

1.4.1 自动装配小结

  • SpringBoot启动会加载大量的自动配置类,查看需要的功能有没有在SpringBoot默认写好的自动配置类当中。
  • 查看自动配置类中到底配置了哪些组件(如果该组件存在要我们要用的组件存在,则无需手动配置),给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。只需要在配置文件中指定这些属性的值即可。
  • (???)AutoConfigurartion

    是自动配置类,给容器中添加组件。

    (???)Properties

    封装配置文件中相关属性,可以通过Springboot配置

    (.yaml)

    修改属性!!

1.4.2 @Conditional

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是

@Conditional

指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效。

Springboot -自动配置分析

注意点

自动配置类必须在一定的条件下才能生效, 可以通过启用

debug=true属性

,让控制台打印自动配置报告,这样就可以知道哪些自动配置类生效。

# 可以通过 debug=true来查看,哪些自动配置类生效,哪些没有生效
debug: true
           
Positive matches:(自动配置类启用的:正匹配)
Springboot -自动配置分析
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Springboot -自动配置分析
Unconditional classes: (没有条件的类)

Springboot -自动配置分析

继续阅读