上期讲到,关于SpringBoot自动装配原理,相信小伙伴们已经看明白啦,今天,我们就来聊一聊如何根据自动装配原理,自定义一个starter吧

什么是starter

我们玩了那么久的SpringBoot, 几乎项目依赖中基本上全是各种各样的starter, 那么到底什么是starter?

starter是一组方便的依赖描述符,当我们使用它时,可以获得所有需要的Spring和相关技术的一站式服务,典型的如spring-boot-starter-web,引入之后,自动引入所有有关spring web项目相关的依赖。

说实话,用久了SpringBoot,阿鉴都已经忘记被每个项目都需要引入spring-core,spring-context,spring-web等等所支配的恐惧了哈哈。

回顾SpringBoot自动装配的内容

在上一节,我们是从探究redis的自动装配过程开始的,那么小伙伴们还记得它的过程吗?

阿鉴带大家回顾一下:

项目启动时,Spring通过@Import注解导入了AutoConfigurationImportSelector, 然后调用该类selectImports时,从classpath下的META-INF/spring.factories文件中读取key为EnableAutoConfiguration的配置类,然后Spring便会将这些类加载到Spring的容器中,变成一个个的Bean。

动手实践

流程已经梳理完了,现在就开始实践吧,思路其实非常简单

  1. 写一个配置类
  2. 将该配置类放到资源文件夹中的META-INF/spring.factories

先来创建一个项目

1. 创建spring-boot-starter-demo项目并编写pom.xml

<?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.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>cn.zijiancode</groupId>
    <artifactId>spring-boot-starter-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <modules>
        <!-- 自定义starter -->
        <module>spring-boot-starter-demo-starter</module>
        <!-- 用于测试自定义starter-->
        <module>spring-boot-starter-demo-sample</module>
    </modules>
</project>

2. 创建子模块spring-boot-starter-demo-starter

<?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>cn.zijiancode</groupId>
        <artifactId>spring-boot-starter-demo</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>spring-boot-starter-demo-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

</project>

3. 创建测试用模块spring-boot-starter-demo-sample

<?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>cn.zijiancode</groupId>
        <artifactId>spring-boot-starter-demo</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>spring-boot-starter-demo-sample</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- 自定义starter-->
        <dependency>
            <groupId>cn.zijiancode</groupId>
            <artifactId>spring-boot-starter-demo-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

项目结构如下:

spring-boot-starter-demo-starter: 我们本次的主角,自定义starter

spring-boot-starter-demo-sample: 测试模块,用于测试starter是否生效

4. 编写starter中的代码

编写一个handler,用于做注入测试

public class DemoHandler {

    public DemoHandler(){
        System.out.println("demo handler init!!");
    }
}

不需要加任何注解,因为等下使用@Bean的方式注入

编写自动配置类

public class DemoAutoConfiguration {

    @Bean
    public DemoHandler demoHandler(){
        return new DemoHandler();
    }
}

自动配置类同样也可以不需要加任何注解,因为它本质上是使用@Import导入的

当然,如果不加@Configuration注解的话在特定的场景其实会引发一个小小的问题

这个小问题阿鉴决定卖个关子,放到下期和大家聊一聊,很快的,就这两天(其实是因为说起来还是有些内容的)

resources目录下新建META-INF/spring.factories文件

编写配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.zijiancode.starter.config.DemoAutoConfiguration

5. 在测试模块中编写测试类

@SpringBootApplication
public class SampleApplication {

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

6. 启动项目查看结果

demo handler成功的被自动配置类注入了

也就是说,我们的starter生效啦~

我知道,到这个时候,肯定有小伙伴说:就这?

阿鉴:确实,就这,哈哈,一个普遍的使用方式到这就结束了~

利用starter做一些额外的操作

在上面的例子中,我们只是通过starter注入了bean,但是其实我们可以利用这样的机制做更多的事情,比如spring-boot-starter-data-redis就在项目启动时与redis建立了连接,并初始化连接池。还有我们之前学的nacos,在项目启动时,将服务注册到nacos等等。

那么,这样的操作应该怎么完成呢?

其实在例子中,我们的DemoHandler在构造方法里打印了一句demo hanlder init,这就是个小小的思路,我们完全可以在初始化bean时做些别的事情,当然,这样的做法并不太好,因为spring中的bean是一个接一个初始化的,如果我们在UserService的构造方法里写调用RoleService的逻辑,很可能会因为RoleService还没初始化而报错。

在Spring中,还有一个东西叫做监听器,我们可以利用它在做一些事情,这也是阿鉴最喜欢的一种方式。

监听器肯定是对应着一系列的事件的,有个事件叫做ContextRefreshedEvent, 表示Spring的上下文刷新完毕,所有的Bean都已经初始化完成,Spring的启动流程即将结束。

试试

1. 在DemoHandler加个方法

public class DemoHandler {

    public DemoHandler(){
        System.out.println("demo handler init!!");
    }
    
    public void hello(){
        System.out.println("hello world for demo starter!");
    }
}

2. 编辑监听器

public class DemoListener implements ApplicationListener<ContextRefreshedEvent> {

    @Resource
    private DemoHandler demoHandler;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        demoHandler.hello();
    }
}

3. 将监听器注入到容器中

public class DemoAutoConfiguration {

    @Bean
    public DemoHandler demoHandler(){
        return new DemoHandler();
    }
    
    @Bean
    public DemoListener demoListener(){
        return new DemoListener();
    }
}

4. 使用测试模块测试

测试成功,listern已生效

小结

本文基于上一期的SpringBoot自动装配原理介绍了如何自定义starter,并和小伙伴们聊了下怎么利用这个机制做一些额外的事情。

这一期的内容还是比较简单的,希望大家有所收获。

我们下期…完了,上一期还说了要和大家聊一聊如何查看组件的源码并进行扩展,我有罪,请允许我放到下一期吧「磕头.png」,我们下期再见~

gitee: https://gitee.com/lzj960515/spring-boot-starter-demo