一.前言
这篇文章的话就是我个人通过一些技术博客以及自己写一些Demo测试获得的一些感悟但是
由于本人的技术水平有限所以肯定就是会出现一些问题所以希望看这篇文章的时候如果发现错误的时候可以提出来然后我个人的话进行修改
二.SpringApplication 的构造函数
创建的一个简单的Spring的项目:
@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的构造函数:这个就是说通过进行一些准备工作如加载文件中的内容以及获得监听器
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文件。设置 ApplicationContextInitializersetInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 获取监听器,也加载了spring.factories文件setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 设置启动类信息this.mainApplicationClass = deduceMainApplicationClass();}
run方法的流程:
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 文件中获取监听器集合,当有事件发生时调用监听器对应事件的方法。下面就就就是代码的详情
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 duplicatesSet<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);
具体代码的实现:
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
//这个就是根据webApplicationType创建上下文
protected ConfigurableApplicationContext createApplicationContext() {return this.applicationContextFactory.create(this.webApplicationType);}
4.上下文的准备工作:
作用:准备和初始化 ApplicationContext
,包括设置环境、应用初始化器、注册监听器、注册单例Bean(这个就是将符合条件的类注册为BeanDefinition然后要通过BeanFactory进行创建)等
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);// 关闭 BootstrapContextbootstrapContext.close(context);// 记录启动信息if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// 注册 Boot 特定的单例 BeanConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}// 配置 BeanFactoryif (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.刷新容器:
refreshContext(context);
详细如下:这个refresh的话我在下面的文章中会仔细进行说明这个也是容器启动一个很重要的部分
private void refreshContext(ConfigurableApplicationContext context) {if (this.registerShutdownHook) {shutdownHook.registerApplicationContext(context);}refresh(context);}
6.ApplicationRunner以及
CommandLineRunner接口:
我们可以通过实现这个接口实现一些额外的业务逻辑因为调用实现这个接口的实现类的时候所有的Bean以及应用上下文完成了初始化
//这个方法就是说应用程序启动成功之后
//然后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测试:
//这个就是一个很简单的启动类
@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 {@Autowiredprivate ApplicationContext context;@Overridepublic 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
(包括NONE
、SERVLET
、REACTIVE
),默认的条件下的话Spring Boot 通常会创建AnnotationConfigApplicationContext
作为应用上下文然后就会到刷新上下文(refresh()这个就是后面的话就是会进行介绍)
4.第三步就是 ApplicationRunner以及CommandLineRunner接口:这个的话就是交给我们的消费这个进行书写然后这个时候我们可以进行一些拓展如:可以进行日志的打印或者如果这个时候可以调用Bean中的一个定时的任务的话(这个和Bean生命周期中的BeanPostProcessor接口一样可以添加一些额外的逻辑这个我在上面的内容进行进行演示,你们也可以尝试一下)