SpringBoot starter机制java
SpringBoot由众多Starter组成(一系列的自动化配置的starter插件),SpringBoot之因此流行,也是由于starter。web
starter是SpringBoot很是重要的一部分,能够理解为一个可拔插式的插件,正是这些starter使得使用某个功能的开发者不须要关注各类依赖库的处理,不须要具体的配置信息,由Spring Boot自动经过classpath路径下的类发现须要的Bean,并织入相应的Bean。redis
例如,你想使用Reids插件,那么可使用spring-boot-starter-redis;若是想使用MongoDB,可使用spring-boot-starter-data-mongodbspring
为何要自定义startermongodb
开发过程当中,常常会有一些独立于业务以外的配置模块。若是咱们将这些可独立于业务代码以外的功能配置模块封装成一个个starter,复用的时候只须要将其在pom中引用依赖便可,SpringBoot为咱们完成自动装配设计模式
自定义starter的命名规则app
SpringBoot提供的starter以spring-boot-starter-xxx
的方式命名的。官方建议自定义的starter使用xxx-spring-boot-starter
命名规则。以区分SpringBoot生态提供的starterless
整个过程分为两部分:maven
首先,先完成自定义starteride
(1)新建maven jar工程,工程名为zdy-spring-boot-starter,导入依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.2.2.RELEASE</version> </dependency> </dependencies>
(2)编写javaBean
@EnableConfigurationProperties(SimpleBean.class) @ConfigurationProperties(prefix = "simplebean") public class SimpleBean { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "SimpleBean{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
(3)编写配置类MyAutoConfiguration
@Configuration @ConditionalOnClass //@ConditionalOnClass:当类路径classpath下有指定的类的状况下进行自动配置 public class MyAutoConfiguration { static { System.out.println("MyAutoConfiguration init...."); } @Bean public SimpleBean simpleBean(){ return new SimpleBean(); } }
(4)resources下建立/META-INF/spring.factories
注意:META-INF是本身手动建立的目录,spring.factories也是手动建立的文件,在该文件中配置本身的自动配置类
<img
src="./images/image-20200111123116471.png"
alt="image-20200111123116471" style="zoom:67%;" />
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.lagou.config.MyAutoConfiguration
使用自定义starter
(1)导入自定义starter的依赖
<dependency> <groupId>com.lagou</groupId> <artifactId>zdy-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
(2)在全局配置文件中配置属性值
simplebean.id=1 simplebean.name=自定义starter
(3)编写测试方法
//测试自定义starter @Autowired private SimpleBean simpleBean; @Test public void zdyStarterTest(){ System.out.println(simpleBean); }
2.4 执行原理
每一个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中经过执行SpringApplication.run()便可启动整个Spring Boot程序。
问题:那么SpringApplication.run()方法究竟是如何作到启动Spring Boot项目的呢?
下面咱们查看run()方法内部的源码,核心代码具体以下:
@SpringBootApplication public class SpringbootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootDemoApplication.class, args); } }
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class[]{primarySource}, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return (new SpringApplication(primarySources)).run(args); }
从上述源码能够看出,SpringApplication.run()方法内部执行了两个操做,分别是SpringApplication实例的初始化建立和调用run()启动项目,这两个阶段的实现具体说明以下
1.SpringApplication实例的初始化建立
查看SpringApplication实例对象初始化建立的源码信息,核心代码具体以下
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { this.sources = new LinkedHashSet(); this.bannerMode = Mode.CONSOLE; this.logStartupInfo = true; this.addCommandLineProperties = true; this.addConversionService = true; this.headless = true; this.registerShutdownHook = true; this.additionalProfiles = new HashSet(); this.isCustomEnvironment = false; this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); //把项目启动类.class设置为属性存储起来 this.primarySources = new LinkedHashSet(Arrays.asList(primarySources)); //判断当前webApplicationType应用的类型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 设置初始化器(Initializer),最后会调用这些初始化器 this.setInitializers(this.getSpringFactoriesInstances( ApplicationContextInitializer.class)); // 设置监听器(Listener) this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); // 用于推断并设置项目main()方法启动的主程序启动类 this.mainApplicationClass = this.deduceMainApplicationClass();
从上述源码能够看出,SpringApplication的初始化过程主要包括4部分,具体说明以下。
(1)this.webApplicationType
= WebApplicationType.deduceFromClasspath()
用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5以前的传统MVC应用)仍是REACTIVE应用(Spring
5开始出现的WebFlux交互式应用)
(2)this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
用于SpringApplication应用的初始化器设置。在初始化器设置过程当中,会使用Spring类加载器SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取全部可用的应用初始化器类ApplicationContextInitializer。
(3)this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))
用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本同样,也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取全部可用的监听器类ApplicationListener。
(4)this.mainApplicationClass
= this.deduceMainApplicationClass()
用于推断并设置项目main()方法启动的主程序启动类
2.项目的初始化启动
分析完(new
SpringApplication(primarySources)).run(args)源码前一部分SpringApplication实例对象的初始化建立后,查看run(args)方法执行的项目初始化启动过程,核心代码具体以下:
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); this.configureHeadlessProperty(); // 第一步:获取并启动监听器 SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting(); Collection exceptionReporters; try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 第二步:根据SpringApplicationRunListeners以及参数来准备环境 ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体 Banner printedBanner = this.printBanner(environment); // 第三步:建立Spring容器 context = this.createApplicationContext(); exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, new Object[]{context}); // 第四步:Spring容器前置处理 this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 第五步:刷新容器 this.refreshContext(context); // 第六步:Spring容器后置处理 this.afterRefresh(context, applicationArguments); stopWatch.stop(); if(this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)) .logStarted(this.getApplicationLog(), stopWatch); } // 第七步:发出结束执行的事件 listeners.started(context); // 返回容器 this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
从上述源码能够看出,项目初始化启动过程大体包括如下部分:
this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实例初始化过程当中初始化的SpringApplicationRunListener监听器并运行。
this.prepareEnvironment(listeners, applicationArguments)方法主要用于对项目运行环境进行预设置,同时经过this.configureIgnoreBeanInfo(environment)方法排除一些不须要的运行环境
根据webApplicationType进行判断, 肯定容器类型,若是该类型为SERVLET类型,会经过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext,接着使用以前初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置
这一步主要是在容器刷新以前的准备动做。设置容器环境,包括各类变量等等,其中包含一个很是关键的操做:将启动类注入容器,为后续开启自动化配置奠基基础
开启刷新spring容器,经过refresh方法对整个IOC容器的初始化(包括bean资源的定位,解析,注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭
扩展接口,设计模式中的模板方法,默认为空实现。若是有自定义需求,能够重写该方法。好比打印一些启动结束log,或者一些其它后置处理。
获取EventPublishingRunListener监听器,并执行其started方法,而且将建立的Spring容器传进去了,建立一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext 的publishEvent方法,也就是说这里是在Spring容器中发布事件,并非在SpringApplication中发布事件,和前面的starting是不一样的,前面的starting是直接向SpringApplication中的监听器发布启动事件。
用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后当即执行一些特定程序。其中,Spring Boot提供的执行器接口有ApplicationRunner 和CommandLineRunner两种,在使用时只须要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,而后Spring Boot项目启动后会当即执行这些特定程序
上了拉勾教育的《Java工程师高薪训练营》,作一下笔记。但愿拉勾能给我推到想去的公司,目标:字节!!