Java知识体系SpringBoot启动原理探究,源码解读

2022-09-03 14:29:27

大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨‍🌾!

||To Up||

未来村村长正推出一系列【To Up】文章,该系列文章重要是对Java开发知识体系的梳理,关注底层原理和知识重点。”天下苦八股文久矣?吾甚哀,若学而作苦,此门无缘,望去之。“该系列与八股文不同,重点在于对知识体系的构建和原理的探究。

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can
“just run”.

Spring被称为 J2EE的春天,是一个开源的轻量级的Java开发框架,具有控制反转(IOC)面向切面(AOP)两大核心。但是使用Spring开发,我们还是需要配置Beans.xml等各种文件,如果是开发Web应用,我们还需要配置web.xml、更多的Beans.xml以及Tomcat服务器,其中存在许多固定的配置套路。

SpringBoot便是为了简化Java开发流程而诞生的。根据官方介绍,Spring Boot使您可以轻松地创建独立的、基于产品级的Spring应用程序,你只需要"run"即可。这里的run就是SpringApplication的run()方法,这是整个SpringBoot项目的启动入口,也是SpringBoot简化开发的思想体现。

importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublicclass xxxApplication{publicstaticvoidmain(String[] args){SpringApplication.run(xxxApplication.class, args);}}

我们将从这个Main类为出发点,分析SpringBoot的自动配置原理,设计框架,启动流程等核心原理。

一、自动配置

1、底层注解解析

在讲解xxxApplication的核心注解前,得先回顾或学习新的注解@Configuration、@Import、@Conditional。

(1)@Configuration

@Configuration:作用在类上,告诉SpringBoot这是一个配置类,相当于Spring中的xml配置文件(可替换xml配置文件),本质上也是一个Component。

@Bean:作用在方法上,用于创建一个Bean对象,该Bean对象会在IoC容器启动时初始化。给容器中添加组件,相当于Spring中xml配置文件中的<bean>标签。这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

@Component: 标注Spring管理的Bean,使用@Component注解在一个类上,表示将此类标记为Spring容器中的一个Bean。

SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理,用于容器初始化时通过依赖注入生成相应的Bean对象。

我们来看@Configuration的代码,我们知道Configuration实际上也是一个Component注解,该注解的属性proxyBeanMethods()的属性默认为true,意为是否通过CGLIB代理@Bean方法以强制执行bean的生命周期行为,被代理的bean将是单例化创建,即容器中只存在一个bean。虽然在Component下使用@Bean进行Bean的注册也是默认单例,但是该注册方法不会通过代理完成。

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic@interfaceConfiguration{@AliasFor(
        annotation=Component.class)Stringvalue()default"";booleanproxyBeanMethods()defaulttrue;}
  • @Configuration(proxyBeanMethods = true):Full模式(全模式),保证每个@Bean方法被调用多少次返回的组件都是单实例的
  • @Configuration(proxyBeanMethods = false):Lite模式(轻量级模式),每个@Bean方法被调用多少次返回的组件都是新创建的

(2)@Import

