Spring Boot Starter 剖析與實踐

来源:https://www.cnblogs.com/Jcloud/archive/2023/08/01/17595634.html
-Advertisement-
Play Games

本文介紹了在沒有 Spring Boot 和 Starter 之前,開發人員在使用傳統的 Spring XML 開發 Web 應用時需要引用許多依賴,並且需要大量編寫 XML 代碼來描述 Bean 以及它們之間的依賴關係。也瞭解瞭如何利用 SPI 載入自定義標簽來載入 Bean 併進行註入。 ...


引言

對於 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

環境依賴

  • JDK 1.8

  • Maven 3

  • Tomcat 8(需要依靠 Web 容器伺服器才能啟動)

  • spring-webmvc 4.3.30.RELEASE

  • dubbo 2.7.23

開發流程

  1. 首先介紹一下,這是一個標準的 Maven 目錄結構與demo-service依賴內容

    <dependencies>
        <!-- SpringMVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.30.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <!-- 此處需要導入databind包即可, jackson-annotations、jackson-core都不需要顯示自己的導入了-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>
    
        <!-- Dubbo -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.23</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-x-discovery</artifactId>
            <version>5.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.8.0</version>
        </dependency>
    
        <!-- Demo API -->
        <dependency>
            <groupId>com.demo</groupId>
            <artifactId>demo-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
    
    
  2. 由於在 Spring XML 下還需要依靠 Java Web 和 Web 容器運行,還需要web/WEB-INF/web.xmlWeb 配置文件,內容配置了 SpringMVC 入口

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!-- Spring監聽器 -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:dubbo.xml</param-value>
        </context-param>
    
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:mvc.xml</param-value>
            </init-param>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    
    
  3. SpringMVC 配置文件mvc.xml與 Dubbo 配置文件dubbo.xml

    <?xml version="1.0" encoding="utf-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            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">
    
        <context:component-scan base-package="com.demo.controller"/>
    
        <!-- 開啟 MVC 註解驅動 -->
        <mvc:annotation-driven/>
    
        <!-- 訪問靜態資源 -->
        <mvc:default-servlet-handler/>
    </beans>
    
    
    <?xml version="1.0" encoding="utf-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    
        <!-- Dubbo -->
        <dubbo:application name="demo-service"/>
        <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
        <dubbo:protocol name="dubbo" port="20880"/>
        <bean id="demoServiceImpl" class="com.demo.provider.DemoServiceImpl"/>
        <dubbo:service interface="com.demo.api.DemoService" ref="demoServiceImpl"/>
    </beans>
    
    
  4. 編寫 Controller 介面與 Dubbo RPC 介面

    package com.demo.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @GetMapping(value = "/say/hello")
        public HelloEntity sayHello() {
            return new HelloEntity("Hello World");
        }
    
    }
    
    
    package com.demo.provider;
    
    import com.demo.api.DemoService;
    import com.demo.dto.HelloEntity;
    
    public class DemoServiceImpl implements DemoService {
    
        @Override
        public HelloEntity sayHello() {
            return new HelloEntity("Hello World");
        }
    }
    
    
  5. 以上還無法單獨運行,需要將以上打包成war包放入到 Tomcat 才可運行。

剖析

從上面的開發流程中,我們可以看到入口都在 web.xml 中。其中有一個監聽器和一個 Servlet,以及初始化參數 dubbo.xml 和 mvc.xml。在 Spring Boot 出現之前,Spring 通常使用 XML 配置方式描述 Bean,或者在 XML 中配置註解驅動和上下文掃描方式解析 Bean。因此,我們可以看出這裡有兩個 XML 文件。經過分析源代碼,我們整理出了以下 XML 標簽解析到 Bean 解析的流程。如下:

  1. 由 Tomcat 啟動載入web.xml並通過監聽器和 Servlet 讓 Spring 載入 XML 並解析。

  2. 直到BeanDefinitionParserDelegate#parseCustomElement開始解析自定義標簽,找到mvc:xxxdubbo:xxx標簽找到了 XML 命名空間。

  3. DefaultNamespaceHandlerResolver處理邏輯:以懶載入方式載入所有 jar 中META-INF/spring.handlers(路徑必須得是這個)並緩存到handlerMappings,通過命名空間 URI 找到與之對應的處理類,SpringMVC 與 Dubbo 命名空間處理類分別為MvcNamespaceHandlerDubboNamespaceHandler

  4. MvcNamespaceHandlerDubboNamespaceHandler都分別實現了NamespaceHandler#init方法,內容如下:


    init方法將 SpringMVC 和 Dubbo 標簽對應的 BeanDefinitionParser 註冊到了 NamespaceHandlerSupport#parsers 中。在上一步中,DefaultNamespaceHandlerResolver 根據標簽獲取到了該標簽的 BeanDefinitionParser,從而將對應的 Bean 註冊到了 Spring IOC 容器中。註冊邏輯不是本文的重點,這裡就不再贅述。至此,SpringMVC 和 Dubbo 的載入流程已經完成。

