2019.7.24 springboot启动初始化过程源码分析和自定义starter场景启动

springboot启动 配置 原理

我们知道,springboot的项目启动时从main入口开始的:所以我们从此开始分析启动原理和运行过程

@SpringBootApplication
public class SpringBoot05JpaApplication {

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

debug调试分析其原理我们发现:运行这个main方法,主要由两个部分组成new一个SpringApplication对象、运行该对象的run(args)方法。
在这里插入图片描述

一 SpringApplication对象

首先分析第一部分,创建SpringApplication对象过程是:在构造中通过初始化来创建这个对象。
在这里插入图片描述
下面我们分析一下初始化SpringApplication对象都做了些什么?

@SuppressWarnings({ "unchecked", "rawtypes" })
	private void initialize(Object[] sources) {
	//(1)首先将SpringBoot05JpaApplication.class字节码对象判断,并保存下来,以后会用哪个。
		if (sources != null && sources.length > 0) {
			this.sources.addAll(Arrays.asList(sources));
		}
		//(2)判断整个项目是不是web环境
		this.webEnvironment = deduceWebEnvironment();
		//(3)实际从名字大概知道,从MATA-INF/spring.factories文件中获取ApplicationContextInitializer类型的所有类,
		//即保存起来
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
	   //(4)和第三步一样。调用的方法都一样,所以也是从从MATA-INF/spring.factories文件中获取
	   //ApplicationListener.class类型的所有类。用于后面的使用
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		//(5)最后一把是判断哪个配置类中有main方法入口,以此类为主配置类。
		this.mainApplicationClass = deduceMainApplicationClass();
	}

至此SpringApplication对象对象就创建完了

这边简述一下第三步中如何获取指定类型的类的简述过程:
(1)进入getSpringFactoriesInstances(ApplicationContextInitializer.class))方法汇总,看内部都调用了那些方法
在这里插入图片描述
找到一个LoadFactoryNames(xxx)方法。进入继续看源码。找到一个常量,这个常量内容就是MATA-INF/spring.factories的路径这就是初始化过程中自动加载指定类的过程。这一过程是springboot中所有自动配置的通用方式
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
至此,springboot中main方法启动的第一步就完成了——创建SpringApplication对象都做了些什么东东(初始化一些内容,保存一些listener,initializer等组件类)。这是所有springboot的通用步骤过程(无论集合什么技术栈这都是springboot应用的通用过程)

二 SpringApplication对象的run方法——最终返回完整的IOC容器

继续debug进入run方法:核心关注两点:(1)本项目生命周期和(2)IOC容器的生命周期。下面run方法是这两个生命周期的运行过程;实际上也可以通过两个类:SpringApplicationRunListener和ApplicationContextInitializer来监听他们生命周期过程。最后面有演示。

	public ConfigurableApplicationContext run(String... args) {
	//这些就是停止监听对象,然后后面启动它,这不用管
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		//这边创建IOC容器对象(核心注意这个对象奥!)。
		ConfigurableApplicationContext context = null;
		FailureAnalyzers analyzers = null;
		//这个和图形化 "java.awt.headless";有关,用的少,也忽略
		configureHeadlessProperty();
		//(1)这边就很重要了,获取本项目(应用的)中要运行的监听器对象。该对象如何获取的呢?下面有该对象获取过程
		//进入方法发现,还是从文件MATE-INFO/spring.factories中获取的。
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//(2)启动该监听器对象;该监听器很重要奥!有对项目启动的监听,环境准备的监听,ioc容器的准备监听、IOC容器加载完成监听、项目run过程都结束后的监听。
		listeners.starting();
		try {
		//(3)这一句就是将参数对象化,将参数内容转为一个对象保存。下面也有该方法实现过程
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
		//(4)这一步是准备环境,各种IOC容器环境,监听器环境等等环境准备,而且还判断是否是web环境等等操作。有点复杂,
		//反正就知道这个方法准备好了各种环境就欧了
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
		//(5)从方法名就知道是打印标幅(图标)
			Banner printedBanner = printBanner(environment);
		//(6)这一步非常重要!!!,是创建spring的IOC容器
			context = createApplicationContext();
			//(7)这主要对容器进行分析的,如果有异常会有异常报告
			analyzers = new FailureAnalyzers(context);
			//(8)这一步比较关键IOC容器完成创建前的一些准备:如通用的监听器,环境等等都开始生效,为后面真正IOC所有内容生效作准备
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			//(9)这一步也很关键,是刷新初始化IOC容器中内容,就是一些自动配置,所有组件都生效的过程,这一步执行完后,IOC
			//容器也就基本彻底完成了。
			refreshContext(context);      //刷新容器就是给容器中初始化组件:所有的配置类,所有的bean都创建完了,如果web环境,内嵌的tomcat
			//也创建成功了。
			//(10)后面就是IOC容器创建后的一些善后工作和完善工作
			//如afterRefresh就是从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调 
			//ApplicationRunner先回调,CommandLineRunner再回调
			afterRefresh(context, applicationArguments);
			listeners.finished(context, null);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			//(11)最终返回启动的IOC容器,里面会有容器中注册的所有组件
			return context;
		}
				catch (Throwable ex) {
			handleRunFailure(context, listeners, analyzers, ex);
			throw new IllegalStateException(ex);
		}
	}

