Spring源码的分析之启动流程

一.前言

这篇文章的话就是我个人通过一些技术博客以及自己写一些Demo测试获得的一些感悟但是

由于本人的技术水平有限所以肯定就是会出现一些问题所以希望看这篇文章的时候如果发现错误的时候可以提出来然后我个人的话进行修改

二.SpringApplication 的构造函数

创建的一个简单的Spring的项目:

java 复制代码
@SpringBootApplication
public class SpringSimpleDemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringSimpleDemoApplication.class, args);
        Object testBean =context.getBean("testBean");
        System.out.println(testBean);
    }

}

Run方法的执行: 这个就是说先创建一个SpringApplication的对象然后调用这个对象里面的run方法

通过查看SpringApplication的构造函数:这个就是说通过进行一些准备工作如加载文件中的内容以及获得监听器

java 复制代码
	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环境、web环境、reactive环境三种。其判断逻辑就是判断是否存在指定的类,默认是Servlet 环境
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		// getSpringFactoriesInstances 方法加载了 spring.factories文件。在这里进行了首次加载spring.factoies文件。设置 ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 获取监听器,也加载了spring.factories文件
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 设置启动类信息
		this.mainApplicationClass = deduceMainApplicationClass();
	}

run方法的流程:

java 复制代码
public ConfigurableApplicationContext run(String... args) {
         //进行开始时间的记录
		long startTime = System.nanoTime();
        //这个就是创建一个上下文的对象用于存储和管理启动时所需的资源和配置信息
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
        //这个就是获得系统的资源以及配置 通过system.peoperty方法
		configureHeadlessProperty();
        //获得监听器就是从META-INF/spring.factories文件中进行获得
		SpringApplicationRunListeners listeners = getRunListeners(args);
        //开启监听器
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
            //这个主要就是命令行参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //环境的封装
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            //配置BeanInfo的忽略
			configureIgnoreBeanInfo(environment);
            //这个就是打印信息的对象
			Banner printedBanner = printBanner(environment);
            //创建上下文对象
			context = createApplicationContext();
            //设置启动的信息
			context.setApplicationStartup(this.applicationStartup);
            //这个就是配置上下文需要的环境配置信息
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            //这个就是进行刷新上下文
			refreshContext(context);
           //这个刷新完成之后进行一些后置的处理
			afterRefresh(context, applicationArguments);
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}
             //开启监听器
			listeners.started(context, timeTakenToStartup);
            //调用运行器执行必要的初始化任务或者是一些命令行操作
			callRunners(context, applicationArguments);
		}
        //失败的处理
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
            //准备时间的计算
			Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
		     //通知监听器启动完成
          	listeners.ready(context, timeTakenToReady);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

三.run方法具体的实现的步骤(重点)

1.获得监听器:

这个主要就是从 spring.factories 文件中获取监听器集合,当有事件发生时调用监听器对应事件的方法。下面就就就是代码的详情

java 复制代码
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);
	}


//当中实现的方法
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

这个从上两张图片可以看出来getSpringFactoriesInstances返回的是一个Collection类型的返回值,然后我们可以通过看下图就可以发现SpringApplicationRunListeners不是说是一个监听器而是说是一个监听器的集合

spring.factories:

位置就是在依赖第三包中的META-INF目录之下,里面的内容就是监听器这个我们打开其他jar包的目录都会看到的所以这个就是一个外界的资源

2.准备一些环境:

  //环境的封装
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

具体代码的实现:

