引言

对于 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依赖内容

    1. <dependencies>
    2. <!-- SpringMVC -->
    3. <dependency>
    4. <groupId>org.springframework</groupId>
    5. <artifactId>spring-webmvc</artifactId>
    6. <version>4.3.30.RELEASE</version>
    7. </dependency>
    8. <dependency>
    9. <groupId>javax.servlet</groupId>
    10. <artifactId>servlet-api</artifactId>
    11. <version>2.5</version>
    12. </dependency>
    13. <!-- 此处需要导入databind包即可, jackson-annotations、jackson-core都不需要显示自己的导入了-->
    14. <dependency>
    15. <groupId>com.fasterxml.jackson.core</groupId>
    16. <artifactId>jackson-databind</artifactId>
    17. <version>2.9.8</version>
    18. </dependency>
    19. <!-- Dubbo -->
    20. <dependency>
    21. <groupId>org.apache.dubbo</groupId>
    22. <artifactId>dubbo</artifactId>
    23. <version>2.7.23</version>
    24. </dependency>
    25. <dependency>
    26. <groupId>org.apache.curator</groupId>
    27. <artifactId>curator-x-discovery</artifactId>
    28. <version>5.1.0</version>
    29. </dependency>
    30. <dependency>
    31. <groupId>org.apache.zookeeper</groupId>
    32. <artifactId>zookeeper</artifactId>
    33. <version>3.8.0</version>
    34. </dependency>
    35. <!-- Demo API -->
    36. <dependency>
    37. <groupId>com.demo</groupId>
    38. <artifactId>demo-api</artifactId>
    39. <version>1.0-SNAPSHOT</version>
    40. </dependency>
    41. </dependencies>
  2. 由于在 Spring XML 下还需要依靠 Java Web 和 Web 容器运行,还需要web/WEB-INF/web.xmlWeb 配置文件,内容配置了 SpringMVC 入口

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    5. version="4.0">
    6. <!-- Spring监听器 -->
    7. <listener>
    8. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    9. </listener>
    10. <context-param>
    11. <param-name>contextConfigLocation</param-name>
    12. <param-value>classpath:dubbo.xml</param-value>
    13. </context-param>
    14. <servlet>
    15. <servlet-name>springmvc</servlet-name>
    16. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    17. <init-param>
    18. <param-name>contextConfigLocation</param-name>
    19. <param-value>classpath:mvc.xml</param-value>
    20. </init-param>
    21. </servlet>
    22. <servlet-mapping>
    23. <servlet-name>springmvc</servlet-name>
    24. <url-pattern>/</url-pattern>
    25. </servlet-mapping>
    26. </web-app>
  3. SpringMVC 配置文件mvc.xml与 Dubbo 配置文件dubbo.xml

    1. <?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. <!-- 开启 MVC 注解驱动 -->
    10. <mvc:annotation-driven/>
    11. <!-- 访问静态资源 -->
    12. <mvc:default-servlet-handler/>
    13. </beans>
    1. <?xml version="1.0" encoding="utf-8" ?>
    2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    6. <!-- Dubbo -->
    7. <dubbo:application name="demo-service"/>
    8. <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
    9. <dubbo:protocol name="dubbo" port="20880"/>
    10. <bean id="demoServiceImpl" class="com.demo.provider.DemoServiceImpl"/>
    11. <dubbo:service interface="com.demo.api.DemoService" ref="demoServiceImpl"/>
    12. </beans>
  4. 编写 Controller 接口与 Dubbo RPC 接口

    1. package com.demo.controller;
    2. import org.springframework.web.bind.annotation.GetMapping;
    3. import org.springframework.web.bind.annotation.RestController;
    4. @RestController
    5. public class HelloController {
    6. @GetMapping(value = "/say/hello")
    7. public HelloEntity sayHello() {
    8. return new HelloEntity("Hello World");
    9. }
    10. }
    1. package com.demo.provider;
    2. import com.demo.api.DemoService;
    3. import com.demo.dto.HelloEntity;
    4. public class DemoServiceImpl implements DemoService {
    5. @Override
    6. public HelloEntity sayHello() {
    7. return new HelloEntity("Hello World");
    8. }
    9. }
  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依赖内容

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-web</artifactId>
    5. </dependency>
    6. <!-- dubbo -->
    7. <dependency>
    8. <groupId>org.apache.dubbo</groupId>
    9. <artifactId>dubbo-spring-boot-starter</artifactId>
    10. <version>2.7.23</version>
    11. </dependency>
    12. <dependency>
    13. <groupId>org.apache.curator</groupId>
    14. <artifactId>curator-x-discovery</artifactId>
    15. <version>5.1.0</version>
    16. </dependency>
    17. <dependency>
    18. <groupId>org.apache.zookeeper</groupId>
    19. <artifactId>zookeeper</artifactId>
    20. <version>3.8.0</version>
    21. </dependency>
    22. <dependency>
    23. <groupId>com.demo</groupId>
    24. <artifactId>demo-api</artifactId>
    25. <version>1.0-SNAPSHOT</version>
    26. </dependency>
    27. </dependencies>
  2. 应用程序入口DemoSpringBootApplication

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

    1. dubbo:
    2. application:
    3. name: demo-provider
    4. protocol:
    5. port: 20880
    6. name: dubbo
    7. registry:
    8. address: zookeeper://127.0.0.1:2181
  4. 编写 Controller 接口与 Dubbo RPC 接口

    1. package com.demo.controller;
    2. import com.demo.dto.HelloEntity;
    3. import org.springframework.web.bind.annotation.GetMapping;
    4. import org.springframework.web.bind.annotation.RestController;
    5. @RestController
    6. public class HelloController {
    7. @GetMapping(value = "/say/hello")
    8. public HelloEntity sayHello() {
    9. return new HelloEntity("Hello World");
    10. }
    11. }
    1. package com.demo.provider;
    2. import com.demo.api.DemoService;
    3. import com.demo.dto.HelloEntity;
    4. @DubboService
    5. public class DemoServiceImpl implements DemoService {
    6. @Override
    7. public HelloEntity sayHello() {
    8. return new HelloEntity("Hello World");
    9. }
    10. }
  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经过去重与声明式排除后,会进行以下进行过滤自动装配:

    1. 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 依赖

  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. <dependencies>
  2. <!-- Spring Boot 自动装配注解 -->
  3. <dependency>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-autoconfigure</artifactId>
  6. </dependency>
  7. <!-- 生成 META-INF/spring-configuration-metadata.json -->
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-configuration-processor</artifactId>
  11. <optional>true</optional>
  12. </dependency>
  13. <!-- Zookeeper -->
  14. <dependency>
  15. <groupId>org.apache.curator</groupId>
  16. <artifactId>curator-framework</artifactId>
  17. <version>5.1.0</version>
  18. <scope>provided</scope>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.apache.curator</groupId>
  22. <artifactId>curator-recipes</artifactId>
  23. <version>5.1.0</version>
  24. <scope>provided</scope>
  25. </dependency>
  26. <!-- Redis -->
  27. <dependency>
  28. <groupId>org.redisson</groupId>
  29. <artifactId>redisson</artifactId>
  30. <version>3.23.1</version>
  31. <scope>provided</scope>
  32. </dependency>
  33. </dependencies>