最后run方法返回的就是一个创建好的IOC容器,这个容器已经经过初始化,创建,加载所有组件后的成型容器了。
(1)对应上面run方法中的序号语句(1)
在这里插入图片描述(3)对应上面run方法中的序号语句(3)在这里插入图片描述
(6)对应上面run方法中的序号语句(6)
在这里插入图片描述
(8)对应上面run方法中的序号语句(8)
这是给IOC创建内容前作最后的准备:如通用的监听器,环境等等都开始生效,为后面真正IOC所有内容生效作准备

	private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
			//将环境内容保存到ioc容器中,然后进行环境设置。
		context.setEnvironment(environment);
		//给IOC容器后置处理
		postProcessApplicationContext(context);
		//该方法内部是调用最初创建SpringApplication对象时,保存的那些所有ApplicationContextInitializer类型(理解为初始化器)的初始化方法
		//终于知道前面保存那些类型的对象干嘛用了,是这边开始调用生效的。如下图所示
		applyInitializers(context);
		//这是监听器的初始操作
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
	//bean工厂中注入那些参数对象,通过map对象,将对应的名和对象注册进工厂中
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		if (printedBanner != null) {
		//容器中获取bean工厂对象,通过map对象,将对应的名和对象添加进工厂中
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}
		// Load the sources  获取到主配置类,就是最main方法中run方法中的那个类
		Set<Object> sources = getSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[sources.size()]));
		listeners.contextLoaded(context);
	}

在这里插入图片描述
(9)对应上面run方法中的序号语句(9)
在这里插入图片描述

其他相关分析

在这里插入图片描述
上面的四个类中
(1)配置在META-INF/spring.factories然后容器到文件中自动加载
ApplicationContextInitializer、 SpringApplicationRunListener
(2) 只需要放在ioc容器中,然后容器对象调用:context.getBeansOfType(ApplicationRunner.class).values()
ApplicationRunner、CommandLineRunner

上面四个类都是在springboot运行过程中调用到的类,而且这些类我们都可以自定义,然后是springboot运行的时候,使用我们自定义的这几个类中方法。但是这四个类实现其接口就可以了,但是要配置到springboot项目中,才能被调用到,这几个类在上面分析源码时候发现,他们调用的方式不同,所以配置的方式也不同,如: ApplicationContextInitializer、 SpringApplicationRunListener这两个类调用是到META-INF/spring.factories下获取的,而ApplicationRunner、CommandLineRunner是直接容器工厂中获取的(context.getBeansOfType(ApplicationRunner.class).values())。所以配置过程让项目调用的方式也不同。第一种要将自定义实现的类配置到源路径下的META-INF/spring.factories中,第二种直接在实现类上加个注解@component注册进容器就OK了

 */
public interface ApplicationRunner {

	/**
	 * Callback used to run the bean.
	 * @param args incoming application arguments
	 * @throws Exception on error
	 */
	void run(ApplicationArguments args) throws Exception;

}


========================================
	private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<Object>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<Object>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}
===================================
		private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
		try {
			(runner).run(args);
		}
		catch (Exception ex) {
			throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
		}
	}