Provides functionality equivalent to the {@code } element in Spring XML.Allows for importing {@code @Configuration} classes, {@link ImportSelector} and{@link ImportBeanDefinitionRegistrar} implementations, as well as regular component classes (as of 4.2; analogous to {@link AnnotationConfigApplicationContext#register}).

Import注解对应的是配置文件中的<import>标签,在Spring的beans.xml文件中,我们可以使用import将不同的beans文件导入到一个文件中。

<importresource="bean1.xml"/>
①@Configuration类或普通类

@Import注解可以实现@Configuration类,ImportSelector和ImportBeanDefinitionRegistrar接口的实现类的导入。在Spring版本4.2以后,Import也可以导入普通类,将其注册成为Spring Bean。Import导入@Configuration类,如同将不同的beans文件导入到一个文件中。

②ImportBeanDefinitionRegistrar接口的实现类

导入ImportBeanDefinitionRegistrar接口的实现类主要用于手动注册bean到容器。

publicclassMyBeanimplementsImportBeanDefinitionRegistrar{@OverridepublicvoidregisterBeanDefinitions(AnnotationMetadata annotationMetadata,BeanDefinitionRegistry beanDefinitionRegistry){//指定bean定义信息(包括bean的类型、作用域等)RootBeanDefinition rootBeanDefinition=newRootBeanDefinition(xxxClass.class);//注册一个bean指定bean名字(id)
        beanDefinitionRegistry.registerBeanDefinition("TestDemo4444",rootBeanDefinition);}}
③ImportSelector接口实现类

ImportSelector是配置类导入选择器,ImportSelector接口源码如下。ImportSelector决定可以引入哪些@Configuration类。该接口提供了selectImports方法,该方法可根据具体实现决定返回哪些配置类的全限定名,结果以字符串数组返回。

当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。

publicinterfaceImportSelector{String[]selectImports(AnnotationMetadata importingClassMetadata);}

一个ImportSelector实现类通常也可能会实现各种Aware接口,如果实现了这些Aware接口,这些接口方法的调用会发生在selectImports之前。比如:EnvironmentAware、BeanFactoryAware、BeanClassLoaderAware、ResourceLoaderAware。

(3)@Conditional

@Conditional(Conditions):根据是否满足某个特定的条件创建一个特定的Bean。

@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceConditional{/**
	 * All {@link Condition} classes that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */Class<?extendsCondition>[]value();}

除Conditional以外,Spring还提供了以下指定条件类型的衍生Conditional:

  • @ConditionalOnClass:该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类;
  • @ConditionalOnMissingBean:该注解表示,如果存在它修饰的类的bean,则不需要再创建这个bean;
  • @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean;
  • @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean;
  • @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean;

2、自动配置核心注解

绕了一大圈,我们再来看启动类的注解。

@SpringBootApplication

该注解是一个组合注解,可以用以下注解进行代替。

@ComponentScan@SpringBootConfiguration@EnableAutoConfiguration

(1)@ComponentScan

用来指定包的扫描范围,加了包扫描@ComponentScan注解后,只要标注了@Controller、@Service、@Repository、@Component注解中的任何一个,其组件都会被自动扫描,加入到容器中。

(2)@SpringBootConfiguration

SpringBootConfiguration也是@Configuration注解,说明被注解的类是一个配置类。当我们使用@SpringBootConfiguration标记一个类时,这意味着该类提供了@Bean定义方法。 Spring容器处理配置类以为我们的应用实例化和配置bean。

(3)@EnableAutoConfiguration

@EnableAutoConfiguration的主要功能是启动Spring应用程序上下文时进行自动配置,它会尝试猜测并配置项目可能需要的Bean。自动配置通常基于项目classpath中引入的类和已定义的Bean来实现的,被自动配置的组件来自项目依赖的jar包。

详情见下节。

3、EnableAutoConfiguration核心原理

(1)@EnableAutoConfiguration

@EnableAutoConfiguration源码如下,其关键功能是通过@Import注解导入的ImportSelector来完成的,是自动配置的核心实现。

//启用SpringApplicationContext的自动配置,尝试猜测和配置您可能需要的bean。@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public@interfaceEnableAutoConfiguration{String ENABLED_OVERRIDE_PROPERTY="spring.boot.enableautoconfiguration";//根据类排除指定的自动配置Class<?>[]exclude()default{};//根据类名排除指定的自动配置String[]excludeName()default{};}

(2)AutoConfigurationImportSelector

从其继承的接口我们可以看到,除了继承DeferredImportSelector接口以外,还继承了一系列的Aware接口。

publicclassAutoConfigurationImportSelectorimplementsDeferredImportSelector,BeanClassLoaderAware,ResourceLoaderAware,BeanFactoryAware,EnvironmentAware,Ordered{}

当使用Import注解引入AutoConfigurationImportSelector类时,其selectImport()方法会被调用执行其实现的自动装配逻辑。在这之前,会先调用实现的Aware接口的方法。selectImports是ImportSelector接口的唯一函数方法。

publicString[]selectImports(AnnotationMetadata annotationMetadata){//检查自动配置功能是否开启if(!isEnabled(annotationMetadata)){return NO_IMPORTS;}//封装被引入的自动配置信息AutoConfigurationEntry autoConfigurationEntry=getAutoConfigurationEntry(annotationMetadata);//返回满足条件的配置类的全限定名数组returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}

selectImports中又调用了getAutoConfigurationEntry()方法,给容器批量导入相应的组件。

protectedAutoConfigurationEntrygetAutoConfigurationEntry(AnnotationMetadata annotationMetadata){if(!isEnabled(annotationMetadata)){return EMPTY_ENTRY;}AnnotationAttributes attributes=getAttributes(annotationMetadata);//加载META-INF目录下的spting.factories文件中的自动配置类List<String> configurations=getCandidateConfigurations(annotationMetadata, attributes);//对获得的类进行去重处理
		configurations=removeDuplicates(configurations);//获得注解中被exclude和excludeName所排除的类集合Set<String> exclusions=getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);//从自动配置类集合中去除被排除的类
		configurations.removeAll(exclusions);//将筛选完成的配置类和排查的配置类构建为事件类,并传入监听器
		configurations=getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);returnnewAutoConfigurationEntry(configurations, exclusions);}

getCandidateConfigurations()方法通过SpringFactoriesLoader的loadFactoryNames()方法加载类路径中META-INF目录下spring.factories文件中针对EnableAutoConfiguration的注册配置类。

protectedList<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;}

(3)SpringFactoriesLoader

publicstaticList<String>loadFactoryNames(Class<?> factoryType,@NullableClassLoader classLoader){ClassLoader classLoaderToUse= classLoader;if(classLoaderToUse==null){
			classLoaderToUse=SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName= factoryType.getName();returnloadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName,Collections.emptyList());}

SpringFactoriesLoader中的loadFactoryNames()方法又会调用loadSpringFactories()方法,加载得到所有的组件。该方法调用getResources()方法传入参数FACTORIES_RESOURCE_LOCATION ,该参数是字符串常量其值为**“META-INF/spring.factories”**。key为接口的全类名,value是对应配置值的List集合。

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

		result=newHashMap<>();try{//获取资源文件的位置Enumeration<URL> urls= classLoader.getResources(FACTORIES_RESOURCE_LOCATION);while(urls.hasMoreElements()){URL url= urls.nextElement();UrlResource resource=newUrlResource(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->newArrayList<>()).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){thrownewIllegalArgumentException("Unable to load factories from location ["+
					FACTORIES_RESOURCE_LOCATION+"]", ex);}return result;}

