springboot启动原理自动配置原理_月会的博客-程序员宅基地

技术标签: spring  spring boot  java  

1. SpringBoot自动加载原理

@SpringBootApplication
public class Application {
    

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

}

首先看@SpringBootApplication注解

@Target({
    ElementType.TYPE}) // 标记在类上
@Retention(RetentionPolicy.RUNTIME) // 作用于运行时
@Documented // 生成javadoc文档
@Inherited // 允许被继承
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {
    @Filter(
    type = FilterType.CUSTOM,
    classes = {
    TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {
    AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    
  // ...
}

其中包含了@SpringBootConfiguration和@EnableAutoConfiguration重要注解

@SpringBootConfiguration

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

该注解实际上起的还是@Configuration的作用,指定配置类

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

EnableAutoConfiguration中有两个注解,分别是

  • @AutoConfigurationPackage
  • @Import({AutoConfigurationImportSelector.class})

@AutoConfigurationPackage

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

这个注解的关键是@Import({Registrar.class}),@Import的作用是让Spring加载Registrar.class作为bean,那么看看Registrar中有什么

Registrar.class
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    
			register(registry, new PackageImport(metadata).getPackageName());
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
    
			return Collections.singleton(new PackageImport(metadata));
		}

	}

@Import加载的类如果实现了ImportBeanDefinitionRegistrar接口,那么就会调用该接口的方法并注册成bean,再看一下该方法做了什么

register(registry, new PackageImport(metadata).getPackageName());

在这段代码上打断点然后debug项目,可以看到getPackageName()最终返回的是整个项目启动类所在的包,即该类的作用就是扫描包下面的组件注册成bean

@Import({AutoConfigurationImportSelector.class})

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered{
    
      // ...
    }

@Import导入的类如果实现了DeferredImportSelector或ImportSelector接口,则会执行selectImports方法,看一下selectImports方法的内容

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
    
		if (!isEnabled(annotationMetadata)) {
    
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		// 跟到这一段里面去
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

这里重点看AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);这段代码,跟到方法里面去

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
    
		if (!isEnabled(annotationMetadata)) {
    
			return EMPTY_ENTRY;
		}
		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 = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

在跟到getCandidateConfigurations方法

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

此处有SpringFactoriesLoader.loadFactoryNames,这个方法比较关键了,跟进去

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sgPO2OOS-1646748601337)(/Users/zhangxing/Library/Application Support/typora-user-images/image-20220228230648913.png)]

可以看到自动配置实际上会加载jar包中的spring.factories文件

所以自动配置实际上分2种情况

  1. 启动类同包下以及子包下的所有组件都会进行扫描并按需加载(如@ConditionXXXX),通过@ComponentScan可以修改扫描位置
  2. 读取resource目录下的META-INF/spring.factories文件中配置的类型

2.SpringApplication.run()

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    
		return new SpringApplication(primarySources).run(args);
	}

这一句代码包含两步骤

  1. 创建SpringApplication的实例
  2. 使用实例调用run方法

创建SpringApplication实例

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  	// 推断web项目使用的mvc还是webflux,通过判断dispatcher的全限定名属于reactive包还是servlet包
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // bootstrap层的初始化,这里我们知道配置问价加载顺序是bootstrap>application,所以此处先初始化bootstrap
		this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
    // 初始化ApplicationContextInitializer,从spring.factories中加载
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 初始化ApplicationListener,从spring.factories中加载
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 通过栈 判断调用main方法的class
		this.mainApplicationClass = deduceMainApplicationClass();
	}

在这个构造方法中,由于容器还没有进行refresh方法(bean的初始化方法),所以可以通过实现ApplicationContextInitializer类来修改配置等上下文信息

run方法

