Spring Cloud Alibaba 基础入门实践

Spring Cloud Alibaba微服务开发最佳实践:

  1. Spring Cloud Gateway:网关
  2. Nacos:服务注册和配置中心
  3. Sentinel:熔断限流
  4. Seata:分布式事务
  5. RocketMQ/RabbitMQ/Kafka:消息队列,削峰填谷

一、Spring boot、Spring Cloud、Spring Cloud Alibaba 三者有严格的兼容关系,版本必须匹配:

xml 复制代码
  <properties>
     <!-- 指定 JDK 版本为 17,这是 Spring Boot 3.x 的硬性要求 [citation:3][citation:7] -->
     <maven.compiler.source>17</maven.compiler.source>
     <maven.compiler.target>17</maven.compiler.target>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <!-- 与 Spring Boot 3.2.12 兼容 -->
       <jackson.version>2.15.3</jackson.version>
     <!-- 核心框架版本 - 三者版本必须严格兼容 -->
     <!-- Spring Boot 3.2.x 是推荐版本 [citation:1][citation:9] -->
     <spring.boot.version>3.2.12</spring.boot.version>
     <!-- Spring Cloud 2023.0.x 系列 [citation:1][citation:9] -->
     <spring.cloud.version>2023.0.3</spring.cloud.version>
     <!-- Spring Cloud Alibaba 2023.0.x 系列,需与 Spring Cloud 2023.0.x 匹配 [citation:2][citation:9][citation:10] -->
     <spring.cloud.alibaba.version>2023.0.1.0</spring.cloud.alibaba.version>
  </properties>
   ......
   ......
   <dependencyManagement>
 <!-- 1. 导入 Spring Cloud 的依赖管理 BOM [citation:1][citation:5] -->
           <dependency>
               <groupId>org.springframework.cloud</groupId>
               <artifactId>spring-cloud-dependencies</artifactId>
               <version>${spring.cloud.version}</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
           <!-- 2. 导入 Spring Cloud Alibaba 的依赖管理 BOM [citation:6][citation:9] -->
           <dependency>
               <groupId>com.alibaba.cloud</groupId>
               <artifactId>spring-cloud-alibaba-dependencies</artifactId>
               <version>${spring.cloud.alibaba.version}</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
       </dependencies>
   </dependencyManagement>
    ......
    ......

二、Nacos的服务注册与发现和配置中心

  • 为什么需要服务注册与发现?
    在微服务架构中会出现跨进程跨主机的服务调用,如何让这个服务之间能互相发现像单体式应用一样提供统一对外的服务调用能力是微服务框架层面需要重点解决的核心问题之一。 在 Spring Cloud 生态中,采用了服务注册与发现模型,来实现微服务之间的互相发现与调用。
  • 为什么需要配置中心?
  • 传统的开发方式中,这些配置信息通常硬编码到应用程序的代码中,与程序代码一起打包和部署。然而,这种方式有很多缺点,比如配置信息不易维护,只要修改配置就得重新构建和部署等。当有大量应用程序需要管理时,手动维护配置文件会变得非常困难。分布式配置中心提供了一个集中管理和分发配置信息的解决方案。

1)、安装nacos server

2)、mvn配置

复制代码
    <!-- Nacos 服务注册与发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!-- Nacos 配置中心 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

      <!-- Spring Cloud Bootstrap 支持 (Spring Boot 3.x 必需) -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>

3)、bootstrap.yml配置(引导阶段读取,会被application.yml 覆盖相同内容)

properties 复制代码
spring:
  application:
    name: provider-server
  cloud:
    nacos:
      # Nacos 服务注册中心地址
      discovery:
        server-addr: http://127.0.0.1:8848
        enabled: true
        # 禁用服务注册到 Nacos。如果不想当前应用的服务被注册到服务中心则开启
        # register-enabled: false
      # Nacos 配置中心地址
      config:
        server-addr: http://127.0.0.1:8848
        file-extension: yaml
        # 共享配置
        shared-configs:
          - data-id: common.yaml
            refresh: true
        # 优先使用共享配置还是应用独立配置
        override-configs: false
        # 是否支持配置热更新
        refresh-enabled: true
        # 加密配置 (如需)
        # encrypt-key: your-encrypt-key
  # 启用配置导入(优先级高于本地的application.yml配置)
  config:
    import:
      - optional:nacos:provider-server.yaml
      - optional:nacos:common.yaml

4)、application.yml配置 (应用启动阶段读取)

properties 复制代码
server:
  port: 8081

spring:
  application:
    name: provider-server

