当前位置: 首页 > news >正文

dbswitch-spi 实现数据库的可插拔扩展

代码

// Copyright tang. All rights reserved. // https://gitee.com/inrgihc/dbswitch // // Use of this source code is governed by a BSD-style license // // Author: tang (inrgihc@126.com) // Date : 2020/1/2 // Location: beijing , china ///////////////////////////////////////////////////////////// package org.dromara.dbswitch.product.register; import org.dromara.dbswitch.core.annotation.Product; import org.dromara.dbswitch.common.consts.Constants; import org.dromara.dbswitch.common.type.ProductTypeEnum; import org.dromara.dbswitch.core.provider.ProductFactoryProvider; import org.dromara.dbswitch.core.provider.ProductProviderFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.ServiceConfigurationError; import java.util.Set; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Configuration; @Slf4j @Configuration @ConditionalOnClass(ProductProviderFactory.class) public class ProductRegisterAutoConfiguration implements InitializingBean, BeanClassLoaderAware { private static final Set<String> providers = new HashSet<>(); private ClassLoader classLoader; private int parseLine(BufferedReader reader, int lc, List<String> names) throws IOException, ServiceConfigurationError { String ln = reader.readLine(); if (ln == null) { return -1; } int ci = ln.indexOf('#'); if (ci >= 0) { ln = ln.substring(0, ci); } ln = ln.trim(); int n = ln.length(); if (n != 0) { if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) { log.error("Illegal configuration-file syntax: {}", ln); } int cp = ln.codePointAt(0); if (!Character.isJavaIdentifierStart(cp)) { log.error("Illegal provider-class name: {}", ln); } for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { cp = ln.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) { log.error("Illegal provider-class name: {}", ln); } } if (!providers.contains(ln) && !names.contains(ln)) { names.add(ln); } } return lc + 1; } private List<String> parse(URL url) throws ServiceConfigurationError { InputStream in = null; BufferedReader reader = null; ArrayList<String> names = new ArrayList<>(); try { in = url.openStream(); reader = new BufferedReader(new InputStreamReader(in, "utf-8")); int lc = 1; while ((lc = parseLine(reader, lc, names)) >= 0) { } } catch (IOException x) { log.error("Error reading configuration file", x); } finally { try { if (reader != null) { reader.close(); } if (in != null) { in.close(); } } catch (IOException y) { log.error("Error closing configuration file", y); } } return names; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } @Override public void afterPropertiesSet() throws Exception { log.info("Register database product now ..."); ClassLoader loader = (null != classLoader) ? classLoader : ProductProviderFactory.class.getClassLoader(); Enumeration<URL> resources = loader.getResources(Constants.SPI_FILE); while (resources.hasMoreElements()) { URL url = resources.nextElement(); providers.addAll(parse(url)); } int totalCount = 0; for (String className : providers) { Class<?> aClass = classLoader.loadClass(className); if (ProductFactoryProvider.class.isAssignableFrom(aClass)) { if (aClass.isAnnotationPresent(Product.class)) { Product annotation = aClass.getAnnotation(Product.class); ProductTypeEnum productType = annotation.value(); if (null != productType) { ProductProviderFactory.register(productType, className); ++totalCount; } } } } log.info("Finish to register total {} database product !", totalCount); } }

这段代码是dbswitch开源数据库同步工具的核心组件之一,负责在 Spring 容器启动时自动扫描并注册支持的数据库产品实现类。以下是详细解析:


🧩 一、核心功能概述

该类通过 Java 的 SPI(Service Provider Interface)机制动态加载数据库方言实现类,并将其注册到ProductProviderFactory工厂中,实现数据库产品的自动发现与注册。


🔍 二、关键代码解析

1.类定义与注解
@Slf4j @Configuration @ConditionalOnClass(ProductProviderFactory.class) public class ProductRegisterAutoConfiguration implements InitializingBean, BeanClassLoaderAware
  • @Configuration:声明为 Spring 配置类。
  • @ConditionalOnClass:仅当类路径存在ProductProviderFactory时生效(保障环境兼容性)。
  • InitializingBean:在 Bean 属性设置后触发afterPropertiesSet()初始化逻辑。
  • BeanClassLoaderAware:获取 Spring 容器的类加载器。