public ConfigurableApplicationContext run(String... args) {
    
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
  // 获取SpringApplicationRunListener类型的启动监听器并实例化,从spring.factories中获取
		SpringApplicationRunListeners listeners = getRunListeners(args);
  // 发送启动事件
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
    
      // 准备环境,包括了环境变量、java环境、spring环境、配置文件配置
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
      // 打印banner
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
      // 准备上下文
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      // 刷新上下文
			refreshContext(context);
      // 预留刷新上下文后的扩展,默认没有实现
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
    
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
      // 发送已启动事件
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
    
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
    
      // 发送run事件
			listeners.running(context);
		}
		catch (Throwable ex) {
    
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
  1. 获取并启动监听器

    private SpringApplicationRunListeners getRunListeners(String[] args) {
          
    		Class<?>[] types = new Class<?>[] {
           SpringApplication.class, String[].class };
    		return new SpringApplicationRunListeners(logger,
    				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
    				this.applicationStartup);
    	}
    

    这里实际上找的是SpringApplicationRunListener类型的监听器,用于监听应用的启用

    监听器实际上最终使用的是spring的事件机制实现

  2. 构造应用上下文环境
    包括系统环境变量、java环境、spring环境、spring配置文件

  3. 初始化应用上下文
    通过SpringApplication实例化的时候对webType进行的推断,当webType为MVC类型的时候,创建的上下文实际上是AnnotationConfigServletWebServerApplicationContext
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQiSdlJh-1646748601338)(/Users/zhangxing/Library/Application Support/typora-user-images/image-20220301213421653.png)]
    AnnotationConfigServletWebServerApplicationContext实现了BeanFactory,具有IOC容器能力,其次这个类也是GenericApplicationContext的子类,GenericApplicationContext具备上下文能力,他使用一个BeanFactory字段保存了IOC容器,所以说,AnnotationConfigServletWebServerApplicationContext是上下文,并且持有了容器,对容器能力可以进行扩展

  4. 刷新应用上下文前的准备阶段

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
    			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
    			ApplicationArguments applicationArguments, Banner printedBanner) {
          
      // 设置环境参数
    		context.setEnvironment(environment);
      // 上下文创建好的后置处理
    		postProcessApplicationContext(context);
      // springapplication初始化时找到的initializers做调用
    		applyInitializers(context);
    		listeners.contextPrepared(context);
    		bootstrapContext.close(context);
    		if (this.logStartupInfo) {
          
    			logStartupInfo(context.getParent() == null);
    			logStartupProfileInfo(context);
    		}
    		// Add boot specific singleton beans
    		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    		if (printedBanner != null) {
          
    			beanFactory.registerSingleton("springBootBanner", printedBanner);
    		}
    		if (beanFactory instanceof DefaultListableBeanFactory) {
          
    			((DefaultListableBeanFactory) beanFactory)
    					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    		}
    		if (this.lazyInitialization) {
          
    			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    		}
    		// Load the sources
    		Set<Object> sources = getAllSources();
    		Assert.notEmpty(sources, "Sources must not be empty");
      // 这里是重点跟进去
    		load(context, sources.toArray(new Object[0]));
    		listeners.contextLoaded(context);
    	}
    

    上下文对象创建出来之后,给上下文设置环境参数,以及调用之前找到的initializer做调用,跟入load方法

    protected void load(ApplicationContext context, Object[] sources) {
          
    		if (logger.isDebugEnabled()) {
          
    			logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    		}
      // 创建bean定义的加载器,跟进去看如何创建的
    		BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
    		if (this.beanNameGenerator != null) {
          
    			loader.setBeanNameGenerator(this.beanNameGenerator);
    		}
    		if (this.resourceLoader != null) {
          
    			loader.setResourceLoader(this.resourceLoader);
    		}
    		if (this.environment != null) {
          
    			loader.setEnvironment(this.environment);
    		}
      // bean定义加载器执行加载
    		loader.load();
    	}
    
    BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
          
    		Assert.notNull(registry, "Registry must not be null");
    		Assert.notEmpty(sources, "Sources must not be empty");
    		this.sources = sources;
      // 注解读取器
    		this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
      // xml读取器
    		this.xmlReader = (XML_ENABLED ? new XmlBeanDefinitionReader(registry) : null);
    		this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null);
      // 包扫描读取器
    		this.scanner = new ClassPathBeanDefinitionScanner(registry);
      // 排除bean过滤器
    		this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
    	}
    

    说明会通过注解方式、包扫描方式和xml方式读取哪些地方配置了需要交给spring管理的bean,最终执行.load()方法,将这三种读取方式分别执行,拿到所有的bean定义,这里只是读取到这个项目一共有哪些bean定义,然后给保存起来,并不是说这些bean定义一定都要载入到上下文中去,至于为什么在瞎main的刷新上下文中讲解

    BeanDefinition其实就是bean的基本信息,比如这个bean是什么class类型的,bean是否是懒加载,这个bean是否是primary修饰的,这个bean是单例还是property(每次都创新对象)模式等等信息…

  5. 刷新应用上下文

    public void refresh() throws BeansException, IllegalStateException {
          
    		synchronized (this.startupShutdownMonitor) {
          
    			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
    
    			// Prepare this context for refreshing.
          // 刷新前的准备,主要校验了环境参数是否完整,比如某些必填配置没有写
    			prepareRefresh();
    
    			// Tell the subclass to refresh the internal bean factory.
          // 告诉子类刷新内部的beanFactory,其实里面没做啥事,主要就是刷新BeanFactory,获取BeanFactory,返回BeanFactory
    			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    			// 准备beanFactory,添加spel表达式的解析器、设置后置处理器等设置一堆东西
    			prepareBeanFactory(beanFactory);
    
    			try {
          
    				// Allows post-processing of the bean factory in context subclasses.
            // 添加bean的后置处理器,bean的后置处理器执行时机是beanDefinition创建之后,bean实例化之前,这样就拥有修改beanDefinition信息的能力
    				postProcessBeanFactory(beanFactory);
    
    				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
    				// Invoke factory processors registered as beans in the context.
            // 这里是springboot自动加载的入口,通过主类转换成的BeanDefinition,拿到basePackage,然后找到basePackage下的所有@Component,然后将beanDefinition加入到容器中
    				invokeBeanFactoryPostProcessors(beanFactory);
    
    				// Register bean processors that intercept bean creation.
    
    				registerBeanPostProcessors(beanFactory);
    				beanPostProcess.end();
    
    				// Initialize message source for this context.
    				initMessageSource();
    
    				// Initialize event multicaster for this context.
    				initApplicationEventMulticaster();
    
    				// Initialize other special beans in specific context subclasses.
    				onRefresh();
    
    				// Check for listener beans and register them.
    				registerListeners();
    
    				// Instantiate all remaining (non-lazy-init) singletons.
    				finishBeanFactoryInitialization(beanFactory);
    
    				// Last step: publish corresponding event.
    				finishRefresh();
    			}
    
    			catch (BeansException ex) {
          
    				if (logger.isWarnEnabled()) {
          
    					logger.warn("Exception encountered during context initialization - " +
    							"cancelling refresh attempt: " + ex);
    				}
    
    				// Destroy already created singletons to avoid dangling resources.
    				destroyBeans();
    
    				// Reset 'active' flag.
    				cancelRefresh(ex);
    
    				// Propagate exception to caller.
    				throw ex;
    			}
    
    			finally {
          
    				// Reset common introspection caches in Spring's core, since we
    				// might not ever need metadata for singleton beans anymore...
    				resetCommonCaches();
    				contextRefresh.end();
    			}
    		}
    	}
    
  6. 刷新应用上下文后的扩展接口
    方便扩展

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq1010830256/article/details/123364681

