SpringBoot源码分析

news2024/11/9 2:21:17

SpringBoot源码分析

  • 1.启动类分析
  • 2.SpringBoot的项目启动流程
    • 1.SpringApplication构造函数
      • 1)deduceFromClasspath()
      • 2)getSpringFactoriesInstances
        • 2.1)loadFactoryNames加载类名称
        • 2.2)createSpringFactoriesInstances创建实例
    • 2.run方法
  • 3.SpringBoot自动配置的原理
    • 1.@SpringBootConfiguration
    • 2.@ComponentScan
    • 3.@EnableAutoConfiguration
    • 4.@EnableAutoConfiguration
      • 4.1.@Import
      • 4.2.ImportSelector接口
      • 4.3.AutoConfigurationImportSelector
        • 1)selectImports
        • 2)加载并过滤自动配置
        • 3)getCandidateConfigurations 加载自动配置
      • 4.4.加载到的配置如何筛选
  • 4.SpringBoot学习总结
      • 4.1.SpringBoot是做什么的?
      • 4.2.SpringBoot自动装配是什么?解决了什么问题
      • 4.2.SpringBoot的启动流程
      • 4.3.SpringBoot自动配置原理

面试的时候,面试官经常会问几个问题:

  • SpringBoot是做什么的吗?
  • SpringBoot的项目启动流程?
  • SpringBoot自动配置的原理呢?

1.启动类分析

项目的入口是带有main函数的启动类:ConsumerApplication

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

这里跟SpringBoot有关联的部分有两个

一个是SpringApplication.run(BankApplication.class, args);

一个就是启动类上的注解:@SpringBootApplication

我们分别跟踪两部分内容。

2.SpringBoot的项目启动流程

main函数中的SpringApplication.run(ConsumerApplication.class,args);就是项目的入口,也是Spring加载的完整过程,我们从这里开始。
首先跟入run方法,流程如图:
在这里插入图片描述
因此,接下来要看的是两部分:

  • new SpringApplication(primarySources):构造函数初始化
  • run(args):成员的run方法

1.SpringApplication构造函数

构造函数有关的几个变量和方法提取出来,方便查看:

// SpringApplication.java

/**
 * 资源加载器,读取classpath下的文件
 */
private ResourceLoader resourceLoader;
/**
 * SpringBoot核心配置类的集合,这里只有一个元素,是我们传入的主函数
 */
private Set<Class<?>> primarySources;
/**
 * 当前项目的应用类型
 */
private WebApplicationType webApplicationType;

/**
 * ApplicationContextInitializer 数组
 */
private List<ApplicationContextInitializer<?>> initializers;
/**
 * ApplicationListener 数组
 */
private List<ApplicationListener<?>> listeners;

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}
// 核心构造函数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 1.记录资源加载器
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 2.将传入的启动类装入集合
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 3.判断当前项目的类型,可以是SERVLET、REACTIVE、NONE
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 4.初始化 initializers 数组
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 5.初始化 listeners 数组
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

解读:

  • 1.ResourceLoader resourceLoader:Spring中用来加载资源的加载器
  • 2.Class<?>... primarySources:这里是启动类,本例中就是ConsumerApplication
  • 3. WebApplicationType.deduceFromClasspath():判断当前项目的类型,可以是SERVLET、REACTIVE、NONE,根据当前classpath中包含的class来判断,会影响后续创建的ApplicationContext的类型
  • 4. getSpringFactoriesInstances(ApplicationContextInitializer.class):获取ApplicationContextInitializer类型的实现类对象数组
  • 5. getSpringFactoriesInstances(ApplicationListener.class):获取ApplicationListener类型的实现类对象数组
  • deduceMainApplicationClass():没有实际用途,打印日志,输出当前启动类名称

我们只看难点部分,也就是步骤3、4、5

1)deduceFromClasspath()

判断项目类型:

	static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

可以看到判断结果包含3种:

  • REACTIVE:要求classpath中包含org.springframework.web.reactive.DispatcherHandler,这个是WebFlux中的核心处理器,我们并没有。
  • SERVLET:要求classpath中包含org.springframework.web.servlet.DispatcherServlet,应用程序应作为基于 servlet 的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。这是SpringMVC的核心控制器,在classpath中肯定可以找到
  • NONE:以上都不满足,就是NONE

