ToB企服应用市场:ToB评测及商务社交产业平台

标题: Spring Boot Starter 剖析与实践 [打印本页]

作者: 瑞星    时间: 2023-8-1 12:06
标题: Spring Boot Starter 剖析与实践
引言

对于 Java 开发人员来说,Spring 框架几乎是必不可少的。它是一个广泛用于开发企业应用程序的开源轻量级框架。近几年,Spring Boot 在传统 Spring 框架的基础上应运而生,不仅提供了 Spring 的全部功能,还使开发人员更加便捷地使用。在使用 Spring Boot 时,我们经常会接触到各种 Spring Boot Starter,例如 spring-boot-starter-web。只需将该依赖加入项目中,我们就可以开始开发应用;在引入 spring-boot-starter-data-jdbc 后,只需在配置文件中填写数据库连接信息,即可连接数据库。此外,您还可以随意切换数据源组件依赖,而无需修改业务代码。Spring Boot Starter 是如何适配的呢?我们能否自己实现一个 Spring Boot Starter 呢?本文将剖析 Spring Boot Starter 的原理,并自定义实现一个 Spring Boot Starter 组件。
一、Spring Boot Starter 是什么?

Spring Boot Starter 是 Spring Boot 中比较重要的概念, 是一种依赖描述符,它可以帮助您简化配置。当需要构建一个 Web 应用程序时,不必再遍历所有的依赖包,一个一个地添加到项目的依赖管理中,而是只需要一个配置spring-boot-starter-web,如以下示例:


从上面示例来看,我们使用了相当少的代码创建了一个 REST 应用程序。Spring 官方提供了许多 Starter,同时第三方也可以自定义 Starter,官方为了加以区分,Starter 从名称上进行了如下规范:spring-boot-starter-xxx;第三方提供的 starter 名称为:xxx-spring-boot-starter。
二、Spring Boot Starter 剖析

前面介绍了 Starter 的概念以及如何快速创建 REST 应用程序。只需添加一个依赖和几行代码,就能完成 REST 接口开发。那么,在没有 Spring Boot 和 Starter 的情况下,我们该如何进行开发呢?Spring Boot Starter 的工作原理又是什么?接下来,我们将通过开发 Web 服务和 Dubbo 服务作为例子,分别剖析纯 Spring 和 Spring Boot Starter。
Spring

环境依赖

开发流程

剖析

从上面的开发流程中,我们可以看到入口都在 web.xml 中。其中有一个监听器和一个 Servlet,以及初始化参数 dubbo.xml 和 mvc.xml。在 Spring Boot 出现之前,Spring 通常使用 XML 配置方式描述 Bean,或者在 XML 中配置注解驱动和上下文扫描方式解析 Bean。因此,我们可以看出这里有两个 XML 文件。经过分析源代码,我们整理出了以下 XML 标签解析到 Bean 解析的流程。如下:

从以上加载流程中,我们可以看出,在没有 Spring Boot 之前,Spring 主要依靠 XML 配置来启动。它会加载 XML 中的自定义标签,找到对应的命名空间,然后扫描 classpath 下的 META-INF/spring.handlers,找到命名空间处理类来解析当前标签。
Spring Boot

环境依赖

开发流程

剖析

从开发流程上没办法第一时间找到解析入口,唯一入口就是在DemoSpringBootApplication,经过源代码分析得出以下流程:

以上就是 Spring Boot 的自动装配过程。Spring Boot 利用被 @Configuration 注解的配置类来代替 Spring XML 完成 Bean 的注入。然后,SpringFactoriesLoader 会最终加载 META-INF/spring.factories 中的自动配置类,实现自动装配过程。依靠“约定大于配置”的思想,如果开发的 Starter 想要生效,就需要按照 Spring Boot 的约定。
小结

通过对比 Spring 与 Spring Boot 的开发流程,我们可以发现 Spring Boot 在完成 Web 与 Dubbo 独立应用开发时,使用了相对较少的代码和配置。这得益于 Spring Boot Starter 的自动装配能力,它是 Spring Boot 的主要功能。通过消除定义一些属于自动配置类部分的需求,自动配置可以帮助简化开发流程并加速开发速度。
SPI

我们从上面剖析发现,两者都使用了一项机制去加载引入的 jar 包中的配置文件从而加载对应类,那就是SPI(Service Provider Interface)
SPI (Service Provider Interface), 是 Java 内置的一种服务提供发现机制,提高框架的扩展性。