從以上載入流程中,我們可以看出,在沒有 Spring Boot 之前,Spring 主要依靠 XML 配置來啟動。它會載入 XML 中的自定義標簽,找到對應的命名空間,然後掃描 classpath 下的 META-INF/spring.handlers,找到命名空間處理類來解析當前標簽。

Spring Boot

環境依賴

  • JDK 1.8

  • Maven 3

  • spring-boot 2.6.9

  • dubbo 2.7.23

開發流程

  1. 目錄結構與 Mavendemo-spring-boot依賴內容

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <!-- dubbo -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.23</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-x-discovery</artifactId>
            <version>5.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.8.0</version>
        </dependency>
    
        <dependency>
            <groupId>com.demo</groupId>
            <artifactId>demo-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
    
    
  2. 應用程式入口DemoSpringBootApplication

    @SpringBootApplication
    public class DemoSpringBootApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoSpringBootApplication.class, args);
        }
    
    }
    
    
  3. application.yml文件內容只有 Dubbo 的配置

    dubbo:
      application:
        name: demo-provider
      protocol:
        port: 20880
        name: dubbo
      registry:
        address: zookeeper://127.0.0.1:2181
    
    
  4. 編寫 Controller 介面與 Dubbo RPC 介面

    package com.demo.controller;
    
    import com.demo.dto.HelloEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @GetMapping(value = "/say/hello")
        public HelloEntity sayHello() {
            return new HelloEntity("Hello World");
        }
    
    }
    
    
    package com.demo.provider;
    
    import com.demo.api.DemoService;
    import com.demo.dto.HelloEntity;
    
    @DubboService 
    public class DemoServiceImpl implements DemoService {
    
        @Override
        public HelloEntity sayHello() {
            return new HelloEntity("Hello World");
        }
    }
    
    
  5. 由於spring-boot-starter-web已經內嵌 tomcat ,只需要直接運行DemoSpringBootApplication#main方法即可運行應用

剖析

從開發流程上沒辦法第一時間找到解析入口,唯一入口就是在DemoSpringBootApplication,經過源代碼分析得出以下流程:

  1. 應用DemoSpringBootApplication類上有@SpringBootApplication註解,而該註解由以下三個註解組成:

    • @SpringBootConfiguration,標註當前類為一個配置類,與[@Configuration](https://my.oschina.net/pointdance)註解功能一致 ,被[@Configuration](https://my.oschina.net/pointdance)註解的類對應 Spring 的 XML 版的容器。

    • @EnableAutoConfiguration,開啟啟動自動裝配的關鍵,由@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)組成

    • @ComponentScan,按照當前類路徑掃描含有@Service@Controller等等註解的類,等同於 Spring XML 中的context:component-scan

  2. Spring Boot 自動裝配由@EnableAutoConfiguration導入的AutoConfigurationImportSelector類,會調用SpringFactoriesLoader#loadFactoryNames從 ClassPath 下掃描所有 jar 包的META-INF/spring.factories內容,由於傳入的EnableAutoConfiguration.class,只會返回org.springframework.boot.autoconfigure.EnableAutoConfigurationkey 的值,得到一個全限定類名字元串數組configurations

  3. configurations經過去重與聲明式排除後,會進行以下進行過濾自動裝配:

    configurations = getConfigurationClassFilter().filter(configurations)
    
    

    分成兩部分:獲取過濾器和執行過濾。

    • getConfigurationClassFilter(),也是通過SpringFactoriesLoader#loadFactoryNamesMETA-INF/spring.factories找到 Key 為org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的值,目前只有:OnBeanConditionOnClassConditionOnClassCondition三個過濾器。

    • 執行過濾,會根據配置類上含有@ConditionOnBean@ConditionalOnClass@ConditionalOnWebApplication等等條件註解來過濾掉部分配置類。比如WebMvcAutoConfiguration指定需要在@ConditionOnWebApplication下才生效。

  4. 在引入各類 Configuration 的配置類後,配置類結合@Bean來完成 Spring Bean 解析和註入,同時 Spring Boot 還提供了許多@ConditionalXXX給開發者完成靈活註入。

