SpringBoot之旅第六篇-启动原理及自定义starter
一、引言
springboot的一大优势就是starter,由于springboot有很多开箱即用的starter依赖,使得我们开发变得简单,我们不需要过多的关注框架的配置。
在日常开发中,我们也会自定义一些starter,特别是现在微服务框架,我们一个项目分成了多个单体项目,而这些单体项目中会引用公司的一些组件,这个时候我们定义starter,可以使这些单体项目快速搭起,我们只需要关注业务开发。
在此之前我们再深入的了解下springboot启动原理。而后再将如何自定义starter。
public static configurableapplicationcontext run(class<?>[] primarysources, string[] args) { return new springapplication(primarysources).run(args); }
这里是创建一个springapplication对象,并调用了run方法
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(); }
从这个方法中可以看出,这个
第一步:保存主配置类。
第二步:确定web应用类型。
第三步:setinitializers方法,这个方法走我们看带入的参数是getspringfactoriesinstances(applicationcontextinitializer.class),我们再往下查看getspringfactoriesinstances
再进入这个方法:
这里就是从类路径下找到meta-inf/spring.factories配置的所有applicationcontextinitializer,然后再保存起来,放开断点,我们可以看到这个时候获取到的
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的全部过程,想要更详细的了解还需自己去看源码。
三、
自定义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
具体的创建过程就不赘述了,就是最简单的项目,去掉不需要的文件,创建完成结构如下:
第三步:我们先将自动配置模块导入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的一些高级场景整合,如缓存、消息、检索、分布式等。