java 复制代码
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
        //这个就是获得以及配置环境就是根据不同的环境
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        //进行一个环境的封装配置多个数据源以及加载 如根据一些命令决定加载的是dev文件还是说其他
        //的配置文件 就是基本的状态也就是确定大致的轮廓
		configureEnvironment(environment, applicationArguments.getSourceArgs());
        //这个就是更加的细分化了将这个些数据源或者配置文件中的内容配置到相对应的组件中
        //如配置文件中的数据库的密码 网址 就会配置到数据库组件当中
        ConfigurationPropertySources.attach(environment);
        //发布相关的事件
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = convertEnvironment(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

不同的环境创建不同的环境默认的情况下就是ApplicationEnvironment

3.创建上下文

context = createApplicationContext();

细分化看: 默认的情况下就是创建annotationapplicationcontext

java 复制代码
//这个就是根据webApplicationType创建上下文
protected ConfigurableApplicationContext createApplicationContext() {
		return this.applicationContextFactory.create(this.webApplicationType);
	}

4.上下文的准备工作:

作用:准备和初始化 ApplicationContext,包括设置环境、应用初始化器、注册监听器、注册单例Bean(这个就是将符合条件的类注册为BeanDefinition然后要通过BeanFactory进行创建)等

java 复制代码
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
	
	// 设置环境
	context.setEnvironment(environment);
	// 后处理应用程序上下文
	postProcessApplicationContext(context);
	// 应用初始化器
	applyInitializers(context);
	// 触发监听器的 contextPrepared 方法
	listeners.contextPrepared(context);
	// 关闭 BootstrapContext
	bootstrapContext.close(context);
	// 记录启动信息
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// 注册 Boot 特定的单例 Bean
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	// 配置 BeanFactory
	if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
		((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
	}
	// 配置懒加载
	if (this.lazyInitialization) {
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	// 配置属性源顺序
	context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
	// 加载来源
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	load(context, sources.toArray(new Object[0]));
	// 触发监听器的 contextLoaded 方法
	listeners.contextLoaded(context);
}

5.刷新容器:

java 复制代码
refreshContext(context);

详细如下:这个refresh的话我在下面的文章中会仔细进行说明这个也是容器启动一个很重要的部分

java 复制代码
private void refreshContext(ConfigurableApplicationContext context) {
		if (this.registerShutdownHook) {
			shutdownHook.registerApplicationContext(context);
		}
		refresh(context);
	}

6.ApplicationRunner以及CommandLineRunner接口:

我们可以通过实现这个接口实现一些额外的业务逻辑因为调用实现这个接口的实现类的时候所有的Bean以及应用上下文完成了初始化

java 复制代码
//这个方法就是说应用程序启动成功之后
//然后context完成了刷新之后那么就会调用这个方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

这个就是我写的Demo测试:

java 复制代码
//这个就是一个很简单的启动类
@SpringBootApplication
public class SpringSimpleDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringSimpleDemoApplication.class, args);
    }
}
//这个就是我写的Bean类
@Component("testBean")
public class TestBean  {
    public void test() {
        System.out.println("测试成功");
    }
}
//这个就是实现了ApplictionRunner接口的类
@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Autowired
    private ApplicationContext context;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("执行一些额外的逻辑");
        TestBean bean = (TestBean) context.getBean("testBean");
        bean.test();
    }
}

最后的结果的展示:

这个的话后面加上一个总结吧因为光看代码的话其实本身就是枯燥的:

1.就是创建一个SpringApplication对象,在这个构造函数里面就是一个环境的类型就是通过判断是否存在一些特定的类进行判断,然后就是加载Springfactory文件然后获得监控器进行设置,后面的话其实就是在run方法

2.第一步就是进行环境的准备 通过configureEnvironment 方法包含运行的时候的所需的配置的信息如配置文件 系统属性以及命令行参数如:spring.profiles.active=dev 通过解析这个命令行来决定加载哪个配置文件然后就是说通过**ConfigurationPropertySources.attach(environment)**将配置的属性源和当前的运行环境进行一个关联这个就是为了环境对象对配置属性源更好地进行利用和管理如:如果添加了或者删除了属性源的话就会自动进行移除

3.第二步就是创建应用上下文:主要就是通过WebApplicationType(包括NONESERVLETREACTIVE),默认的条件下的话Spring Boot 通常会创建AnnotationConfigApplicationContext作为应用上下文然后就会到刷新上下文(refresh()这个就是后面的话就是会进行介绍)

4.第三步就是 ApplicationRunner以及CommandLineRunner接口:这个的话就是交给我们的消费这个进行书写然后这个时候我们可以进行一些拓展如:可以进行日志的打印或者如果这个时候可以调用Bean中的一个定时的任务的话(这个和Bean生命周期中的BeanPostProcessor接口一样可以添加一些额外的逻辑)

相关推荐
哎呦没6 分钟前
Spring Boot与林业产品推荐系统的融合
java·spring boot·后端
大梦百万秋7 分钟前
云原生后端开发:构建现代化可扩展的服务
后端
gorgor在码农8 分钟前
redis 底层数据结构
java·数据库·redis
Erosion202016 分钟前
JAVA WEB和Tomcat各组件概念
java·tomcat
G丶AEOM18 分钟前
JVM中TLAB(线程本地分配缓存区)是什么
java·jvm
《源码好优多》22 分钟前
基于Java Springboot华为数码商城交易平台
java·开发语言·spring boot
往日情怀酿做酒 V176392963831 分钟前
Django基础之路由
后端·python·django
桃园码工1 小时前
第三章:基本语法 2.变量和常量 --Go 语言轻松入门
后端·golang·go语言
♡喜欢做梦1 小时前
【Java】二叉树:数据海洋中灯塔式结构探秘(下:基本操作)
java·数据结构·算法
Allen Bright1 小时前
Java代码操作Zookeeper(使用 Apache Curator 库)
java·zookeeper·java-zookeeper