2)getSpringFactoriesInstances

在构造函数中被调用了两次,分别加载ApplicationContextInitializerApplicationListener

在这里插入图片描述

getSpringFactoriesInstances(Class<T> type) 方法的作用是获得指定接口的实现类的实例集合。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    // 调用下面的一个重载方法,参数type就是接口的类型
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 真正的处理逻辑
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // 1.先加载指定接口的实现类的名称集合
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 2.根据类的名称,创建实例对象
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // 3.排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

这里关键是第1步中,调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)方法,是用来获取指定接口的实现类的名称字符串,而后就可以根据名称创建实例了。

例如我们传递的参数是:ApplicationContextInitializer.class,那么获取的就是ApplicationContextInitializer下面的实现类的名称字符串集合。

那么这里是如何根据接口找到对应的实现类名称呢?

2.1)loadFactoryNames加载类名称

那么loadFactoryNames是如何根据接口找到对应的实现类名称呢,继续跟入:
SpringFactoriesLoader.loadFactoryNames(type, classLoader)方法:

// SpringFactoriesLoader
/**
  * 使用指定的类加载器,加载{@value #FACTORIES_RESOURCE_LOCATION}中记录的,指定factoryClass
  * 类型的实现类的全路径名。
  * @param factoryClass 需要加载的接口或抽象类
  * @param classLoader 用来加载资源的类加载器
  */
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    // 获取接口名称
    String factoryClassName = factoryClass.getName();
    // 从loadSpringFactories(classLoader)方法返回的是一个Map:key是接口名称字符串,值是实现类的名称集合
    // 然后就可以调用map的get方法,根据factoryClass名称获取对应的实现类名称数组
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

注意到这里是先调用loadSpringFactories(classLoader)方法,此方法方法返回的是一个Map:key是接口名称字符串,值是实现类的名称集合。
那么,loadSpringFactories方法是如何读取到这样的map呢?代码如下:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 尝试从缓存中获取结果
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // 从默认路径加载资源文件,地址是:"META-INF/spring.factories"
        Enumeration<URL> urls = (classLoader != null ?
                                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        // 创建空map
        result = new LinkedMultiValueMap<>();
        // 遍历资源路径
        while (urls.hasMoreElements()) {
            // 获取某个路径
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 加载文件内容,文件中是properties格式,key是接口名,value是实现类的名称以,隔开
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                // 获取key的 名称
                String factoryClassName = ((String) entry.getKey()).trim();
                // 将实现类字符串变成数组并遍历,然后添加到结果result中
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        // 缓存中放一份,下次再加载可以从缓存中读取
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

这个方法是利用ClassLoader加载classpath下的所有的/META-INF/spring.factories文件。注意:所有jar包都会被扫描和查找

例如,在spring-boot的jar包中,就有这样的文件
在这里插入图片描述
内容类似这样:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor

根据传入的接口名称,例如org.springframework.boot.env.PropertySourceLoader,就可以寻找到对应的实现类,例如:

org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

得到一个字符串集合并返回。
结束后,把得到的名字集合传递给createSpringFactoriesInstance方法,创建实例

2.2)createSpringFactoriesInstances创建实例

然后看看#createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) 方法,创建对象的代码:

/**
 * 根据类的全名称路径数组,创建对应的对象的数组
 *
 * @param type 父类类型
 * @param parameterTypes 构造方法的参数类型
 * @param classLoader 类加载器
 * @param args 构造方法参数
 * @param names 类全名称的数组
 */
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
		Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
		Set<String> names) {
    // 定义空实例集合
	List<T> instances = new ArrayList<>(names.size()); 
	// 遍历 names 数组
	for (String name : names) {
		try {
			// 获得类名称 name
			Class<?> instanceClass = ClassUtils.forName(name, classLoader);
			// 判断类是否实现自 type 类
			Assert.isAssignable(type, instanceClass);
			// 获得构造方法
			Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
			// 创建对象
			T instance = (T) BeanUtils.instantiateClass(constructor, args);
			instances.add(instance);
		} catch (Throwable ex) {
			throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
		}
	}
	return instances;
}

基本上就是利用反射根据类名称,获取类的字节码,然后创建对象