虽然127个组件自动配置启动的时候默认全部加载,但按照条件装配规则(@Conditional及其衍生注解),最终会按需配置,例如AOP组件中有@ConditionalOnProperty、@ConditionalOnClass、@ConditionalOnMissingClass等条件注解,其中@ConditionalOnClass(Advice.class)就是判断是否存在Advice类,如果没有则对应组件不会被加载。

@Configuration(proxyBeanMethods=false)@ConditionalOnProperty(prefix="spring.aop", name="auto", havingValue="true", matchIfMissing=true)publicclassAopAutoConfiguration{@Configuration(proxyBeanMethods=false)@ConditionalOnClass(Advice.class)staticclassAspectJAutoProxyingConfiguration{@Configuration(proxyBeanMethods=false)@EnableAspectJAutoProxy(proxyTargetClass=false)@ConditionalOnProperty(prefix="spring.aop", name="proxy-target-class", havingValue="false")staticclassJdkDynamicAutoProxyConfiguration{}@Configuration(proxyBeanMethods=false)@EnableAspectJAutoProxy(proxyTargetClass=true)@ConditionalOnProperty(prefix="spring.aop", name="proxy-target-class", havingValue="true",
				matchIfMissing=true)staticclassCglibAutoProxyConfiguration{}}@Configuration(proxyBeanMethods=false)@ConditionalOnMissingClass("org.aspectj.weaver.Advice")@ConditionalOnProperty(prefix="spring.aop", name="proxy-target-class", havingValue="true",
			matchIfMissing=true)staticclassClassProxyingConfiguration{@BeanstaticBeanFactoryPostProcessorforceAutoProxyCreatorToUseClassProxying(){return(beanFactory)->{if(beanFactoryinstanceofBeanDefinitionRegistry){BeanDefinitionRegistry registry=(BeanDefinitionRegistry) beanFactory;AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}};}}}