上面发现这两个 ApplicationRunner、CommandLineRunner类是容器中获得对象调用的,所以我们可以自定义这个类型,然后注册进容器中,这样源码中调用该类型的话,就用我们自定义的了。

项目运行周期和IOC生命周期

这两个接口 ApplicationContextInitializer(容器初始化器)、 SpringApplicationRunListener(spring应用运行监听器),从名字就知道功能了。但值得注意的是,上面分析生命周期过程中,他们对象被调用的方式是从配置在META-INF/spring.factories中获取的,所以自定义这类后,必须依照源码中调用它的位置,而去配置我们自定义的类。

1.首先自定义 ApplicationContextInitializer(容器初始化器)
package com.example.listener;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class myApplicationContextInitializer implements ApplicationContextInitializer
<ConfigurableApplicationContext> {

	@Override
	public void initialize(ConfigurableApplicationContext applicationContext) {
		String applicationName = applicationContext.getApplicationName();
		System.out.println("myApplicationContextInitializer........的初始化方法运行"+"........."
		+applicationName+"......");		
	}

}

2.接着自定义SpringApplicationRunListener(spring应用运行监听器)
package com.example.listener;

import java.util.Arrays;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class MySpringApplicationRunListener implements SpringApplicationRunListener {

	public MySpringApplicationRunListener(SpringApplication application, String[] args) {
		// TODO Auto-generated constructor stub
	}
	
	@Override
	public void starting() {
		System.out.println("springboot应用运行开始。。。。。。");		
	}

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		String[] activeProfiles = environment.getActiveProfiles();
		System.out.println(Arrays.toString(activeProfiles));
		System.out.println("springboot应用运行到应用的环境准备。。。。。。");		
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		System.out.println("springboot应用运行到IOC对象创建前的准备。。。。。。");	
	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		System.out.println("springboot应用运行到IOC容器中内容加载前。。。。。。");	
		
	}

	@Override
	public void finished(ConfigurableApplicationContext context, Throwable exception) {
		System.out.println("springboot应用运行完美结束了。。。。。。");	
		
	}

}

3.将他们配置进指定调用的位置META-INF/spring.factories

这样就完成注册自定义的监听器的过程。这边监听器对象获取是从META-INF/spring.factories获取的,所以我们也要配置到这位置,但有些类是直接容器工厂中获取的(context.getBeansOfType(ApplicationRunner.class).values())。这样我们自定义的类直接在上面@component注册进容器就OK了。
在这里插入图片描述
最终通过监听器,确实可以了解到项目运行周期和IOC容器的生命周期,如下所示
在这里插入图片描述

三.starter场景启动器的自定义

上面分析springboot应用的启动整个流程,我们只要其原理了,核心:

  • 创建SpringApplication对象都做了些什么东东(初始化一些内容,保存一些listener,initializer等组件类)
  • run方法——最终返回完整的IOC容器(初始化,创建,加载所有组件后的成型容器了)
    只要他的运行原理后,我们就可以基于其项目构架和运行流程,自定义添加自己的部分。如自定义自己的自动配置类,然后打包成starter启动器,提供别人直接jar包使用。
(3.1)首先分析下maven项目和springboot项目的区别:

(1) maven是一个纯净版的项目管理,只有最原生的功能:

  • 依赖管理,项目中不用内部手动添加和管理jar包,直接pom配置文件中配置就自动调用外部jar包。节省空间。
  • 一键构建:使项目构建的过程简单化。maven可以完成:1.编译 2.测试 3.运行 4.打包 5.部署 的整个过程。

(2)而springboot在maven基础上添加了具有自动配置、注解开发等辅助功能。这样更方法我们开发。

(3.2)创建starter启动器