2.run方法

在完成SpringApplication对象初始化后,会调用其中的run方法:

public ConfigurableApplicationContext run(String... args) {
    // 1.计时器,记录springBoot启动耗时
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 2.配置headLess属性,这个跟AWT有关,忽略即可
    configureHeadlessProperty();
    // 3.获取SpringApplicationRunListener实例数组,默认获取的是EventPublishRunListener
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 启动监听
    listeners.starting();
    try {
        // 4.创建ApplicationArguments对象
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //5.加载属性配置。所有的environment的属性都会加载进来,包括 application.properties 和外部的属性配置
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 6.打印Banner
        Banner printedBanner = printBanner(environment);
        // 7.根据WebApplicationType,创建不同的ApplicationContext
        context = createApplicationContext();
        // 8.获取异常报告器
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                                                         new Class[] { ConfigurableApplicationContext.class }, context);
        // 9.调用各种初始化器的initialize方法,初始化容器
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        //重点 10.准备Bean工厂,调用一个BeanDefinition和BeanFactory的后处理器,初始化各种Bean,初始化tomcat
        refreshContext(context);
        // 11.执行初始化的后置逻辑,默认为空
        afterRefresh(context, applicationArguments);
        // 停止计时器
        stopWatch.stop();
        // 12.打印 Spring Boot 启动的时长日志
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 13.通知监听器,SpringBoot启动完成
        listeners.started(context);
        // 14.调用 ApplicationRunner的运行方法
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 通知监听器,SpringBoot正在运行
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

3.SpringBoot自动配置的原理

启动类的注解:@SpringBootApplication
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

最透彻的SpringBoot自动配置

@SpringBootApplication这个注解的源码:
点击进入,查看源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //标明该类为配置类
@EnableAutoConfiguration //启动自动配置功能
@ComponentScan(excludeFilters = { //包扫描器<context:component-scan base-package="com.xxx.xxx"/>
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) 
public @interface SpringBootApplication {

	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};
}

可以发现@SpringBootApplication上面又包含多个注解,重点的注解有3个:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

逐个来看。

1.@SpringBootConfiguration

org.springframework.boot.@SpringBootConfiguration 注解,标记这是一个 Spring Boot 配置类。代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

可以看到,它上面继承自 @Configuration 注解,所以两者功能也一致,都是标记一个类作为配置类。而配置类里面可以做各种Java配置,也就是说我们可以在我们的main函数所在的启动类中写入java配置。

2.@ComponentScan

我们跟进源码,核心代码是:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

	/**
	 * 标记需要扫描的包,与basePackages作用一样
	 */
	@AliasFor("basePackages")
	String[] value() default {};

	@AliasFor("value")
	String[] basePackages() default {};

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

	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	
	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	boolean useDefaultFilters() default true;


	Filter[] includeFilters() default {};


	Filter[] excludeFilters() default {};

	boolean lazyInit() default false;
	....................
}

类上的一段注释说明了这个注解的作用:

大概的意思:

配置组件扫描的指令。提供了类似与<context:component-scan>标签的作用

通过basePackageClasses或者basePackages属性来指定要扫描的包。如果没有指定这些属性,那么将从声明这个注解的类所在的包开始,扫描包及子包

而我们的@SpringBootApplication注解声明的类就是main函数所在的启动类,因此扫描的包是该类所在包及其子包。因此,一般启动类会放在一个比较前的包目录中

在这里插入图片描述

3.@EnableAutoConfiguration

关于这个注解,官网上有一段说明:

The second class-level annotation is @EnableAutoConfiguration. This annotation
tells Spring Boot to “guess” how you want to configure Spring, based on the jar
dependencies that you have added. Since spring-boot-starter-web added Tomcat
and Spring MVC, the auto-configuration assumes that you are developing a web
application and sets up Spring accordingly.

简单翻译以下:

第二级的注解@EnableAutoConfiguration,告诉SpringBoot基于你所添加的依赖,去“猜测”你想要如何配置Spring。比如我们引入了spring-boot-starter-web,而这个启动器中帮我们添加了tomcatSpringMVC的依赖。此时自动配置就知道你是要开发一个web应用,所以就帮你完成了web及SpringMVC的默认配置了!