5)、启动入口添加注解:@EnableDiscoveryClient

java 复制代码
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

6)、启动后自动注册服务到nacos

java 复制代码
@RestController
@RequestMapping("/sysUser")
public class SysUserController {
    private final ISysUserService sysUserService;

    public SysUserController(ISysUserService sysUserService) {
        this.sysUserService = sysUserService;
    }

    @GetMapping("/selectById/{id}")
    public SysUser selectById(@PathVariable("id") Long id) {
        SysUser user = new SysUser();
        user.setNickName("我是服务端的人");
        return user;
    }
}

7)、nacos配置热更新(注意:不是所有的配置都能热更新,比如数据库配置、日志配置等是在启动阶段加载的,所以热更新是无效的,必须重启程序才会生效)

  • 常用方式一:@RefreshScope +@Value
java 复制代码
@RestController
@RefreshScope  // 关键注解:支持热更新
public class OrderController {
    
    @Value("${order.timeout:3000}")
    private int timeout;
    
    @Value("${order.discount.enabled:false}")
    private boolean discountEnabled;
    
    @GetMapping("/order/config")
    public Map<String, Object> getConfig() {
        return Map.of(
            "timeout", timeout,
            "discountEnabled", discountEnabled
        );
    }
}
  • 常用方式二:@ConfigurationProperties
java 复制代码
@Component
@ConfigurationProperties(prefix = "order")  // 自动映射 order.* 配置
@Data
public class OrderProperties {
    private int timeout = 3000;
    private Discount discount = new Discount();
    
    @Data
    public static class Discount {
        private boolean enabled = false;
        private double rate = 1.0;
    }
}

@RestController
public class OrderController {
    
    @Autowired
    private OrderProperties orderProperties;
    
    @GetMapping("/order/config")
    public OrderProperties getConfig() {
        // 配置更新后,orderProperties 会自动更新(无需 @RefreshScope)
        return orderProperties;
    }
}

三、OpenFeign远程调用 + Sentinel限流、熔断、降级

1)、首先需要连接nacos server去发现服务,同时使用nacos config配置中心管理配置

2)、安装Sentinel Server

1)、mvn依赖

xml 复制代码
<!-- Nacos 服务注册与发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!-- Nacos 配置中心 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

      <!-- Spring Cloud Bootstrap 支持 (Spring Boot 3.x 必需) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <!-- Sentinel 流量控制(如需熔断限流) -->
     <dependency>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
     </dependency>
   <!--        openfeigh 远程调用-->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-openfeign</artifactId>
     </dependency>
<!--        feign 通过服务名的方式远程调用时,默认走负载均衡模式,必须添加loadbalancer-->
     <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-loadbalancer</artifactId>
     </dependency>

2)、bootstrap.yml

properties 复制代码
spring:
  application:
    name: customer-server
  cloud:
  # 接入Sentinel server
    sentinel:
      transport:
        client-ip: 127.0.0.1
        port: 8866
        dashboard: http://127.0.0.1:8866
    nacos:
      # Nacos 服务注册中心地址
      discovery:
        server-addr: http://127.0.0.1:8848
        enabled: true
        # 禁用服务注册到 Nacos
        register-enabled: false
      # Nacos 配置中心地址
      config:
        server-addr: http://127.0.0.1:8848
        file-extension: yaml
        # 共享配置
        shared-configs:
          - data-id: common.yaml
            refresh: true
        # 优先使用共享配置还是应用独立配置
        override-configs: false
        # 是否支持配置热更新
        refresh-enabled: true
        # 加密配置 (如需)
        # encrypt-key: your-encrypt-key
  # 启用配置导入
  config:
    import:
      - optional:nacos:common.yaml

3)、application.yml

properties 复制代码
server:
  port: 8082

spring:
  application:
    name: customer-server
    
# 开启feign 对 Sentinel的支持
feign:
  sentinel:
    enabled: true

4)、启动类

java 复制代码
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CustomerApplication {
    public static void main(String[] args) {
        SpringApplication.run(CustomerApplication.class, args);
    }
}

5)、创建FeignClient接口,并通过Sentinel对OpenFeign的整合支持,实现服务降级

  • FeignClient接口
java 复制代码
// 使用openFeign , 自带负载均衡,集成Sentinel服务降级
// name要调用的服务名(注册到nacos中的)、path目标服务的前缀,类似@RequstMapping的path、fallback 目标服务调用失败、熔断、限流等情况导致的服务调用失败的降级类
@FeignClient(name = "provider-server", path = "/sysUser",fallback = SysUserClientFallback.class)
public interface SysUserClient {
    @GetMapping("/selectById/{id}")
    SysUser selectById(@PathVariable("id") Long id);
}
  • 服务降级类:当Feign调用@GetMapping中的服务失败时,会降级到该实现类的内容