Java SPI

Java 内置的 SPI 通过java.util.ServiceLoader类解析 Classpath 和 jar 包的META-INF/services目录下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此完成调用。
但是 Java SPI 会有一定不足:
Spring SPI

Spring SPI 沿用了 Java SPI ,但是在实现上和 Java SPI 存在差异,但是核心机制相同,在不修改 Spring 源码前提下,可以做到对 Spring 框架的扩展开发。
Spring Boot 2.7.0

在本文中 Spring Boot 自动装配使用了 SPI 来加载到EnableAutoConfiguration所指定的自动装配的类名,但在 Spring Boot2.7.0之后自动装配 SPI 机制有所改动,META-INF/spring.factories将废弃,同时在 Spring Boot 3 以上会将相关代码移除,改动如下:
三、Spring Boot Stater 实践

在使用spring-boot-starter-jdbc或者spring-boot-starter-jpa等数据库操作时,通常会引入一个数据库数据源连接池,比如:HikariCP、DBCP等,同时可随意切换依赖而不需要去更改任何业务代码,开发人员也无需关注底层实现,在此我们自定义一个 Starter 同时也实现这种兼容。因为我们以开发一个分布式锁的 Starter 并拥有多个实现:Zookeeper、Redis。 在此使用 Spring Boot 2.6.9 版本。
开发

项目结构与 Maven 依赖
  1. └── src
  2.     ├── main
  3.     │   ├── java
  4.     │   │   └── com.demo.distributed.lock
  5.     │   │      ├── api
  6.     │   │      │   ├── DistributedLock.java
  7.     │   │      │   └── LockInfo.java
  8.     │   │      ├── autoconfigure
  9.     │   │      │   ├── DistributedLockAutoConfiguration.java
  10.     │   │      │   └── DistributedLockProperties.java
  11.     │   │      ├── redis
  12.     │   │      │   └── RedisDistributedLockImpl.java
  13.     │   │      └── zookeeper
  14.     │   │          └── ZookeeperDistributedLockImpl.java
  15.     │   └── resources
  16.     │       └── META-INF
  17.     │           └── spring.factories
复制代码
  1.                 org.springframework.boot        spring-boot-autoconfigure<?xml version="1.0" encoding="utf-8" ?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xmlns:context="http://www.springframework.org/schema/context"
  5.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  6.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  7.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  8.     <context:component-scan base-package="com.demo.controller"/>
  9.    
  10.     <mvc:annotation-driven/>
  11.    
  12.     <mvc:default-servlet-handler/>
  13. </beans>org.springframework.boot        spring-boot-configuration-processor        true<?xml version="1.0" encoding="utf-8" ?>
  14. <beans xmlns="http://www.springframework.org/schema/beans"
  15.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  16.        xmlns:context="http://www.springframework.org/schema/context"
  17.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  18.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  19.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  20.     <context:component-scan base-package="com.demo.controller"/>
  21.    
  22.     <mvc:annotation-driven/>
  23.    
  24.     <mvc:default-servlet-handler/>
  25. </beans>    org.apache.curator        curator-framework        5.1.0        provided                org.apache.curator        curator-recipes        5.1.0        provided<?xml version="1.0" encoding="utf-8" ?>
  26. <beans xmlns="http://www.springframework.org/schema/beans"
  27.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  28.        xmlns:context="http://www.springframework.org/schema/context"
  29.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  30.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  31.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  32.     <context:component-scan base-package="com.demo.controller"/>
  33.    
  34.     <mvc:annotation-driven/>
  35.    
  36.     <mvc:default-servlet-handler/>
  37. </beans>org.redisson        redisson        3.23.1        provided   
复制代码
在依赖里可以看到 Zookeeper 和 Redis 依赖关系被设置为provided,作用为编译与测试阶段使用,不会随着项目一起发布。即打包时不会带上该依赖。该设置在 Spring Boot Starter 作用较大。
分布式锁接口与实现

接口
  1. public interface DistributedLock {
  2.     /**
  3.      * 加锁
  4.      */
  5.     LockInfo tryLock(String key, long expire, long waitTime);
  6.     /**
  7.      * 释放锁
  8.      */
  9.     boolean release(LockInfo lock);
  10. }