以上就是 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 會有一定不足:

  • 不能做到按需載入,需要遍歷所有的實現並實例化,然後在迴圈中找到所需要的實現。

  • 多個併發多線程使用ServiceLoader類的實例不安全

  • 載入不到實現類時拋出並不是真正原因的異常,錯誤難定位。

Spring SPI

Spring SPI 沿用了 Java SPI ,但是在實現上和 Java SPI 存在差異,但是核心機制相同,在不修改 Spring 源碼前提下,可以做到對 Spring 框架的擴展開發。

  • 在 Spring XML 中,由DefaultNamespaceHandlerResolver負責解析spring.handlers生成 namespaceUri 和 NamespaceHandler 名稱的映射,等有需要時在進行實例化。

  • 在 Spring Boot 中,由SpringFactoriesLoader負責解析spring.factories文件,並將指定介面的所有實現類/全限定類名返回。

Spring Boot 2.7.0

在本文中 Spring Boot 自動裝配使用了 SPI 來載入到EnableAutoConfiguration所指定的自動裝配的類名,但在 Spring Boot2.7.0之後自動裝配 SPI 機制有所改動,META-INF/spring.factories將廢棄,同時在 Spring Boot 3 以上會將相關代碼移除,改動如下:

  • 新的註解:@AutoConfiguration代替@Configuration

  • 讀取自動裝配的類文件位置改為:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,並且實現類全限定類名按照一行一個

  • org.springframework.boot.context.annotation.ImportCandidates#load負責解析META-INF/spring/%s.imports,其中%s是介面名的占位符

三、Spring Boot Stater 實踐

在使用spring-boot-starter-jdbc或者spring-boot-starter-jpa等資料庫操作時,通常會引入一個資料庫數據源連接池,比如:HikariCPDBCP等,同時可隨意切換依賴而不需要去更改任何業務代碼,開發人員也無需關註底層實現,在此我們自定義一個 Starter 同時也實現這種相容。因為我們以開發一個分散式鎖的 Starter 並擁有多個實現:Zookeeper、Redis。 在此使用 Spring Boot 2.6.9 版本。

開發

項目結構與 Maven 依賴

└── src
    ├── main
    │   ├── java
    │   │   └── com.demo.distributed.lock
    │   │      ├── api
    │   │      │   ├── DistributedLock.java
    │   │      │   └── LockInfo.java
    │   │      ├── autoconfigure
    │   │      │   ├── DistributedLockAutoConfiguration.java
    │   │      │   └── DistributedLockProperties.java
    │   │      ├── redis
    │   │      │   └── RedisDistributedLockImpl.java
    │   │      └── zookeeper
    │   │          └── ZookeeperDistributedLockImpl.java
    │   └── resources
    │       └── META-INF
    │           └── spring.factories

<dependencies>
    <!-- Spring Boot 自動裝配註解 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>

    <!-- 生成 META-INF/spring-configuration-metadata.json -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>

    
    <!-- Zookeeper -->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>5.1.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>5.1.0</version>
        <scope>provided</scope>
    </dependency>

    <!-- Redis -->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.23.1</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

在依賴里可以看到 Zookeeper 和 Redis 依賴關係被設置為provided,作用為編譯與測試階段使用,不會隨著項目一起發佈。即打包時不會帶上該依賴。該設置在 Spring Boot Starter 作用較大。

