一, 引言
SpringBoot 的一大优势就是 Starter, 由于 SpringBoot 有很多开箱即用的 Starter 依赖, 使得我们开发变得简单, 我们不需要过多的关注框架的配置.
在日常开发中, 我们也会自定义一些 Starter, 特别是现在微服务框架, 我们一个项目分成了多个单体项目, 而这些单体项目中会引用公司的一些组件, 这个时候我们定义 Starter, 可以使这些单体项目快速搭起, 我们只需要关注业务开发.
在此之前我们再深入的了解下 SpringBoot 启动原理. 而后再将如何自定义 starter.
二, 启动原理
要想了解启动原理, 我们可以 Debug 模式跟着代码一步步探究, 我们从入口方法开始:
- public static ConfigurableApplicationContext run(Class<?>[] primarySources,
- String[] args) {
- return new SpringApplication(primarySources).run(args);
- }
这里是创建一个 SpringApplication 对象, 并调用了 run 方法
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();
- }
从这个方法中可以看出, 这个
第一步: 保存主配置类.
第二步: 确定 Web 应用类型.
第三步: setInitializers 方法, 这个方法走我们看带入的参数是 getSpringFactoriesInstances(ApplicationContextInitializer.class), 我们再往下查看 getSpringFactoriesInstances
再进入这个方法:
这里就是从类路径下找到 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 的全部过程, 想要更详细的了解还需自己去看源码.
三, 自定义 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 的一些高级场景整合, 如缓存, 消息, 检索, 分布式等.
来源: https://www.cnblogs.com/yuanqinnan/p/10784508.html