总结,SpringBoot内部对大量的第三方库或Spring内部库进行了默认配置,这些配置默认并未生效

@EnableAutoConfiguration就像一个开关,或者一个启动者,它会让这些SpringBoot准备的默认配置生效。

那么问题来了:

  • 这些默认配置在哪里?
  • @EnableAutoConfiguration是如何找到这些配置的?
  • @EnableAutoConfiguration是如何从中筛选出想要启用的默认配置的?

4.@EnableAutoConfiguration

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

这个注解导入了一个新注解:
在这里插入图片描述

4.1.@Import

在这里插入图片描述
@Import注解的作用就是把一个或多个类导入到Spring容器中。不过,导入的方式多种多样:

  • 可以直接通过类名导入:@Import(User.class)就是把User这个类导入

  • 可以通过ImportSelector来导入。接口ImportSelector种有一个selectImports方法,它返回值是一个字符串数组,数组中的每一个元素分别代表一个将被导入的配置类的权限定名。

  • 通过 ImportBeanDefinitionRegistrar来导入一些bean,通过它,我们可以手动将多个BeanDefinition注册到IOC容器中,从而实现个性化的定制

我们可以看到在@EnableAutoConfiguration使用@Import注解时,传递的参数是:

在这里插入图片描述

可以看到参数名是:AutoConfigurationImportSelector。显然,这是一个ImportSelector相关的类,与上述第二种方式一致:
利用该特性我们可以给IOC容器动态的导入多个配置类
在这里插入图片描述

4.2.ImportSelector接口

在这里插入图片描述
基于AnnotationMetadata来导入多个@Configuration类型的类的名称数组并返回。也就是说这个方法就是去寻找Spring提供的默认配置类的。

那么AutoConfigurationImportSelector这个类肯定会实现这个方法,去加载Spring提供的默认配置。

4.3.AutoConfigurationImportSelector

在这里插入图片描述

1)selectImports

这个类实现了ImportSelector,其中会有selectImports方法:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		//加载默认配置
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

2)加载并过滤自动配置

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		// 1 判断是否开启。如未开启,返回空数组。
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// 2 获取annotationMetadata的注解@EnableAutoConfiguration的属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 3 从资源文件spring.factories中获取EnableAutoConfiguration对应的所有类 重点
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		// 3.1  移除重复的配置类
		configurations = removeDuplicates(configurations);
		// 4 获得需要排除的配置类
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 4.1 校验排除的配置类是否合法,没有则跳过检查
		checkExcludedClasses(configurations, exclusions);
		// 4.2 通过@EnableAutoConfiguration设置的exclude相关属性,从 configurations 中,移除需要排除的配置类
		configurations.removeAll(exclusions);
		// 5 根据条件(@ConditionalOn注解),过滤掉不符合条件的配置类
		configurations = getConfigurationClassFilter().filter(configurations);
		// 6 触发自动配置类引入完成的事件
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

重点关注的方法
在这里插入图片描述

3)getCandidateConfigurations 加载自动配置

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		// 利用SpringFactoriesLoader加载指定类型对应的类的全路径
		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;
	}

继续跟进

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

SpringFactoriesLoader
在这里插入图片描述

List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
``

这段代码的SpringFactoriesLoader.loadFactoryNames()方法我们已经见过一次了,它会去classpath下的/META-INF/spring.factories中寻找。

本例中是找以EnableAutoConfiguration为key的配置类的名称:在这里插入图片描述
所有的自动配置类加载完毕

4.4.加载到的配置如何筛选

刚才加载的所有自动配置类,都可以再spring-boot-autoconfigure包中找到这些自动配置类:
在这里插入图片描述
非常多,几乎涵盖了现在主流的开源框架,例如:

  • redis
  • jms
  • amqp
  • jdbc
  • jackson
  • mongodb
  • jpa
  • solr
  • elasticsearch

… 等等

我们来看一个我们熟悉的,例如SpringMVC,查看mvc 的自动配置类:在这里插入图片描述
打开WebMvcAutoConfiguration:在这里插入图片描述