java 复制代码
@Component
public class SysUserClientFallback implements SysUserClient{
    @Override
    public SysUser selectById(Long id) {
        SysUser sysUser = new SysUser();
        sysUser.setNickName("fallback user");
        return sysUser;
    }
}

6)、使用远程服务调用:

java 复制代码
@RestController
@RequestMapping("/customer/sysUser")
public class SysUserController {
    private final SysUserClient sysUserClient;
    public SysUserController(SysUserClient sysUserClient) {
        this.sysUserClient = sysUserClient;
    }

    @GetMapping("/selectById/{id}")
    public SysUser selectById(@PathVariable("id") Long id) {
        return sysUserClient.selectById(id);
    }
}

致此,customer的接口请求/customer/sysUser/selectById/1应该能访问到provider-server中对应的接口/sysUser/selectById/1的内容

7)、Sentinel 的限流与熔断,在工程中通过sentinel.transport 的配置接入到Sentinel server时,在Sentinel的控制台就能看到接入的服务,通过控制台即可配置资源的限流、熔断等规则:

四、使用Seata处理分布式事务

  • 在分布式系统中一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务节点上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
  • 在Seata的核心概念中,有三个核心角色:
    • TC Transaction Coordinator 全局事务协调器,管理全局事务状态,由Seata Server充当
    • TM Transaction Manager 发起全局事务,提交或回滚,由服务消费方充当
    • RM Resource Manager 管理分支事务,执行本地事务并上报状态,执行数据库操作的每一个业务方
      整体流程:
md 复制代码
│                                                                  │
│  1. 【创建】                                                      │
│     TM 调用 @GlobalTransactional                                 │
│         ↓                                                        │
│     TC 生成 XID: 192.168.1.100:8091:1234567890                  │
│         ↓                                                        │
│     XID 绑定到当前线程(ThreadLocal)                             │
│                                                                  │
│  2. 【传播】                                                      │
│     服务消费者调用提供者服务                                           │
│         ↓                                                        │
│     Feign 将 XID 放入请求头                                       │
│         ↓                                                        │
│     服务提供者提供者从请求头提取 XID,绑定到本地线程                        │
│                                                                  │
│  3. 【注册】                                                      │
│     提供者服务执行本地事务前                                         │
│         ↓                                                        │
│     RM 向 TC 注册分支事务(携带 XID)                              │
│         ↓                                                        │
│     TC 在 branch_table 记录分支关系                               │
│                                                                  │
│  4. 【执行】                                                      │
│     本地事务执行,数据变更记录到 undo_log(关联 XID)               │
│                                                                  │
│  5. 【提交/回滚】                                                 │
│     所有分支执行完毕                                               │
│         ↓                                                        │
│     TM 通知 TC 提交或回滚(携带 XID)                              │
│         ↓                                                        │
│     TC 根据 XID 查找所有分支,通知各 RM 执行提交或回滚              │
│                                                                  │
│  6. 【清理】                                                      │
│     undo_log 删除(提交时)或使用(回滚时)                        │
│     XID 从线程解绑                                                │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

1)、安装Seata Server

2)、修改数据源及创建Seata数据源代理:

java 复制代码
@Configuration
public class DataSourceConfig {

    /**
     * 创建原始的数据源
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource(){
        return new DruidDataSource();
    }

    /**
     * 使用seata对数据源进行代理,从而进行分布式事务
     * @param dataSource
     * @return
     */
    @Bean
    @Primary
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }
}

3)bootstrap.yml

properties 复制代码
# Seata 配置
seata:
  # Seata 应用 ID
  application-id: ${spring.application.name}
  # 事务组名称
  tx-service-group: default_tx_group
  service:
    # 事务组与集群的映射关系
    vgroup-mapping:
      default_tx_group: default
    # Seata Server 地址
    grouplist:
      default: 127.0.0.1:8091
  # 注册中心配置
  registry:
    type: file
  config:
    type: file

4)、消费者请求开启分布式事务

java 复制代码
@GetMapping("/updateUser/{id}")
@GlobalTransactional(name = "update-user", rollbackFor = Exception.class)
public void updateUser(@PathVariable("id") Long id){
    sysUserClient.updateUserNickName(id);
    sysUserClient.updateUserEmail(id);
}