复制代码
Redis 实现
  1. public class RedisDistributedLockImpl implements DistributedLock {
  2.     private final RedissonClient client;
  3.     public RedisDistributedLockImpl(RedissonClient client) {
  4.         this.client = client;
  5.     }
  6.     @Override
  7.     public LockInfo tryLock(String key, long expire, long waitTime) {
  8.         //do something
  9.         return null;
  10.     }
  11.     @Override
  12.     public boolean release(LockInfo lock) {
  13.         //do something
  14.         return true;
  15.     }
  16. }
复制代码
Zookeeper 实现
  1. public class ZookeeperDistributedLockImpl implements DistributedLock {
  2.     private final CuratorFramework client;
  3.     public ZookeeperDistributedLockImpl(CuratorFramework client) {
  4.         this.client = client;
  5.     }
  6.     @Override
  7.     public LockInfo tryLock(String key, long expire, long waitTime) {
  8.         return null;
  9.     }
  10.     @Override
  11.     public boolean release(LockInfo lock) {
  12.         return false;
  13.     }
  14. }
复制代码
DistributedLockAutoConfiguration 配置类
  1. @EnableConfigurationProperties(DistributedLockProperties.class)
  2. @Import({DistributedLockAutoConfiguration.Zookeeper.class, DistributedLockAutoConfiguration.Redis.class})
  3. public class DistributedLockAutoConfiguration {
  4.     @Configuration
  5.     @ConditionalOnClass(CuratorFramework.class)
  6.     @ConditionalOnMissingBean(DistributedLock.class)
  7.     @ConditionalOnProperty(name = "distributed.lock.type", havingValue = "zookeeper",
  8.             matchIfMissing = true)
  9.     static class Zookeeper {
  10.         @Bean
  11.         CuratorFramework curatorFramework(DistributedLockProperties properties) {
  12.             //build CuratorFramework client
  13.             return null;
  14.         }
  15.         @Bean
  16.         ZookeeperDistributedLockImpl zookeeperDistributedLock(CuratorFramework client) {
  17.             return new ZookeeperDistributedLockImpl(client);
  18.         }
  19.     }
  20.     @Configuration
  21.     @ConditionalOnClass(RedissonClient.class)
  22.     @ConditionalOnMissingBean(DistributedLock.class)
  23.     @ConditionalOnProperty(name = "distributed.lock.type", havingValue = "redis",
  24.             matchIfMissing = true)
  25.     static class Redis {
  26.         @Bean
  27.         RedissonClient redissonClient(DistributedLockProperties properties) {
  28.             //build RedissonClient client
  29.             return null;
  30.         }
  31.         @Bean
  32.         RedisDistributedLockImpl redisDistributedLock(RedissonClient client) {
  33.             return new RedisDistributedLockImpl(client);
  34.         }
  35.     }
  36. }
复制代码
spring.factories
  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2.   com.demo.distributed.lock.autoconfigure.DistributedLockAutoConfiguration
复制代码
我们只需要将该文件放到resource/META-INF/spring.factories下,就会被 Spring Boot 加载,这也是 Spring Boot 的约定大于配置的思想。
使用

