详解SpringBoot——启动原理及自定义starter

1、引言


SpringBoot的一大优点就是Starter,因为SpringBoot有不少开箱即用的Starter依赖,使得咱们开发变得简单,咱们不须要过多的关注框架的配置。java

在平常开发中,咱们也会自定义一些Starter,特别是如今微服务框架,咱们一个项目分红了多个单体项目,而这些单体项目中会引用公司的一些组件,这个时候咱们定义Starter,可使这些单体项目快速搭起,咱们只须要关注业务开发。程序员

在此以前咱们再深刻的了解下SpringBoot启动原理。然后再将如何自定义starter。web

2、 启动原理


要想了解启动原理,咱们能够Debug模式跟着代码一步步探究,咱们从入口方法开始:面试

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

这里是建立一个SpringApplication对象,并调用了run方法spring

2.1 建立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应用类型
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	//从类路径下找到META-INF/spring.factories配置的全部ApplicationContextInitializer;而后保存起来
	setInitializers((Collection) getSpringFactoriesInstances(
		          ApplicationContextInitializer.class));
	//从类路径下找到ETA-INF/spring.factories配置的全部ApplicationListener
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	//从多个配置类中找到有main方法的主配置类
	this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码

从这个方法中能够看出,这个sql

第一步:保存主配置类。apache

第二步:肯定web应用类型。缓存

第三步:setInitializers方法,这个方法走咱们看带入的参数是getSpringFactoriesInstances(ApplicationContextInitializer.class),咱们再往下查看getSpringFactoriesInstancesspringboot

再进入这个方法:bash

这里就是从类路径下找到META-INF/spring.factories配置的全部ApplicationContextInitializer,而后再保存起来,放开断点,咱们能够看到这个时候获取到的

第四步:从类路径下找到ETA-INF/spring.factories配置的全部ApplicationListener,原理也基本相似,进入断点

第五步:从多个配置类中找到有main方法的主配置类。这个执行完以后,SpringApplication就建立完成

2.2 run方法

先贴出代码

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	//从类路径下META-INF/spring.factories获取SpringApplicationRunListeners
	SpringApplicationRunListeners listeners = getRunListeners(args);
	//回调全部的获取SpringApplicationRunListener.starting()方法
	listeners.starting();
	try {
		//封装命令行参数
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
		            args);
		//准备环境 
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
		            applicationArguments);
		//建立环境完成后回调SpringApplicationRunListener.environmentPrepared();表示环境准备完成
		configureIgnoreBeanInfo(environment);
		//打印Banner图
		Banner printedBanner = printBanner(environment);
		//建立ApplicationContext,决定建立web的ioc仍是普通的ioc  
		context = createApplicationContext();
		//异常分析报告
		exceptionReporters = getSpringFactoriesInstances(
		            SpringBootExceptionReporter.class,
		            new Class[] { ConfigurableApplicationContext.class }, context);
		//准备上下文环境,将environment保存到ioc中
		//applyInitializers():回调以前保存的全部的ApplicationContextInitializer的initialize方法 
		//listeners.contextPrepared(context) 
		//prepareContext运行完成之后回调全部的SpringApplicationRunListener的contextLoaded()
		prepareContext(context, environment, listeners, applicationArguments,
		            printedBanner);
		//刷新容器,ioc容器初始化(若是是web应用还会建立嵌入式的Tomcat)
		//扫描,建立,加载全部组件的地方,(配置类,组件,自动配置)
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass)
			               .logStarted(getApplicationLog(), stopWatch);
		}
		//全部的SpringApplicationRunListener回调started方法
		listeners.started(context);
		//从ioc容器中获取全部的ApplicationRunner和CommandLineRunner进行回调,
		//ApplicationRunner先回调,CommandLineRunner再回调
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}
	try {
		//全部的SpringApplicationRunListener回调running方法
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	//整个SpringBoot应用启动完成之后返回启动的ioc容器
	return context;
}
复制代码

前面的代码不用分析,主要是准备对象,咱们从 SpringApplicationRunListeners listeners = getRunListeners(args)开始分析,

第一步:是从类路径下META-INF/spring.factories获取SpringApplicationRunListeners,

这个方法跟前面分析的两个获取配置方法相似。

第二步:回调全部的获取SpringApplicationRunListener.starting()方法。

第三步: 封装命令行参数。

第四步:准备环境,调用prepareEnvironment方法。

第五步:打印Banner图(就是启动时的标识图)。

第六步:建立ApplicationContext,决定建立web的ioc仍是普通的ioc。

第七步:异常分析报告。

第八步:准备上下文环境,将environment保存到ioc中,这个方法须要仔细分析下,咱们再进入这个方法

这里面有一个applyInitializers方法,这里是回调以前保存的全部的ApplicationContextInitializer的initialize方法

还有一个listeners.contextPrepared(context),这里是回调全部的SpringApplicationRunListener的contextPrepared(),

最后listeners.contextLoaded(context) 是prepareContext运行完成之后回调全部的SpringApplicationRunListener的contextLoaded()。

第九步:刷新容器,ioc容器初始化(若是是web应用还会建立嵌入式的Tomcat),这个就是扫描,建立,加载全部组件的地方,(配置类,组件,自动配置)。

第十步:全部的SpringApplicationRunListener回调started方法。

第十一步:从ioc容器中获取全部的ApplicationRunner和CommandLineRunner进行回调,ApplicationRunner先回调,CommandLineRunner再回调。

第十二步:全部的SpringApplicationRunListener回调running方法。

