springboot启动原理自动配置原理_defaultbootstrapcontext和configurableapplicationcon-程序员宅基地

技术标签: 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

智能推荐

Linux下PHP开启Oracle支持(oci8)-程序员宅基地

使用php的常见问题是:编译php时忘记加入某扩展,后来想加入扩展,可是由于安装php后又装了一些东西如PEAR等,不想删除文件夹重装,那么此时就须要自己又一次添加某模块支持了,Linux操作系统下能够用phpize给PHP动态加入扩展.下面就以扩展模块 oci8为例(php连接orac...

这里有一个保存用户账户信息的字典,请用程序模拟系统的登录验证过程_请用程序实现学校某系统的登录验证流程-程序员宅基地

请用程序实现用键盘模拟用户输入,判断输入的用户名或密码是否正确,并输出登录信息。如果输入的用户名存在,且密码正确,则输出success如果输入的用户名存在,但密码不正确,则输出password error如果输入的用户名不存在,则输出not found输入格式分两行输入,第一行为用户名,第二行为密码。示例输入zhangsan123456输出success思路:第一步:先判断username是否在users字典中存在。if username not in users,如果成立,则_请用程序实现学校某系统的登录验证流程

linux 内核rt,实时操作系统kernel rt_笥課鸴煕的博客-程序员宅基地

https://blog.csdn.net/baidu_34045013/article/details/78886617实时应用程序在某些触发事件和应用程序对该事件的响应之间有操作截止日期。为了满足这些操作期限,程序员使用实时操作系统(RTOS),在该系统上可以可靠地计算或测量给定应用程序和环境的最大响应时间。典型的RTOS使用优先级。需要CPU的最高优先级任务总是在事件唤醒任务之后的固定时间内..._linux kernel rd rt

狂神说smbms项目(完整)_smbms项目spring框架-程序员宅基地

resmbms浏览器项目中出现乱码问题:配置tomcat启动参数-Dfile.encoding=UTF-8技术亮点 可以使用EL表达式将请求信息提取到登录页面 使用json实现前后端分离 使用ajax更新部分网页技术 对增删改需要启动事务处理,对事务的特性有更清晰的认识 可以使用StringBuffer实现字符串拼接,以及使用HashMap封装键值对参数传递到前端,使用List集合将多个User类封装保存,可以使用list集合拼接参数,使用Obje_smbms项目spring框架

详解:网络虚拟化卸载加速技术的演进-程序员宅基地

虚拟化技术以牺牲部分效率为代价提升了资源的使用率,将原来需要硬件完成的工作,通过软件模拟的方式,满足多个云租户的需要。随着DPU的出现,网络虚拟化可以offload到DPU中,实现网络功能的卸载,在提升效率的同时,减轻对CPU的占用。越来越多的硬件厂商开始原生支持virtio协议,将虚拟化功能Offload到硬件上,把嵌入式CPU集成到SmartNIC中,网卡处理所有网络中的数据,而嵌入式CPU控制路径的初始化并处理异常情况。在virtio的通用化实现方式中,有两个非常重要的模块:KVM和QEMU。

如何解决加载动态链接库DLL失败,返回0,GetLastError返回错误码126-程序员宅基地

通常情况下使用LoadLibrary加载DLL都可以成功,但是当被加载的DLL内部依赖其他DLL无法被找到时,该函数会返回126(ERROR_MOD_NOT_FOUND)错误。解决办法有2种:1)使用depends查看DLL的依赖项,查看有警告的即为不存在的,这时候可以通过查找下载等方式补齐2)基于第1种方法的结果,假如发现所有DLL的依赖项都存在,那很有可能就是加载DLL的程序和DL...

随便推点

SUSE linux相关-程序员宅基地

1、启动suse linux的vsftpd服务,但是FTP连接不上 解决办法: 修改/etc/vsftpd.conf文件 使local_enable=YES配置生效 修改/etc/ftpusers文件 把其中的root用户注释掉(注意其中还有oracle用户,安装oracle时需注意使用oracle上传文件前必须先修改此文件) 2、防火墙关闭不了 解决办法...

移动办公不是梦!最高效的移动驾驶舱来袭!_移动端驾驶舱案例-程序员宅基地

疫情时期,在家办公成为迫于无奈的举措,但不少人却爱上了这种模式。后疫情时代,“移动办公”或许会成为未来工作模式的流行趋势。其实,“移动办公”也在一点一滴地融入我们的生活。比如在很多企业中,许多管理层都十分喜欢炫酷漂亮的可视化大屏,一个超大屏幕可以让管理者实现“一屏”了解公司发展与治理的方方面面。走出了公司,管理者就不得不与数据脱节,无法随时随地了解企业数据的变化。因此移动驾驶舱应运而生,从不可移动的电脑“大屏”,到可移动的手机“小屏”,工作内容正在被“浓缩”和“共享”,工作形式变得多样化,工作变得更实时、_移动端驾驶舱案例

Nginx日志配置远程Syslog采集_nginx配置syslog 需要安装syslog吗-程序员宅基地

本文将指引你:如何对Nginx日志进行采集,并通过Syslog协议,自动实时的发送到远程的集中日志分析中心,便于集中式的日志存储和管理,提高网站的运维效率。第一步:初始化日志采集环境先确保系统中的/var/spool/rsyslog 目录已存在:mkdir -v /var/spool/rsyslogif [ "$(grep Ubuntu /etc/issue)" != "" ]_nginx配置syslog 需要安装syslog吗

算法面试题汇总 leetcode_ligtgb 算法面试题目-程序员宅基地

算法面试题汇总 leetcode_ligtgb 算法面试题目

面试问题-程序员宅基地

说说你对闭包的理解使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。闭包有三个特性:1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收请你谈谈Cookie的弊端cookie虽然在持久保存客户端数据提供了方便,分担了服务器存储的...

推荐文章

热门文章

相关标签