4、自动配置流程总结

(1)注解组成

在这里插入图片描述

@SpringBootApplication注解由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三者组成。

  • @SpringBootConfiguration说明该类是一个配置类,类似于一个xml配置文件。
  • @ComponentScan用于包的扫描,其扫描带有@Component、@Controller、@Repository、@Service的类,将其注册为Bean。
  • @EnableAutoConfiguration是实现自动配置的核心注解,其加载META-INF下spring.factories中注册的各种AutoConfiguration类,当该AutoConfiguration类满足相应的@conditional及其衍生注解的条件时,才会实例化该AutoConfigutation中定义的Bean,并注入到IoC容器中。

(2)自动配置流程

在这里插入图片描述

  • @EnableAutoConfiguration注解的核心注解是Import注解,其传入ImportSelector实现类时会调用该实现类的selectImports方法,该方法会调用getAutoConfigutationEntry()方法。
  • 在这方法中通过调用SpringFactoriesLoader的LoadFactoryNames()方法,该方法又调用了loadSpringFactories()方法去加载类路径中META-INF目录下的spring.factories文件中的自动配置类。
  • getAutoConfigutationEntry()对获得的配置类通过exculde或exculdeName方法进行排除,去除被排除的类。
  • 最终被加载的类又会经过@conditional及其衍生注解的条件筛选才实例化相应的Bean,并注入到IoC容器中

二、SpringApplication实例化

Class that can be used to bootstrap and launch a Spring application from a Java main method.

说完了Main类的注解,我们就得看Main方法中的短短一行代码是如何执行的了。

SpringApplication.run(xxxApplication.class, args);

我们来看run方法的一系列调用情况,我们发现之前的run方法绕了个圈子。

publicstaticConfigurableApplicationContextrun(Class<?> primarySource,String... args){returnrun(newClass<?>[]{ primarySource}, args);}publicstaticConfigurableApplicationContextrun(Class<?>[] primarySources,String[] args){returnnewSpringApplication(primarySources).run(args);}

所以代码我们可以写成下列这种形式,我们可以看到实际上原来的代码分为两步执行,先传入primarySources参数即带有@SpringBootApplication注解的xxxApplication.class对SpringApplication进行实例化,再调用run方法来启动程序。

newSpringApplication(xxxApplication.class).run(args);

SpringApplication的作用就是该节开头引入的那句官方注解,用于从Main方法引导和启动Spring应用程序,具体的启动方法就是run方法。

1、构造方法

我们通过SpringApplication构造方法就能知道相应的实例化流程。

publicSpringApplication(Class<?>... primarySources){this(null, primarySources);}@SuppressWarnings({"unchecked","rawtypes"})publicSpringApplication(ResourceLoader resourceLoader,Class<?>... primarySources){this.resourceLoader= resourceLoader;Assert
  • 作者:未来村村长
  • 原文链接:https://blog.csdn.net/apple_51976307/article/details/124865615
    更新时间:2022-09-03 14:29:27