//表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration(proxyBeanMethods = false)
//Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;这里是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(type = Type.SERVLET)
//判断当前项目有没有这个类Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

	public static final String DEFAULT_PREFIX = "";

	public static final String DEFAULT_SUFFIX = "";

	private static final String[] SERVLET_LOCATIONS = { "/" };

	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}

	@Bean
	@ConditionalOnMissingBean(FormContentFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
	public OrderedFormContentFilter formContentFilter() {
		return new OrderedFormContentFilter();
	}

	static String[] getResourceLocations(String[] staticLocations) {
		String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
		System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
		System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
		return locations;
	}
	........................
}

我们看到这个类上的4个注解:

  • @Configuration:声明这个类是一个配置类

  • @ConditionalOnWebApplication(type = Type.SERVLET)

    ConditionalOn,翻译就是在某个条件下,此处就是满足项目的类是是Type.SERVLET类型,也就是一个普通web工程,显然我们就是

  • @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })

这里的条件是OnClass,也就是满足以下类存在:Servlet、DispatcherServlet、WebMvcConfigurer,其中Servlet只要引入了tomcat依赖自然会有,后两个需要引入SpringMVC才会有。这里就是判断你是否引入了相关依赖,引入依赖后该条件成立,当前类的配置才会生效

4.SpringBoot学习总结

来总结下面试官问题的答案:

4.1.SpringBoot是做什么的?

SpringBoot是一个快速构建项目并简化项目配置的工具,内部集成了Tomcat及大多数第三方应用和Spring框架的默认配置。与我们学习的SpringMVC和Mybatis并无冲突,SpringBoot提供的这些默认配置,大大简化了SpringMVC、Mybatis等基于Spring的应用的开发。

4.2.SpringBoot自动装配是什么?解决了什么问题

自动装配简单的来说就是自动的把第三方组件的Bean装载到IOC容器里面,不需要开发人员再去写相关的配置。在SpringBoot里面只需要在启动类上去加上@SpringBootApplication注解就可以去实现自动装配。
在这里插入图片描述

4.2.SpringBoot的启动流程

SpringBoot项目启动第一步就是创建SpringApplication的实例,并且调用SpringApplication.run()这个方法。

创建SpringApplication实例主要完成三件事情:

  • 记录当前启动类字节码
  • 判断当前项目类型,普通Servlet、响应式WebFlux、NONE
  • 加载/META-INF/spring.factories文件,初始化ApplicationContextInitializer和ApplicationListener实例

而后的run()方法则会创建spring容器,流程如下:

  • 准备监听器,监听Spring启动的各个过程
  • 创建并配置环境参数Environment
  • 创建ApplicationContext
  • prepareContext():初始化ApplicationContext,准备运行环境
  • refreshContext(context):准备Bean工厂,调用一个BeanDefinition和BeanFactory的后处理器,初始化各种Bean,初始化tomcat
  • afterRefresh():拓展功能,目前为空
  • 发布容器初始化完毕的事件

4.3.SpringBoot自动配置原理

在这里插入图片描述
Spring Boot 自动配置机制的原理?

SpringBoot为我们提供了各种框架的默认配置,而默认配置生效的步骤如下:

  • @EnableAutoConfiguration开启自动配置,会去寻找classpath下的META-INF/spring.factories文件,读取其中以EnableAutoConfigurationkey的所有类的名称,这些类就是提前写好的自动配置类
  • 这些类都声明了@Configuration注解,并且通过@Bean注解提前配置了我们所需要的一切实例。完成自动配置
  • 但是,这些配置不一定生效,因为有@ConditionalOn注解,满足一定条件才会生效。比如条件之一:是一些相关的类要存在
  • 类要存在,我们只需要引入了相关依赖(starter),依赖有了条件成立,自动配置生效。
  • 如果我们自己配置了相关Bean,那么会覆盖默认的自动配置的Bean
  • 我们还可以通过配置application.yml文件,来覆盖自动配置中的属性

因此,使用SpringBoot自动配置的关键有两点:

1)启动器starter

要想自动配置生效,只需要引入依赖即可,而依赖版本我们也不用操心,因为只要引入了SpringBoot提供的stater(启动器),就会自动管理依赖及版本了。

因此,玩SpringBoot的第一件事情,就是找starter,SpringBoot提供了大量的默认starter

2)全局配置yml文件