智能推荐

广电国家标准对高清视频服务器播出文件的码率规定是多少,常用视频标准尺寸和码率..._铁关公女孩的博客-程序员宅基地

《常用视频标准尺寸和码率》由会员分享,可在线阅读,更多相关《常用视频标准尺寸和码率(1页珍藏版)》请在人人文库网上搜索。1、常用视频标准尺寸和码率一:4K高清电视分辨率 4096 x 2304,像素达到800万是HD1080P的4倍。码率为40M-65M,一分钟的4K视频输出MP4格式40M码率 大概是800M-1G左右/分钟。真正意义上的4K视频需要4K摄像机拍摄.二:蓝光高清分辨率一般为192...

linux 安装git错误:libcurl.so.3_一个人的场域的博客-程序员宅基地

执行sudo yum install -y git后出错:Error: Package: git-1.7.12.4-1.el5.rf.x86_64 (dag)           Requires: libcurl.so.3()(64bit)           Available: curl-7.15.5-17.el5_9.x86_64 (base)             

【Linux】关于理解fork()函数的简单例子_Vincent's Blog的博客-程序员宅基地

1.fork()函数fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,这个新产生的进程称为子进程。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。需要注意的一点:就是调用fork函数之后,一定是两个进程同时执行的代码段是fork函数之后的代码,而之前