在依赖里可以看到 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. }
  • @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

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

我们只需要将该文件放到resource/META-INF/spring.factories下,就会被 Spring Boot 加载,这也是 Spring Boot 的约定大于配置的思想。

使用

Maven 依赖关系

  1. <dependencies>
  2. <dependency>
  3. <groupId>com.demo</groupId>
  4. <artifactId>distributed-lock-spring-boot-starter</artifactId>
  5. <version>1.0.0-SNAPSHOT</version>
  6. </dependency>
  7. </dependencies>
  8. <profiles>
  9. <profile>
  10. <id>dev</id>
  11. <activation>
  12. <activeByDefault>true</activeByDefault>
  13. </activation>
  14. <dependencies>
  15. <!-- Redis -->
  16. <dependency>
  17. <groupId>org.redisson</groupId>
  18. <artifactId>redisson</artifactId>
  19. <version>3.23.1</version>
  20. </dependency>
  21. </dependencies>
  22. </profile>
  23. <profile>
  24. <id>test</id>
  25. <dependencies>
  26. <dependency>
  27. <groupId>org.apache.curator</groupId>
  28. <artifactId>curator-framework</artifactId>
  29. <version>5.1.0</version>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.apache.curator</groupId>
  33. <artifactId>curator-recipes</artifactId>
  34. <version>5.1.0</version>
  35. </dependency>
  36. </dependencies>
  37. </profile>
  38. </profiles>

此处结合 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 来完成开发。这样,我们可以兼容多种分布式锁实现,并且在不同环境下使用不同的分布式锁实现,而无需修改业务代码。

作者:京东零售 陈炎清

来源:京东云开发者社区