Maven 依赖关系
  1.             com.demo        distributed-lock-spring-boot-starter        1.0.0-SNAPSHOT                dev<?xml version="1.0" encoding="utf-8" ?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.        xmlns:context="http://www.springframework.org/schema/context"
  5.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  6.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  7.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  8.     <context:component-scan base-package="com.demo.controller"/>
  9.    
  10.     <mvc:annotation-driven/>
  11.    
  12.     <mvc:default-servlet-handler/>
  13. </beans>true<?xml version="1.0" encoding="utf-8" ?>
  14. <beans xmlns="http://www.springframework.org/schema/beans"
  15.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  16.        xmlns:context="http://www.springframework.org/schema/context"
  17.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  18.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  19.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  20.     <context:component-scan base-package="com.demo.controller"/>
  21.    
  22.     <mvc:annotation-driven/>
  23.    
  24.     <mvc:default-servlet-handler/>
  25. </beans><?xml version="1.0" encoding="utf-8" ?>
  26. <beans xmlns="http://www.springframework.org/schema/beans"
  27.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  28.        xmlns:context="http://www.springframework.org/schema/context"
  29.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  30.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  31.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  32.     <context:component-scan base-package="com.demo.controller"/>
  33.    
  34.     <mvc:annotation-driven/>
  35.    
  36.     <mvc:default-servlet-handler/>
  37. </beans>                org.redisson                redisson                3.23.1<?xml version="1.0" encoding="utf-8" ?>
  38. <beans xmlns="http://www.springframework.org/schema/beans"
  39.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  40.        xmlns:context="http://www.springframework.org/schema/context"
  41.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  42.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  43.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  44.     <context:component-scan base-package="com.demo.controller"/>
  45.    
  46.     <mvc:annotation-driven/>
  47.    
  48.     <mvc:default-servlet-handler/>
  49. </beans>                test<?xml version="1.0" encoding="utf-8" ?>
  50. <beans xmlns="http://www.springframework.org/schema/beans"
  51.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  52.        xmlns:context="http://www.springframework.org/schema/context"
  53.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  54.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  55.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  56.     <context:component-scan base-package="com.demo.controller"/>
  57.    
  58.     <mvc:annotation-driven/>
  59.    
  60.     <mvc:default-servlet-handler/>
  61. </beans>                org.apache.curator                curator-framework                5.1.0<?xml version="1.0" encoding="utf-8" ?>
  62. <beans xmlns="http://www.springframework.org/schema/beans"
  63.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  64.        xmlns:context="http://www.springframework.org/schema/context"
  65.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  66.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  67.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  68.     <context:component-scan base-package="com.demo.controller"/>
  69.    
  70.     <mvc:annotation-driven/>
  71.    
  72.     <mvc:default-servlet-handler/>
  73. </beans><?xml version="1.0" encoding="utf-8" ?>
  74. <beans xmlns="http://www.springframework.org/schema/beans"
  75.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  76.        xmlns:context="http://www.springframework.org/schema/context"
  77.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  78.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  79.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  80.     <context:component-scan base-package="com.demo.controller"/>
  81.    
  82.     <mvc:annotation-driven/>
  83.    
  84.     <mvc:default-servlet-handler/>
  85. </beans>org.apache.curator                curator-recipes                5.1.0<?xml version="1.0" encoding="utf-8" ?>
  86. <beans xmlns="http://www.springframework.org/schema/beans"
  87.        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  88.        xmlns:context="http://www.springframework.org/schema/context"
  89.        xmlns:mvc="http://www.springframework.org/schema/mvc"
  90.        xsi:schemaLocation="http://www.springframework.org/schema/beans
  91.         http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  92.     <context:component-scan base-package="com.demo.controller"/>
  93.    
  94.     <mvc:annotation-driven/>
  95.    
  96.     <mvc:default-servlet-handler/>
  97. </beans>   
复制代码
此处结合 Maven profile 功能按照不同环境依赖不同分布式锁底层实现,同时 Spring Boot 也提供了 Spring Boot Profile 加载不同配置,可以从开发、测试、生产环境使用不同底层了,同时 Maven profile 可以根据-P指定加载不同的依赖进行打包,解决了不同环境使用不同分布式锁实现。
代码使用
  1. private final DistributedLock distributedLock;
  2. public DemoServiceImpl(DistributedLock distributedLock) {
  3.     this.distributedLock = distributedLock;
  4. }
  5. public void test() {
  6.     LockInfo lock = null;
  7.     try {
  8.         lock = distributedLock.tryLock("demo", 1000, 1000);
  9.         //do something
  10.     } finally {
  11.         if (lock != null) {
  12.             distributedLock.release(lock);
  13.         }
  14.     }
  15. }
复制代码
业务代码中由于依赖的是接口,结合 Spring Boot Starter 条件注解 + Maven Profile 不管依赖哪个分布式锁实现,都无需去修改代码。
四、总结

本文介绍了在没有 Spring Boot 和 Starter 之前,开发人员在使用传统的 Spring XML 开发 Web 应用时需要引用许多依赖,并且需要大量编写 XML 代码来描述 Bean 以及它们之间的依赖关系。也了解了如何利用 SPI 加载自定义标签来加载 Bean 并进行注入。而 Spring Boot Starter 则提供了一种更加现代化的配置方式,它通过 SPI 机制加载自动装配的 @Configuration 配置类来代替传统的 Spring XML 完成 Bean 的注入,从而消除了大量的 XML 配置。最后,我们通过自定义开发了一个分布式锁 Spring Boot Starter 组件,利用一系列的 @ConditionalXXX 注解和 Maven Profile 来完成开发。这样,我们可以兼容多种分布式锁实现,并且在不同环境下使用不同的分布式锁实现,而无需修改业务代码。
作者:京东零售 陈炎清
来源:京东云开发者社区

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4