五、Spring Cloud Stream + Rabbit/RocketMQ/Kafka

1)、Spring Cloud Stream 提供了消息中间件配置的统一抽象,内部有两个概念:Binder 和 Binding

  • Binder: 跟外部消息中间件集成的组件,用来创建 Binding,各消息中间件都有自己的 Binder 实现。
  • Binding: 包括 Input Binding 和 Output Binding。

使用Spring Cloud Stream可以无缝切换不同的消息中间件,而不用去修改对应的业务代码对各中间件的API调用

2)、安装对应的消息中间件,如:RabbitMQRocketMQ

3)、maven依赖

xml 复制代码
<!--        spring cloud stream + RabbitMQ-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>

4)、application.properties

properties 复制代码
spring:
  application:
    name: customer-server
  cloud:
    stream:
      # 配置 Binder(连接 RabbitMQ)
      binders:
        defaultRabbit:
          type: rabbit
          environment:
            spring:
              rabbitmq:
                host: localhost
                port: 5672
                username: guest
                password: guest
                virtual-host: /
      # 配置 Binding(通道与交换机的绑定)
      bindings:
        # 生产者通道
        output:
          destination: demo-exchange      # Exchange 名称
          content-type: application/json  # 消息格式
          binder: defaultRabbit           # 使用的 Binder
        # 消费者通道
        input-in-0:
          destination: demo-exchange
          content-type: application/json
          binder: defaultRabbit
          group: customer-group               # 相同 group 的实例竞争消费,不设置group则广播所有实例

      # RabbitMQ 特有配置
      rabbit:
        bindings:
          output:
            producer:
              exchangeType: topic        # 交换机类型
              routing-key-expression: headers['routeTo']  # 从消息头动态获取路由键
          input-in-0:
            consumer:
              exchangeType: topic
              bindingRoutingKey: info.*    #  # 订阅所有 info.x
              acknowledge-mode: manual    # 手动确认
              bindingRoutingKeyDelimiter: ","
              auto-bind-dlq: true           # 自动创建死信队列
              dlq-name: my-queue.dlq        # 自定义死信队列名称
              republish-to-dlq: true        # 失败消息重新发布到 DLQ
              max-attempts: 3               # 重试次数

5)、创建处理消息的component,包含了消息发送方法、注册处理消费者通道信息的Bean

java 复制代码
@Configuration
public class MyMessageComponent {

    private static final Logger log = LoggerFactory.getLogger(MyMessageComponent.class);

    public static final String OUTPUT_BINDING = "output";
    public static final String INPUT_BINDING = "input";

    private final StreamBridge streamBridge;

    public MyMessageComponent(StreamBridge streamBridge) {
        this.streamBridge = streamBridge;
    }

    public boolean sendMessage(Object payload) {
        return sendMessage(payload, null);
    }

    public boolean sendMessage(Object payload, String routingKey) {
        Message<?> message = routingKey != null
                ? MessageBuilder.withPayload(payload)
                .setHeader("routeTo", routingKey)
                .build()
                : MessageBuilder.withPayload(payload).build();

        boolean result = streamBridge.send(OUTPUT_BINDING, message);
        log.info("消息发送{}: {}", result ? "成功" : "失败", payload);
        return result;
    }

    /**
     * 自动处理所有到input通道的信息
     * @return
     */
    @Bean
    public Consumer<Message<Map<String, Object>>> input() {
        return message -> {
            Map<String, Object> payload = message.getPayload();
            // 获取 路由,用于执行对应逻辑
            String routingKey = (String) message.getHeaders().get("amqp_receivedRoutingKey");
            // 获取 Channel(用于发送 ACK 命令)
            Channel channel = (Channel) message.getHeaders().get("amqp_channel");
            // 获取 deliveryTag(消息的唯一标识)
            Long deliveryTag = (Long) message.getHeaders().get("amqp_deliveryTag");

            try {
                log.info("收到消息:路由=>", routingKey);
                log.info("收到消息:{}", payload);

                if (channel != null && deliveryTag != null) {
                    channel.basicAck(deliveryTag, false);
                    log.info("消息确认成功");
                }
            } catch (Exception e) {
                log.error("消息处理失败", e);
                try {
                    if (channel != null && deliveryTag != null) {
                        channel.basicNack(deliveryTag, false, false);
                        log.info("消息拒绝,不重新入队");
                    }
                } catch (Exception ex) {
                    log.error("消息拒绝失败", ex);
                }
            }
        };
    }
}

使用示例:

java 复制代码
myMessageComponent.sendMessage(user,"user.create");