Spring Boot Starter 剖析与实践的更多相关文章

  1. 自定义的Spring Boot starter如何设置自动配置注解

    本文首发于个人网站: 在Spring Boot实战之定制自己的starter一文最后提到,触发Spring Boot的配置过程有两种方法: spring.factories:由Spring Boot触 ...

  2. Spring Boot Admin简介及实践

    问题 在若干年前的单体应用时代,我们可以相对轻松地对整个业务项目进行健康检查.指标监控.配置管理等等项目治理.如今随着微服务的发展,我们将大型单体应用按业务模型进行划分,以此形成众多小而自治的微服务, ...

  3. Spring Boot Starter 介绍

    http://www.baeldung.com/spring-boot-starters 作者:baeldung 译者:http://oopsguy.com 1.概述 依赖管理是任何复杂项目的关键部分 ...

  4. spring -boot s-tarter 详解

    Starter POMs是可以包含到应用中的一个方便的依赖关系描述符集合.你可以获取所有Spring及相关技术的一站式服务,而不需要翻阅示例代码,拷贝粘贴大量的依赖描述符.例如,如果你想使用Sprin ...

  5. Spring Boot (一): Spring Boot starter自定义

    前些日子在公司接触了spring boot和spring cloud,有感于其大大简化了spring的配置过程,十分方便使用者快速构建项目,而且拥有丰富的starter供开发者使用.但是由于其自动化配 ...

  6. SpringBoot 之Spring Boot Starter依赖包及作用

    Spring Boot 之Spring Boot Starter依赖包及作用 spring-boot-starter 这是Spring Boot的核心启动器,包含了自动配置.日志和YAML. spri ...

  7. Spring boot starter pom的依赖关系说明

    Spring Boot 通过starter依赖为项目的依赖管理提供帮助.starter依赖起始就是特殊的maven依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖. sp ...

  8. Spring Boot Starter列表

    转自:http://blog.sina.com.cn/s/blog_798f713f0102wiy5.html Spring Boot Starter 基本的一共有43种,具体如下: 1)spring ...

  9. 创建自己的Spring Boot Starter

    抽取通用模块作为项目的一个spring boot starter.可参照mybatis的写法. IDEA创建Empty Project并添加如下2个module,一个基本maven模块,另一个引入sp ...

  10. 自己写spring boot starter

    自己写spring boot starter 学习了:<spring boot实战>汪云飞著 6.5.4节 pom.xml <project xmlns="http://m ...

随机推荐

  1. Apache hudi 核心功能点分析

    Hudi 文中部分代码对应 0.14.0 版本 发展背景 初始的需求是Uber公司会有很多记录级别的更新场景,Hudi 在Uber 内部主要的一个场景,就是乘客打车下单和司机接单的匹配,乘客和司机分别 ...

  2. 理解 React 中的 useEffect、useMemo 与 useCallback

    useEffect 先理解 useEffect 有助于学习 useMemo 和 useCallback.因为 useMemo 和 useCallback 的实现实际上都是基于 useEffect 的. ...

  3. 2022-12-02:有a块草莓蛋糕,有b块芝士蛋糕,两人轮流拿蛋糕, 每次不管是谁只能选择在草莓蛋糕和芝士蛋糕中拿一种, 拿的数量在1~m之间随意, 谁先拿完最后的蛋糕谁赢。 返回先手赢还是后手赢。

    2022-12-02:有a块草莓蛋糕,有b块芝士蛋糕,两人轮流拿蛋糕, 每次不管是谁只能选择在草莓蛋糕和芝士蛋糕中拿一种, 拿的数量在1~m之间随意, 谁先拿完最后的蛋糕谁赢. 返回先手赢还是后手赢. ...

  4. 2021-08-14:给定两个字符串S和T,返回S的所有子序列中有多少个子序列的字面值等于T。

    2021-08-14:给定两个字符串S和T,返回S的所有子序列中有多少个子序列的字面值等于T. 福大大 答案2021-08-14: 样本对应模型. 时间复杂度:O(N^2). 空间复杂度:O(N^2) ...

  5. 「P4」试下1个半月能不能水出个毕设

    期间的一些感想 对于这个时间的把控,前一个月实际上我什么都没做,现在都堆在最后的半个月了 在做毕业设计的阶段,我总结了一个教训,就是:「慢就是快」,我想这句话可能对我以后的学习都会有比较大的影响.我是 ...

  6. SCI 投稿中像素、DPI、图片分辨率的一些知识

    最近在学习 Linux 命令行下的 ImageMagick 图像处理,对图像本身的一些概念有点懵,搜集整理了一点资料,仅供自己和大家学习与参考. SCI 期刊对分辨率大多都有一定的要求,例如一段来自 ...

  7. 基于飞桨paddlespeech训练中文唤醒词模型

    飞桨Paddlespeech中的语音唤醒是基于hey_snips数据集做的.Hey_snips数据集是英文唤醒词,对于中国人来说,最好是中文唤醒词.经过一番尝试,我发现它也能训练中文唤醒词,于是我决定 ...

  8. 【Photoshop】切图保存小坑(选择png格式得到gif问题)

    默认情况下:Photoshop 导出切片为[GIF]格式 当你很嗨皮的把[GIF]调整为[PNG]或[JPG]格式,并保存时: 你会发现,自己的图片格式莫名其妙还是[GIF]: 但,我们的期望是: 原 ...

  9. Rust的类型系统

    Rust的类型系统 类型于20世纪50年代被FORTRAN语言引入,其相关的理论和应用已经发展得非常成熟.现在,类型系统已经成为了各大编程语言的核心基础. 通用基础 所谓类型,就是对表示信息的值进行的 ...

  10. java中基本数据类型和包装数据类型

    基本数据类型和包装数据类型在 Java 中有着重要的区别和联系,对于 Java 程序员来说,熟悉这两种数据类型的特点和使用方法是非常必要的. 基本数据类型 Java 中的基本数据类型一共有 8 种,分 ...