分散式鎖介面與實現

介面

public interface DistributedLock {

    /**
     * 加鎖
     */
    LockInfo tryLock(String key, long expire, long waitTime);

    /**
     * 釋放鎖
     */
    boolean release(LockInfo lock);

}

Redis 實現

public class RedisDistributedLockImpl implements DistributedLock {

    private final RedissonClient client;

    public RedisDistributedLockImpl(RedissonClient client) {
        this.client = client;
    }

    @Override
    public LockInfo tryLock(String key, long expire, long waitTime) {
        //do something
        return null;
    }

    @Override
    public boolean release(LockInfo lock) {
        //do something
        return true;
    }
}

Zookeeper 實現

public class ZookeeperDistributedLockImpl implements DistributedLock {

    private final CuratorFramework client;

    public ZookeeperDistributedLockImpl(CuratorFramework client) {
        this.client = client;
    }

    @Override
    public LockInfo tryLock(String key, long expire, long waitTime) {
        return null;
    }

    @Override
    public boolean release(LockInfo lock) {
        return false;
    }
} 

DistributedLockAutoConfiguration 配置類

@EnableConfigurationProperties(DistributedLockProperties.class)
@Import({DistributedLockAutoConfiguration.Zookeeper.class, DistributedLockAutoConfiguration.Redis.class})
public class DistributedLockAutoConfiguration {

    @Configuration
    @ConditionalOnClass(CuratorFramework.class)
    @ConditionalOnMissingBean(DistributedLock.class)
    @ConditionalOnProperty(name = "distributed.lock.type", havingValue = "zookeeper",
            matchIfMissing = true)
    static class Zookeeper {

        @Bean
        CuratorFramework curatorFramework(DistributedLockProperties properties) {
            //build CuratorFramework client
            return null;
        }


        @Bean
        ZookeeperDistributedLockImpl zookeeperDistributedLock(CuratorFramework client) {
            return new ZookeeperDistributedLockImpl(client);
        }
    }


    @Configuration
    @ConditionalOnClass(RedissonClient.class)
    @ConditionalOnMissingBean(DistributedLock.class)
    @ConditionalOnProperty(name = "distributed.lock.type", havingValue = "redis",
            matchIfMissing = true)
    static class Redis {

        @Bean
        RedissonClient redissonClient(DistributedLockProperties properties) {
            //build RedissonClient client
            return null;
        }

        @Bean
        RedisDistributedLockImpl redisDistributedLock(RedissonClient client) {
            return new RedisDistributedLockImpl(client);
        }
    }
}

  • @EnableConfigurationProperties(DistributedLockProperties.class)開啟配置類 Properties 信息,會將配置文件里的信息註入 Properties 類里。

  • @Configuration配置註解

  • @ConditionalOnClass(CuratorFramework.class)條件註解,要求存在CuratorFramework類當前配置類才生效,Redis 的子配置類同理。

  • @ConditionalOnMissingBean(DistributedLock.class)條件註解,Spring 不存在DistributedLockBean 當前配置類才生效,Redis 的子配置類同理。

  • @ConditionalOnProperty(name = "distributed.lock.type", havingValue = "zookeeper", matchIfMissing = true)條件註解,這裡判斷配置文件distributed.lock.type等於zookeeper才生效,當如果沒配置則預設當做zookeeper,Redis 的子配置類同理。

  • @Bean將方法返回的 Bean 註入到 Spring IOC 容器里,方法入參中含依賴的 Bean

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.demo.distributed.lock.autoconfigure.DistributedLockAutoConfiguration

我們只需要將該文件放到resource/META-INF/spring.factories下,就會被 Spring Boot 載入,這也是 Spring Boot 的約定大於配置的思想。

使用

Maven 依賴關係

<dependencies>
    <dependency>
        <groupId>com.demo</groupId>
        <artifactId>distributed-lock-spring-boot-starter</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </dependency>
</dependencies>

<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <dependencies>
            <!-- Redis -->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.23.1</version>
            </dependency>
        </dependencies>
    </profile>
    <profile>
        <id>test</id>
        <dependencies>
            <dependency>
                <groupId>org.apache.curator</groupId>
                <artifactId>curator-framework</artifactId>
                <version>5.1.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.curator</groupId>
                <artifactId>curator-recipes</artifactId>
                <version>5.1.0</version>
            </dependency>
        </dependencies>
    </profile>