Amazon Dynamo论文中文版_点火三周的博客-程序员宅基地

原著: Werner Vogels 翻译: quest.run (翻译本文,旨在讨论NoSQL时会有一个共同的Terminology,对于要实现NoSQL方案的同学,建议啃啃原文,因为很多术语在翻译成中文后语义差异很大,如quorum, replica/replication, read-repair, anti-entropy, partition/partitionin

layui 鼠标移入变为小手_layui实现鼠标移动到单元格上显示数据的方法_weixin_39748928的博客-程序员宅基地

layui实现鼠标移动到单元格上显示数据的方法如下所示:{field : 'operNm',//title是纯文本title : '用户姓名',width : 150,align : 'center',//下面的是从数据库里取出来的数据templet:'{{d.year}}'//判空/*templet:'{{# if(d.operNm!=undefined){ }}{{d.operNm}}{{# ...

解决ubuntu14.04 samba服务出错_segment-fault的博客-程序员宅基地

ubuntu版本号:14.04.1确定ubuntu安装版本 cat /etc/[email protected]:/home/work/tutorial_ffmpeg# cat /etc/issueUbuntu 14.04.1 LTS \n \lubuntu14.04在使用samba的过程中可能会出现无法访问samba服务器的情况,具体表现为安装时没有出现问题,在window

随便推点

给oracle用户查询其它用户表的权限_weixin_34049948的博客-程序员宅基地

1、创建用户user1create user user1identified by xxxxdefault tablespace XXXX_tabletemporary tablespace tempprofile defaultgrant connect to user1;2、方式一:批量表赋权SELECT 'grant select on PPOSUAT_17.'|| ...

定制属于自己的自动化安装的linux系统镜像_weixin_30307267的博客-程序员宅基地

使用软件和平台1、基于平台:Vmwareworkstation8.02、基于系统镜像:rhel-server-5.8-i386-dvd.iso3、ISO编辑软件:UltraISO4、自定义配置文件:ks.cfg5、需要重新编辑的配置文件:...

ARM指令集详解(超详细!带实例!)_dxmcu的博客-程序员宅基地_arm指令集

算术和逻辑指令ADC :带进位的加法(Addition withCarry)ADC{条件}{S} , ,                 dest = op_1 + op_2 + carryADC将把两个操作数加起来,并把结果放置到目的寄存器中。它使用一个进位标志位,这样就可以做比 32位大的加法。下列例子将加两个 128位的数。128 位结果:

PHP与HTML实现数据的分页显示的页码具体实现分析_风云小虾米的博客-程序员宅基地_php分页页码动态的实现

PHP与HTML实现数据的分页显示一、分页样式与结果二、PHP函数分析1、数据库查询操作函数分析2、数据表格显示函数分析三、HTML部分的逻辑划分分析1、分页类型判断2、分页页码不足3、页码溢出一、分页样式与结果  分页的页码总共显示10个,如果需要分的页超过10个就将显示2~11,3~12…,15~24等依次类推,界面依旧显示10个可操作页码。  具体的CSS样式,参考Bootstraphttps://v3.bootcss.com/components/二、PHP函数分析1、数据库查询

org.springframework.beans.factory.BeanDefinitionStoreExcepti_lkyyy喔的博客-程序员宅基地

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [H:\IDEAPROJECT\mybatis_plus\target\classes\com\lkyyy\MybatisPlusApplication.class]; nested exception is java.lang.annotation.AnnotationFormatErr

修改RK3399系统一些常用命令_工程师丶佛爷的博客-程序员宅基地

目录前言修改横线屏方法隐藏导航栏隐藏状态栏前言最近有一个项目和硬件打交道比较多,比如要修改安卓板子的分辨率以及横竖屏等操作,在这里记录一下。修改横线屏方法第一步使用Type-C连接PC与板子,在PC上使用adb 拉取到项目中,如图:adb pull /system/build.prop第二步: 修改 build.propa) 修改 ro.sf.hwrotation=90b) 增加 sys.hwc.device.primar...

推荐文章

热门文章

相关标签