第十三步:整个SpringBoot应用启动完成之后返回启动的ioc容器。

这就是run的所有过程,想要更详细的了解还需本身去看源码。

3、自定义starter


自定义starter(场景启动器),咱们要作的事情是两个:肯定依赖和编写自动配置。咱们重点要作的就是编写自动配置,咱们以前写过一些自动配置,主要是注解配置的使用,主要的注解有:

  • @Configuration :指定这个类是一个配置类
  • @ConditionalOnXXX :在指定条件成立的状况下自动配置类生效
  • @AutoConfigureAfter:指定自动配置类的顺序
  • @Bean:给容器中添加组件
  • @ConfigurationPropertie:结合相关xxxProperties类来绑定相关的配置
  • @EnableConfigurationProperties:让xxxProperties生效加入到容器中

按照这些注解写好自动配置类后,咱们还须要进行自动配置的加载,加载方式是将须要启动就加载的自动配置类,配置在META-INF/spring.factories,启动器的大体原理是如此,而启动器的实际设计是有必定模式的,就是启动器模块是一个空 JAR 文件,仅提供辅助性依赖管理,而自动配置模块应该再从新设计一个,而后启动器再去引用这个自动配置模块。Springboot就是如此设计的:

另外还有一个命名规则:

官方命名空间

  • 前缀:“spring-boot-starter-”
  • 模式:spring-boot-starter-模块名
  • 举例:spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc

自定义命名空间

  • 后缀:“-spring-boot-starter”
  • 模式:模块-spring-boot-starter
  • 举例:mybatis-spring-boot-starter

3.1 建立自定义starter

第一步:由于咱们须要建立两个模块,因此先新建一个空的项目,而后以模块形式建立两个模块。

第二步:再建立两个模块,一个starter和一个自动配置模块

具体的建立过程就不赘述了,就是最简单的项目,去掉不须要的文件,建立完成结构以下:

第三步:咱们先将自动配置模块导入starter中,让启动模块依赖自动配置模块

启动模块的POM文件加入依赖

<dependencies>
    <!--引入自动配置模块-->
    <dependency>
        <groupId>com.yuanqinnan-starter</groupId>
        <artifactId>yuanqinnan-springboot-starter-autoconfigurer</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>
复制代码

自动配置模块的完整POM文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yuanqinnan-starter</groupId>
    <artifactId>yuanqinnan-springboot-starter-autoconfigurer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--引入spring-boot-starter;全部starter的基本配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>
</project>
复制代码

至此,两个项目基本建立完成,如今咱们实现简单的配置。

第五步:对自动配置类进行自动配置代码编写

先编写一个配置类,用于配置:

@ConfigurationProperties(prefix = "yuanqinnan.hello")
public class HelloProperties {
	//前缀
	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;
	}
}
复制代码

再编写一个服务

public class HelloService {
	HelloProperties helloProperties;
	public HelloProperties getHelloProperties() {
		return helloProperties;
	}
	public void setHelloProperties(HelloProperties helloProperties) {
		this.helloProperties = helloProperties;
	}
	public String sayHello(String name) {
		return helloProperties.getPrefix() + "-" + name + helloProperties.getSuffix();
	}
}
复制代码

而后再将这个服务注入组件:

@Configuration
@ConditionalOnWebApplication //web应用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
	@Autowired
	    HelloProperties helloProperties;
	@Bean
	    public HelloService helloService(){
		HelloService service = new HelloService();
		service.setHelloProperties(helloProperties);
		return service;
	}
}
复制代码

这个时候咱们的自动配置以及写完,还差最后一步,由于SpringBoot读取自动配置是在META-INF的spring.factories文件中,因此咱们还要将咱们的自动配置类写入其中

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
  com.yuanqinnan.starter.HelloServiceAutoConfiguration
复制代码

最后的结构以下:

至此,代码以及编写完成,这个时候咱们将其装入仓库中,让其余项目引用

3.2 使用自定义starter

建立一个web项目,而后在项目中引入依赖

<!--引入自定义starter-->
<dependency>
  <groupId>com.yuanqinnan.starter</groupId>
  <artifactId>yuanqinnan-springboot-starter</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>
复制代码

在application.properties 配置中加上配置:

yuanqinnan.hello.prefix=早安
yuanqinnan.hello.suffix=晚安
复制代码

加入测试:

@Autowired
HelloService helloService;

@Test
public void contextLoads() {
	System.out.println(helloService.sayHello("世界"));
}
复制代码

这样自定义Starter和引用自定义都已完成,Springboot的核心知识已经总结完成,后面再进行Springboot的一些高级场景整合,如缓存、消息、检索、分布式等。

读者福利

分享免费学习资料

针对于Java程序员,我这边准备免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)

为何某些人会一直比你优秀,是由于他自己就很优秀还一直在持续努力变得更优秀,而你是否是还在知足于现状心里在窃喜!但愿读到这的您能点个小赞和关注下我,之后还会更新技术干货,谢谢您的支持!

资料领取方式:加入Java技术交流群963944895点击加入群聊,私信管理员便可免费领取

如何成为一个有逼格的Java架构师

怎么提升代码质量?——来自阿里P8架构师的研发经验总结

阿里P8分享Java架构师的学习路线,第六点尤其重要

每一个Java开发者应该知道的八个工具

想面试Java架构师?这些最基本的东西你都会了吗?

画个图来找你的核心竞争力,变中年危机为加油站

哪有什么中年危机,不过是把定目标当成了有计划

被裁人不是寒冬重点,重点是怎么破解职业瓶颈

相关文章
相关标签/搜索