</profiles>

此處結合 Maven profile 功能按照不同環境依賴不同分散式鎖底層實現,同時 Spring Boot 也提供了 Spring Boot Profile 載入不同配置,可以從開發、測試、生產環境使用不同底層了,同時 Maven profile 可以根據-P指定載入不同的依賴進行打包,解決了不同環境使用不同分散式鎖實現。

代碼使用

private final DistributedLock distributedLock;

public DemoServiceImpl(DistributedLock distributedLock) {
    this.distributedLock = distributedLock;
}

public void test() {
    LockInfo lock = null;
    try {
        lock = distributedLock.tryLock("demo", 1000, 1000);
        //do something
    } finally {
        if (lock != null) {
            distributedLock.release(lock);
        }
    }
}

業務代碼中由於依賴的是介面,結合 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 來完成開發。這樣,我們可以相容多種分散式鎖實現,並且在不同環境下使用不同的分散式鎖實現,而無需修改業務代碼。

作者:京東零售 陳炎清

來源:京東雲開發者社區


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大家好,我是棧長。 經過 Spring Cloud Alibaba 2022 的第一個候選版本 2022.0.0.0-RC1 發佈 7 個多月後,中間還有一個 2022.0.0.0-RC2 版本,就在前幾天,**Spring Cloud Alibaba 2022.0.0.0 正式版** 終於正式發佈 ...
  • 在 Protocol Buffers (protobuf) 中,可以使用特定的選項來指定生成的 JSON 標簽。通過在消息定義中使用 `[(json_name)]` 選項,可以控制生成的 JSON 欄位名稱。這樣可以確保 Protocol Buffers 和 JSON 之間的互操作性。 下麵是一個示 ...
  • ## 一、問題是怎麼發現的 最近有個新系統開發完成後要上線,由於系統調用量很大,所以先對核心介面進行了一次壓力測試,由於核心介面中基本上只有純記憶體運算,所以預估核心介面的壓測QPS能夠達到上千。 壓測容器配置:4C8G 先從10個併發開始進行發壓,結果cpu一下就飆升到了100%,但是核心介面的qp ...
  • 編程基礎常識 一、註釋 1、對代碼的說明與解釋,它不會被編譯執行,也不會顯示在編譯結果中 2、註釋分為:單行註釋和多行註釋 3、用#號開始,例如:#這是我的第一個python程式 4、註釋可以寫在單獨一行,也可以寫在一句代碼後面 5、不想執行編譯,又不能刪除的代碼,可以先用#註釋掉,代碼批量註釋用C ...
  • 要解決多線程併發問題,常見的手段無非就幾種。加鎖,如使用synchronized,ReentrantLock,加鎖可以限制資源只能被一個線程訪問;CAS機制,如AtomicInterger,AtomicBoolean等原子類,通過自旋的方式來嘗試修改資源;還有本次我們要介紹的ThreadLocal類 ...
  • MQ(Message Queue)作為一種用於實現非同步通信的技術,具有重要的作用和應用場景。在面試過程中,MQ相關的問題經常被問到,因此瞭解MQ的用途和設計原則是必不可少的。本文總結了MQ的常見面試題,包括MQ的作用、產品選型、消息不丟失的保證、消息消費的冪等性、消息順序的保證、消息的高效讀寫、分佈... ...
  • 狀態機是有限狀態自動機的簡稱,是現實事物運行規則抽象而成的一個數學模型。狀態機,也就是 State Machine ,不是指一臺實際機器,而是指一個數學模型。說白了,一般就是指一張狀態轉換圖。 ...
  • EOF,為End Of File的縮寫,通常在文本的最後存在此字元表示資料結束。 在微軟的DOS和Windows中,讀取數據時終端不會產生EOF。此時,應用程式知道數據源是一個終端(或者其它“字元設備”),並將一個已知的保留的字元或序列解釋為文件結束的指明;最普遍地說,它是ASCII碼中的替換字元( ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...