六、分布式定时任务

@SchedulerLock 注解是一个分布式锁的框架,结合@Scheduled 注解,可以保证任务同一时间,在多个节点上只会执行一次。该框架支持多种分布式锁的实现,比如Jdbc、Zookeeper、Redis等。原理如下:

1)、maven依赖

xml 复制代码
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>6.3.1</version> <!-- 最新版本 -->
</dependency>
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>6.3.1</version>
</dependency>

2)、mysql加表

sql 复制代码
CREATE TABLE shedlock (
    name VARCHAR(64) NOT NULL,          -- 锁名称(对应任务名)
    lock_until TIMESTAMP(3) NOT NULL,   -- 锁失效时间
    locked_at TIMESTAMP(3) NOT NULL,    -- 加锁时间
    locked_by VARCHAR(255) NOT NULL,    -- 锁持有者标识
    PRIMARY KEY (name)
);

3)、在启动类添加注解

java 复制代码
@SpringBootApplication
@EnableScheduling //开启spring调度
@EnableSchedulerLock(defaultLockAtMostFor = "3m") // 开启调度锁
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

4)、创建配置类

java 复制代码
@Configuration
public class ScheduledLockConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public LockProvider lockProvider() {
        return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder()
            .withJdbcTemplate(new JdbcTemplate(dataSource))
            .withTimeZone(TimeZone.getTimeZone("UTC"))
            .build());
    }
}

5)、创建定时任务

java 复制代码
@Component
public class SpringJob {

    /**
     * 每5分钟跑一次
     */
    @Scheduled(cron = "0 */5 * * * ?")
    @SchedulerLock(name = "SpringJob.job1", lockAtMostFor = "2m", lockAtLeastFor = "1m")
    public void job1() {
        System.out.println("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job1...");
    }

    /**
     * 每5秒跑一次
     */
    @Scheduled(fixedRate = 5000)
    @SchedulerLock(name = "SpringJob.job2", lockAtMostFor = "4s", lockAtLeastFor = "4s")
    public void job2() {
        System.out.println("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job2...");
    }

    /**
     * 上次跑完之后隔5秒再跑
     * @throws InterruptedException
     */
    @Scheduled(fixedDelay = 5000)
    @SchedulerLock(name = "SpringJob.job3", lockAtMostFor = "4s", lockAtLeastFor = "4s")
    public void job3() throws InterruptedException {
        System.out.println("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job3...");
        Thread.sleep(10000);
    }

}

七、网关Spring Cloud Gateway

最佳实践如下,内容涉及多个框架整合,可参考入门教程:Spring Cloud Alibaba Gateway 实战:从零构建高性能服务网关

  1. 生产环境配置

    • 使用 Nacos 配置中心管理路由
    • 启用 Sentinel 进行流量控制
    • 配置 Redis 集群提高可用性
  2. 安全加固

    • 实现 JWT 鉴权
    • 配置 HTTPS
    • 限制 CORS 来源
  3. 性能优化

    • 调整 Netty 线程池参数
    • 配置合适的连接超时时间
    • 启用响应式编程
  4. 监控告警

    • 集成 Prometheus + Grafana
    • 配置日志收集(ELK)
    • 设置关键指标告警
相关推荐
Victor3562 小时前
MongoDB(68)如何使用mongoexport和mongoimport?
后端
nbsaas-boot2 小时前
AI编程的现实困境与未来路径:从“可用”到“可靠”的跃迁
java·运维·开发语言·架构
Victor3562 小时前
MongoDB(67)如何使用mongodump和mongorestore?
后端
AI服务老曹2 小时前
掌握核心代码:基于 Spring Boot + Vue 的 AI 视频管理平台源码架构与二次开发实战(全开源/低代码/私有化)
vue.js·人工智能·spring boot
东离与糖宝2 小时前
Java 26 Vector API 第十一轮孵化:AI 推理性能提升 80% 实战
java·人工智能
散峰而望2 小时前
【数据结构】单调栈与单调队列深度解析:从模板到实战,一网打尽
开发语言·数据结构·c++·后端·算法·github·推荐算法
星如雨グッ!(๑•̀ㅂ•́)و✧2 小时前
WebClient请求样例
java
两年半的个人练习生^_^2 小时前
dynamic-datasource多数据源使用和源码讲解
java·开发语言·数据库·mybatis
无籽西瓜a2 小时前
【西瓜带你学设计模式 | 第一期-单例模式】单例模式——定义、实现方式、优缺点与适用场景以及注意事项
java·后端·单例模式·设计模式