另外,SpringBoot的默认配置,都会读取默认属性,而这些属性可以通过自定义application.yml文件来进行覆盖。这样虽然使用的还是默认配置,但是配置中的值改成了我们自定义的。

因此,玩SpringBoot的第二件事情,就是通过application.yaml来覆盖默认属性值,形成自定义配置。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/420741.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gradle环境搭建

目录 gradle是什么 gradle环境搭建 IDEA 配置 Gradle 创建 Gradle 项目 gradle是什么 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建开源工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置&#xff0c;也增加了基于Kotlin语言的kotlin-based …

Java基础总结(二)

文章目录一、ObjectObject中的成员方法&#xff08;11个&#xff09;toStringequalsclone二、Objects三、BigInteger和BigDecimaBigIntegerBigDecima四、正则表达式五、DateJDK7前时间相关类SimpleDateFormat类Calendar类JDK8新增时间相关类六、包装类一、Object 没有一个属性…

【密码算法 之十四】非对称算法,ECC椭圆曲线算法 之 ECDSA、ECDH、SM2、SM9等

文章目录1. ECC椭圆曲线1.1 曲线类型1.2 曲线标准1.3 表示方法1.4 曲线运算1.4.1 点加&#xff08;Point Addition&#xff09;1.4.2 点乘&#xff08;Point Multiplication&#xff09;1.4.3 倍点&#xff08;Point Double&#xff09;2. ECDSA2.1 私钥签名2.2 公钥验签3. ECD…

Java——旋转数组的最小数字

题目链接 牛客在线oj题——旋转数组的最小数字 题目描述 有一个长度为 n 的非降序数组&#xff0c;比如[1,2,3,4,5]&#xff0c;将它进行旋转&#xff0c;即把一个数组最开始的若干个元素搬到数组的末尾&#xff0c;变成一个旋转数组&#xff0c;比如变成了[3,4,5,1,2]&…

Stable Diffusion成为生产力工具(五):放大并修复老照片、马赛克照片、身份证件照

S&#xff1a;你安装stable diffusion就是为了看小姐姐么&#xff1f; I &#xff1a;当然不是&#xff0c;当然是为了公司的发展谋出路~~ 预先学习&#xff1a; 安装webui《Windows安装Stable Diffusion WebUI及问题解决记录》。运行使用时问题《Windows使用Stable Diffusion时…

Kubernetes 多集群管理工具Kuboard v3

目录 一、概述 二、安装和基本使用 2.1 添加k8s集群 2.2 信息查看 2.2.1概要信息查看 2.2.2导入集群的节点信息 2.2.3 存储 2.3创建工作负载 一、概述 Kuboard&#xff0c;是一款免费的 Kubernetes 图形化管理工具&#xff0c;Kuboard 力图帮助用户快速在 Kubernetes 上…

Tomcat8性能优化

文章目录授人以鱼不如授人以渔目的服务器资源Tomcat整体架构Tomcat配置优化Linux环境安装运行Tomcat8AJP连接什么是AJP执行器&#xff08;线程池&#xff09;3种运行模式bionioapr禁用DNS查询添加Listener版本号隐藏压缩传输部署测试用的web项目查看服务器信息查看Linux版本查看…

three.js学习 01-使用最基本的方法创建出来一个threejs立方体,three.js开发环境搭建

1.当前实现的最终效果&#xff1a; 我们将会在页面上使用threejs的渲染器创建场景和相机&#xff0c;并且将一个简单几何体结果的canvas嵌入到我们的网页中 2.环境以及工具介绍&#xff1a; three中文官方文档地址&#xff1a;https://www.three3d.cn/docs/index.html 使用的开…

制作两栏布局的 6+5 种方法:从相当合理到完全错误

一个挑战 假设您需要创建一个两列布局。是的&#xff0c;最简单的那种&#xff1a;左边一列&#xff0c;右边一列&#xff0c;中间有一些空隙。有一个明显的现代解决方案&#xff1a; .columns {display: grid;grid-template-columns: 1fr 1fr;gap: 20px; }完毕&#xff01;当…

Go 语言高质量编程

编写高质量的 Go 代码~ 前言&#xff1a; 本次课程简要介绍了高质量编程的定义和原则&#xff0c;分享了代码格式、注释、命名规范、控制流程、错误和异常处理五方面的常见编码规范&#xff0c;帮助我们在今后的开发过程中写出更加优秀的代码 … 什么是高质量编程&#xff1f…