2.SPI 文件解析
private List<String> parse(URL url) { ... } private int parseLine(BufferedReader reader, int lc, List<String> names) { ... }
  • 作用:读取META-INF/services目录下的 SPI 配置文件(文件名由Constants.SPI_FILE定义)。
  • 流程
    1. 按行读取配置文件(如org.dromara.dbswitch.core.provider.ProductFactoryProvider)。
    2. 过滤注释(#开头)和非法类名(如包含空格或非 Java 标识符)。
    3. 将有效实现类全限定名存入providers集合。
3.数据库产品注册
@Override public void afterPropertiesSet() { // 获取类加载器 ClassLoader loader = (null != classLoader) ? classLoader : ProductProviderFactory.class.getClassLoader(); // 扫描所有 SPI 配置文件 Enumeration<URL> resources = loader.getResources(Constants.SPI_FILE); while (resources.hasMoreElements()) { providers.addAll(parse(resources.nextElement())); // 解析文件内容 } // 注册到工厂 int totalCount = 0; for (String className : providers) { Class<?> aClass = classLoader.loadClass(className); if (ProductFactoryProvider.class.isAssignableFrom(aClass) // 确保实现接口 && aClass.isAnnotationPresent(Product.class)) { // 检查 @Product 注解 Product annotation = aClass.getAnnotation(Product.class); ProductTypeEnum productType = annotation.value(); // 获取数据库类型(如 ORACLE、MYSQL) ProductProviderFactory.register(productType, className); // 注册到工厂 ++totalCount; } } log.info("Finish to register total {} database product !", totalCount); }

📦 三、技术要点总结

特性说明
SPI 机制通过META-INF/services文件实现数据库方言的可插拔扩展
动态注册利用 Spring 的InitializingBean在容器启动时完成注册
类型安全通过@Product注解绑定数据库类型枚举(如ProductTypeEnum.ORACLE
错误防护校验类名合法性,避免非法配置导致运行时异常

🌰 四、实际应用场景

假设新增达梦数据库支持:

  1. 实现接口
    @Product(ProductTypeEnum.DAMENG) public class DamengProductProvider implements ProductFactoryProvider { ... }
  2. SPI 配置
    META-INF/services/org.dromara.dbswitch.core.provider.ProductFactoryProvider文件中添加:
    org.dromara.dbswitch.dm.DamengProductProvider
  3. 自动注册
    Spring 启动时,该类会被扫描并注册到ProductProviderFactory,后续可通过ProductTypeEnum.DAMENG调用。

⚙️ 五、与 dbswitch 架构的关系

此组件是 dbswitch多数据库支持的基石,其设计体现了以下核心思想:

  1. 解耦:通过 SPI 分离接口与实现。
  2. 扩展性:新增数据库仅需添加实现类和 SPI 配置。
  3. 自动化:Spring 容器管理生命周期,避免手动注册。

通过这种设计,dbswitch 能灵活支持 20+ 种数据库(如 Oracle、MySQL、PostgreSQL、达梦等),成为异构数据库迁移的高效工具。

http://www.cnnetsun.cn/news/65359.html

相关文章:

  • ComfyUI集成Qwen3-14B的可能性探索:视觉+语言新组合
  • GPT-5.2震撼上线!性能碾压人类专家,程序员必学大模型技术,建议收藏
  • CompTIA CV0-004 Cloud+ 認證考試完整介紹
  • PyTorch安装避坑指南 + vLLM性能调优技巧
  • AutoGPT与Metabase Plus集成:增强版报表自动化
  • PyTorch安装后如何加载Qwen3-VL-8B进行多模态推理
  • 【收藏必看】2025大模型技术岗位全景图:15大方向详解,助你成为AI人才
  • LobeChat支持GraphQL接口吗?API扩展能力分析
  • LobeChat能否实现对话分享功能?链接公开化实践
  • 全球工程软件格局重塑:中国AI原生平台的机会窗口
  • 【Dubbo】接口特性与开发注意事项
  • 测试环境管理的最佳实践
  • Miniconda环境下安装PyTorch GPU版的完整流程
  • AI推理的“哥白尼革命”!何恺明团队推翻LLM,将抽象推理重新定义为视觉问题
  • 深度学习训练器框架全面对比指南
  • 火山引擎AI大模型新玩法:结合vLLM实现高效推理
  • 16、GTK+ 样式定制全解析
  • LobeChat是否支持Service Worker?离线访问能力构建
  • 重学计算机基础013:减法运算的底层逻辑——为什么没有“减法器”?
  • apk pure安全性争议下,本地大模型成新趋势
  • LobeChat能否支持NFT头像展示?个性化形象设定
  • LobeChat + Kubernetes:大规模部署AI前端界面的可行路径
  • 20万以内家用新能源SUV怎么选?纯电动车型主动安全系统深度对比
  • 基于28DR+VU13P的宽带高速信号处理板
  • AutoGPT镜像上线促销:限时赠送免费Token额度
  • 达人内容乱+不合规?KOL/KOS/KOC/KOC/KOX内容协同+合规管控,品牌调性不跑偏
  • 解锁优质创意素材:这四个专业平台值得收藏
  • 毕设分享 深度学习遮挡下的人脸识别(源码+论文)
  • Python UV搭配Miniconda:下一代包管理体验
  • 实验室装修,怎样做更省心?