通常情况下,starter启动器模块本身内部没有其他本质功能,只负责导入对应自动配置的jar包(依赖管理和依赖导入的)。而真正自动配置的实现在另外一个模块,starter启动器模块中通过依赖管理导入对应自动配置的实现。这样好处就是场景启动器可以封装打包一个完整项目的所有配置作为一个完备功能的应用吧。
我们也按照这样模式:(1)starter只负责依赖管理,不用那些自动配置和注解开发的功能,所以只要创建为纯净版的maven项目就可以了,(2)真正自动配置的实现要创建为springboot项目,它需要具有自动配置和注解开发的辅助功能。但不要web,数据访问的功能就不要加入这些启动器场景,这样自定义的starter启动器会简洁点,不会冗余

  1. 创建starter自动配置的启动器;这个启动器是maven纯净的项目,只负责依赖管理所需的jar包,只要在pom中导入自己所定义的自动配置依赖就OK了,其他什么都不用编写。

在这里插入图片描述
在这里插入图片描述
2. 开始自定义自动配置模块的编写:由于需要自动配置和注解开发,所以创建基本的springboot项目,它具有这些负责功能。本自动配置内容是简单的创建一个字符串返回值,所以不用其他场景启动器,只要springboot最基本的导入就OK了(spring-boot-starter),其他test、web等都没有。插件什么也不用。
在这里插入图片描述
3. 配置类和自动配置类区别在于:要在META-INF/spring.factories中EnableAutoConfiguration键对应的值内注入该类的全路径名。 编写配置类,然后注入到META-INF/spring.factories中才能自动配置类奥,因为springboot应用启动原理知道,会自动到META-INF/spring.factories下加载组件。
4. (1)首先自定义一个实体类(xxx),如果想要在实体类中使用一些灵活的属性,(2)可以在编写一个同名属性类(xxxproperties),这个属性类通过注解(@ConfigurationProperties(prefix= “myspring.properties” ))标注为属性类,这样可以灵活在属性配置文件中注入可变的属性。(3)然后编写一个配置类,这配置类中将实体类注册进容器就完成了,但注册的过程要使用一些注解标记来完成。
(1)编写一个实体类mybean ,就是普通的java引用类型的类

public class mybean {
	mybeanproperties properties;
	
public mybeanproperties getProperties() {
		return properties;
	}
	public void setProperties(mybeanproperties properties) {
		this.properties = properties;
	}
public String  show(String name) {
	return  properties.getPrefix()+"  "+name+"  "+properties.getSuffix();
}
}

(2)编写一个和实体类同名的属性类,可以完成想实体类中注入灵活的属性值。这个灵活属性值可以在外边属性配置文件中设置属性值。
在这里插入图片描述

@ConfigurationProperties(prefix= "myspring.properties" )
public class mybeanproperties {
private String prefix;
private String suffix;
public String getPrefix() {
	return prefix;
}
public void setPrefix(String prefix) {
	this.prefix = prefix;
}
public String getSuffix() {
	return suffix;
}
public void setSuffix(String suffix) {
	this.suffix = suffix;
}
}

(3)编写一个配置类,配置类中将实体类注入容器。

@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(mybeanproperties.class)
public class mybeanautoconfiguration {

	@Autowired
	mybeanproperties properties;
	@Bean
	public mybean createbean() {
		mybean mybean = new mybean();
		mybean.setProperties(properties);
		return mybean;
		
	}
}

最终配置类要想实现自动配置,要了解springboot应用启动原理,过程中会自动到所有META-INF/spring.factories下加载键名为EnableAutoConfiguration所对应的全路径配置类的。
在这里插入图片描述
至此自动配置类就完成了,然后通过maven的install打包成jar包,存于本地仓库中,之后在新的springboot项目中,就可以直接通过对应的场景启动器引入该jar包,然后直接在新的项目中使用该自动配置所注册的bean了,这就是自动配置原理的全过程。
新项目中导入自定义的自动配置的jar包
在这里插入图片描述
看maven注入的依赖包,确实有自定义的jar包
在这里插入图片描述
这个自定义的jar包由于根据springboot启动原理,知道启动时会到META-INF/spring.factories下加载所有名为EnableAutoConfiguration的类,而我已经在jar包中设置了这个位置,所以启动这个新项目就会自动加载到我的配置类了,而配置类中的注解就会自动完成对应的功能,即注册bean到容器中,所以项目启动完成后,我就可以直接从容器中拿到我自己的bean实体来应用了。所以我写一个web测试类的时候,直接从容器中拿出我的bean,直接调用其bean中方法,,最终通过springmvc功能,直接将结果放到web的响应体中,响应给客户端。
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值