凌恩生物文献分享|微刊:三代全长16s扩增子——环境多样性研究的明星

在微生物研究领域&#xff0c;PacBio三代全长的时代已经来临&#xff0c;如果你还没用过那就太可惜了&#xff01; 要问三代有什么好&#xff0c;那我可得说道说道。 相比于传统二代Illumina平台测序&#xff0c;PacBio Sequel lle 平台获得的序列更长&#xff0c;信息量更多…

Java Servlet Tomcat(HttpServlet)处理底层机制详解总括

以tomact服务器为例&#xff1a; 热知识&#xff1a;Servlet是java定义的处理动态资源&#xff08;非静态资源&#xff09;的java接口规范&#xff0c;HttpServlet是tomcat实现了servlet接口的类 一.当第一次发送请求时候&#xff1a; 1.查询web.xml中的url-parrtern中配置的…

LNMP及论坛搭建

安装 Nginx 服务 systemctl stop firewalld systemctl disable firewalld setenforce 01.安装依赖包 #nginx的配置及运行需要pcre、zlib等软件包的支持&#xff0c;因此需要安装这些软件的开发包&#xff0c;以便提供相应的库和头文件。 yum -y install pcre-devel zlib-deve…

排序(4)——归并排序

目录 前言 1.归并排序的递归实现 1.1 归并排序概念 1.2 归并排序递归实现 2.归并排序的非递归实现 前言 今天给大家带来比较排序的最后一种&#xff0c;归并排序&#xff0c;这个排序&#xff0c;需要我们对递归&#xff0c;循环控制要有着较强的理解&#xff0c;我相信大…

【iOS的NSNULL nil Nil 】

前言 偶然看到了NSNULL 简单了解他的兄弟nil Nil记录一下。 NSNULL Nil nil 在iOS中&#xff0c;nil、Nil和NSNull都表示“空值”的概念&#xff0c;但它们在使用时有所不同。 nil和Nil都表示空指针&#xff0c;可以用于指针类型的变量、对象类型的变量、和Objective-C对象…

浙江海發進出口股份有限公司官网上线|LTD五金技术行业案例分享

​浙江海發進出口股份有限公司 (以下简称海發)是一家多元化的国际贸易企业。拥有自己的工厂&#xff0c;稳定的资金储备和最好的服务&#xff0c;在商业领域赢得了很高的声誉。地处长江三角洲交通经济中心嘉兴市。 浙江海發進出口股份有限公司 (以下简称海發)是一家多元化的国…

Python轻量级Web框架Flask(5)——Flask模型基础和数据迁移

0、前言&#xff1a;学习这部分的前提是对python的面向对象有一定的了解&#xff0c;同时对MySQL有扎实的学习 1、Flask模型基础知识&#xff1a; Flask模型 ORM &#xff08;注意&#xff1a;在flask中用ORM可以实现SQL语句功能&#xff0c;但是并不意味着SQL语句不重要&am…

NISACTF2023 WP

NISACTF2023 WP 前言 2年多没玩CTF了&#xff0c;pwn显得手生了不少&#xff0c;我的PWN环境已经在硬盘的某个角落里吃灰了。今天参加了一场校赛&#xff0c;捣鼓了一下午&#xff0c;Reverse和PWN都AK了。其实比赛是新手向&#xff0c;没啥难度&#xff0c;不过有道PWN设计的…

ChatGPT实战100例 - (02) 自动出PPT它不香么?

文章目录ChatGPT实战100例 - (02) 自动出PPT它不香么&#xff1f;一、需求与思路1. 需求&#xff1a;出个PPT&#xff0c;5分钟后要用2. 思路&#xff1a;生成markdown然后转化二、生成markdown语法的思维导图1. 问题2. 回答三、把markdown文本转换成PPTChatGPT实战100例 - (02…

STM32 gpio外部中断详解

什么是中断&#xff1f; 打断CPU执行正常的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续运行&#xff0c;就叫中断 中断的作用和意义 中断的意义&#xff1a;高效处理紧急程序&#xff0c;不会一直占用CPU资源 STM32 GPIO外部中断简图 NVIC 什么…