likes
comments
collection
share

详解Spring的SPI机制和自定义Springboot的Starter包

作者站长头像
站长
· 阅读数 16

为了给组里的实习生妹妹讲如何自定义SpringbootStarter

我觉得需要让她先知道Springboot的自动装配机制,

但又感觉是不是得先让她了解SpringSPI机制,

最后又觉得还有必要说明一下一个@Configuration配置类在Spring中如何被解析,这又会引出对ConfigurationClassPostProcessor的讲解,

完了,收不住了,到处都是知识盲区。

知识盲区脑图如下。

详解Spring的SPI机制和自定义Springboot的Starter包

往期文章

一文学会Springboot的自动装配机制

一文学会Spring的@Configuration配置类解析


前言

本文会先对JDKSPI机制和SpringSPI机制进行说明和演示,最后演示如何自定义Springbootstarter

Springboot版本:2.4.1

正文

一. JDK-SPI机制

SPI全称为Service Provider Interface,是指在模块装配的时候动态加载接口的实现类的一种机制。使用JDK-SPI的规则如下所示。

  • 服务提供模块提供了接口的具体实现后,需要在服务提供模块的jar包的META-INF/services目录下创建一个配置文件,配置文件名为接口的全限定名,文件内容为当前jar包中接口的实现类的全限定名;
  • 服务提供模块的jar包需要在主程序的classpath下;
  • 在主程序中,通过java.util.ServiceLoaderload(接口.class) 方法来加载接口的实现类,实现方式就是遍历classpath下每个jar包中的META-INF/services目录下的配置文件,通过配置文件中的接口实现类的全限定名加载接口实现类;
  • 接口实现类必须有无参构造方法。

下面是一个具体示例。

定义接口和实现类,如下所示。

public interface MyApi {}

public class MyController implements MyApi {}

public class MyService implements MyApi {}

public class MyDao implements MyApi {}

META-INF/services目录下创建配置文件,配置文件名为com.learn.spi.jdkspi.MyApi,内容如下所示。

com.learn.spi.jdkspi.MyController
com.learn.spi.jdkspi.MyService
com.learn.spi.jdkspi.MyDao

测试程序如下所示。

public class MyTest {

    public static void main(String[] args) {
        ServiceLoader<MyApi> myApis = ServiceLoader.load(MyApi.class);
        for (MyApi myApi : myApis) {
            System.out.println(myApi.getClass().getName());
        }
    }

}

测试结果如下所示。

详解Spring的SPI机制和自定义Springboot的Starter包

二. Spring-SPI机制

Spring中提供了和JDK-SPI类似的机制,通过SpringFactoriesLoader扫描classpath下所有jar包中META-INF目录下spring.factories文件,来得到需要加载的类的集合。

下面是一个具体示例。

定义两个接口,如下所示。

public interface MyApi {}

public interface MyAnotherApi {}

定义业务类,如下所示。

public class MyController {}

public class MyService {}

public class MyDao {}

public class MyAnotherController implements MyAnotherApi {}

public class MyAnotherService implements MyAnotherApi {}

public class MyAnotherDao implements MyAnotherApi {}

spring.factories文件内容如下所示。

com.learn.spi.springspi.MyApi=\
  com.learn.spi.springspi.MyController,\
  com.learn.spi.springspi.MyService,\
  com.learn.spi.springspi.MyDao

com.learn.spi.springspi.MyAnotherApi=\
  com.learn.spi.springspi.MyAnotherController,\
  com.learn.spi.springspi.MyAnotherService,\
  com.learn.spi.springspi.MyAnotherDao

测试程序如下所示。

public class MyTest {

    public static void main(String[] args) {
        // 不要求加载的类实现了接口
        List<String> factoryNames = SpringFactoriesLoader
                .loadFactoryNames(MyApi.class, null);
        for (String factoryName : factoryNames) {
            System.out.println(factoryName);
        }

        // 要求加载的类实现了接口
        List<MyAnotherApi> myAnotherApis = SpringFactoriesLoader
                .loadFactories(MyAnotherApi.class, null);
        for (MyAnotherApi myAnotherApi : myAnotherApis) {
            System.out.println(myAnotherApi.getClass().getName());
        }
    }

}

测试结果如下所示。

详解Spring的SPI机制和自定义Springboot的Starter包

三. 自定义starter

相信有了前面几篇文章的铺垫,现在自己实现一个starter,简直是信手拈来。

自定义starter组件工程结构如下所示。

详解Spring的SPI机制和自定义Springboot的Starter包

MyModuleControllerMyModuleServiceMyModuleDao定义如下。

public class MyModuleController {}

public class MyModuleService {}

public class MyModuleDao {}

MyModuleConfig定义如下。

@Configuration
public class MyModuleConfig {

    @Bean
    public MyModuleController myModuleController() {
        return new MyModuleController();
    }

    @Bean
    public MyModuleService myModuleService() {
        return new MyModuleService();
    }

    @Bean
    public MyModuleDao myModuleDao() {
        return new MyModuleDao();
    }

}

spring.factories内容如下。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.learn.module.stater.config.MyModuleConfig

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-parent</artifactId>
        <version>2.4.1</version>
    </parent>

    <groupId>com.learn.module.stater</groupId>
    <artifactId>learn-module-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

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

</project>

最后通过Maven将自定义starter组件安装到本地仓库。

四. 测试自定义starter

创建一个测试工程,工程目录如下所示。

详解Spring的SPI机制和自定义Springboot的Starter包

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-parent</artifactId>
        <version>2.4.1</version>
    </parent>

    <groupId>com.test.module.starter</groupId>
    <artifactId>test-module-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

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

        <dependency>
            <groupId>com.learn.module.stater</groupId>
            <artifactId>learn-module-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

ModuleStarterApplication定义如下。

@SpringBootApplication
public class ModuleStarterApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext
                = SpringApplication.run(ModuleStarterApplication.class, args);

        MyModuleController myModuleController = applicationContext
                .getBean(MyModuleController.class);
        MyModuleService myModuleService = applicationContext
                .getBean(MyModuleService.class);
        MyModuleDao myModuleDao = applicationContext
                .getBean(MyModuleDao.class);
        System.out.println(myModuleController);
        System.out.println(myModuleService);
        System.out.println(myModuleDao);
    }

}

运行ModuleStarterApplication,打印如下所示。

详解Spring的SPI机制和自定义Springboot的Starter包

总结

至此,总算是能够给组里的实习生妹妹讲明白如何自定义一个starter。当然,自定义starter是很简单的,但是其背后的工作原理,还是有一定的复杂度的。

比如我们的starter里面的各种bean,如何被Springboot加载到容器,这里面就涉及到SpringSPI机制以及Springboot的自动装配机制。

Spring对@Configuration配置类的解析看起来和Springboot的自动装配机制没什么关系,但是却又是息息相关。

所以如果都明白了上述的一些机制的作用原理,那么实现一个starter,真的就是很简单的事情了。

如果觉得本篇文章对你有帮助,求求你点个赞,加个收藏最后再点个关注吧。创作不易,感谢支持!


本文正在参加「金石计划」