nacos插件实现原理
前言
Nacos从2.1.0版本开始,支持通过SPI的方式注入鉴权相关插件,并在application.properties
配置文件中选择某一种插件实现作为实际鉴权服务。
官方自带插件:
- 鉴权
- 配置加密
- 数据源
- 轨迹追踪
- 环境变量
其中映像深刻的就是数据源,遥想当初2.0版本中适配pg数据库使用起来是相当复杂,需要去修改nacos源码来适配pg数据库,还需要去考虑修改对于源代码影响程度。nacos后续如果涉及到升级还需要去修改此部门代码,痛苦程度可想而知。
nacos 2.1.0 ~ 2.2 版本中利用插件的方式完美解决了这个问题,连接方式可以通过自己去按照一定规则去进行增强,妥妥的符合开闭原则啊。nacos源码是如何实现这一操作的?
这是我目前唯一想了解的。扒源码,开干;
理想很丰满,现实却很骨感。
由于对nacos源码不太了解,导致根本找不到代码相关入口,如何加载自己写的jar包。如何让他能够起效果?
经过漫长百度无果后,发现有人在git上已经上传了nacos适配pg数据库的插件,不得不佩服强人还是很多的。转念一想,他可以适配的原因不就是利用spi加载了相关实现,并进一步进行了相关代码的实现吗?这不正是我想找的入口吗。。。。。来了
插件项目解析
从datasource-plugin-ext-base 项目中我们可以发现 service文件,如下图:
com.alibaba.nacos.plugin.datasource.dialect.DefaultDatabaseDialect
注: 这里加载会用到spi技术,这里不做说明。可从这里查看spi相关知识
从service文件中可以看到:DefaultDatabaseDialect 类的结构图如下:
这样我们就可以定位到程序加载的是: DatabaseDialect 这个接口类。
public interface DatabaseDialect {
/**
* get database type.
* @return return database type name
*/
public String getType();
/**
* get frist index page param.
* @param page current pageNo
* @param pageSize current pageSize
* @return offset val or maxRange
*/
public int getPagePrevNum(int page, int pageSize);
查询到getType() 方法调用者来看,我们跟踪线到了 DatabaseDialectManager 这个类。 根据现在已有知识来说这个manager类 从命名来说应该是管理数据源相关工具类,具体代码如下: 删除一些不重要代码
public class DatabaseDialectManager {
private static final DatabaseDialectManager INSTANCE = new DatabaseDialectManager();
private static final Map<String, DatabaseDialect> SUPPORT_DIALECT_MAP = new ConcurrentHashMap<String, DatabaseDialect>();
private DatabaseDialectManager() {
}
static {
//加载多种数据库方言为映射信息
Collection<DatabaseDialect> dialectList = NacosServiceLoader.load(DatabaseDialect.class);
for (DatabaseDialect dialect : dialectList) {
SUPPORT_DIALECT_MAP.put(dialect.getType(), dialect);
}
if (SUPPORT_DIALECT_MAP.isEmpty()) {
LOGGER.warn("[DatasourceDialectManager] Load DatabaseDialect fail, No DatabaseDialect implements");
}
}
public DatabaseDialect getDialect(String databaseType) {
DatabaseDialect databaseDialect = SUPPORT_DIALECT_MAP.get(databaseType);
if (databaseDialect == null) {
return new DefaultDatabaseDialect();
}
return databaseDialect;
}
public static DatabaseDialectManager getInstance() {
return INSTANCE;
}
}
知识点:
- 利用单例模式进行类的创建
- 通过静态代码块的形式+spi模式将DatabaseDialect接口类的所有实现类均加载出来并存放在map中
代码跟踪到这发现线路断了,好像这边跟nacos没有什么结合的地方。nacos又是如何将他加载到系统中的?如何做到替换成pg数据?好像一个问题都没解决掉,不过我们也学习到了其他知识。
emo…… 线索断了就需要我们在重新梳理下;
nacos通过spi加载 ——> 插件项目一定在service文件中编写相关接口 目标不错的话就不会有太大问题。
继续查看插件项目代码
终于:在 nacos-postgresql-datasource-plugin-ext 项目中,看到关键性信息
查看这个Mapper文件:这不就是我所需要的连接点吗。。。。 Mapper类
com.alibaba.nacos.plugin.datasource.impl.postgresql.ConfigInfoAggrMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.ConfigInfoBetaMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.ConfigInfoMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.ConfigInfoTagMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.ConfigTagsRelationMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.HistoryConfigInfoMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.TenantInfoMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.TenantCapacityMapperByPostgresql
com.alibaba.nacos.plugin.datasource.impl.postgresql.GroupCapacityMapperByPostgresql
ps:
若能直接点到这个项目能省很多事情,但干程序这一行就是需要折腾,这样你才能从中学习到知识。
Nacos项目解析
- 连接点 Mapper接口 位于 com.alibaba.nacos.plugin.datasource.mapper.Mapper 下,plugin 插件包 datasource 下。项目中同样有service文件,这样也能确定上面的猜想。
com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoAggrMapperByMySql
com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoBetaMapperByMySql
com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoMapperByMySql
com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoTagMapperByMySql
com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigTagsRelationMapperByMySql
com.alibaba.nacos.plugin.datasource.impl.mysql.HistoryConfigInfoMapperByMySql
com.alibaba.nacos.plugin.datasource.impl.mysql.TenantInfoMapperByMySql
com.alibaba.nacos.plugin.datasource.impl.mysql.TenantCapacityMapperByMySql
com.alibaba.nacos.plugin.datasource.impl.mysql.GroupCapacityMapperByMysql
com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoAggrMapperByDerby
com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoBetaMapperByDerby
com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoMapperByDerby
com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoTagMapperByDerby
com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoTagsRelationMapperByDerby
com.alibaba.nacos.plugin.datasource.impl.derby.HistoryConfigInfoMapperByDerby
com.alibaba.nacos.plugin.datasource.impl.derby.TenantInfoMapperByDerby
com.alibaba.nacos.plugin.datasource.impl.derby.TenantCapacityMapperByDerby
com.alibaba.nacos.plugin.datasource.impl.derby.GroupCapacityMapperByDerby
- 项目中很明显也可以看到有个manager类。MapperManager ,依照惯例,这个类不能放过
public class MapperManager {
private static final Logger LOGGER = LoggerFactory.getLogger(MapperManager.class);
public static final Map<String, Map<String, Mapper>> MAPPER_SPI_MAP = new HashMap<>();
private static final MapperManager INSTANCE = new MapperManager();
private boolean dataSourceLogEnable;
private MapperManager() {
loadInitial();
}
/**
* Get the instance of MapperManager.
* @return The instance of MapperManager.
*/
public static MapperManager instance(boolean isDataSourceLogEnable) {
INSTANCE.dataSourceLogEnable = isDataSourceLogEnable;
return INSTANCE;
}
/**
* The init method.
*/
public void loadInitial() {
Collection<Mapper> mappers = NacosServiceLoader.load(Mapper.class);
for (Mapper mapper : mappers) {
Map<String, Mapper> mapperMap = MAPPER_SPI_MAP.getOrDefault(mapper.getDataSource(), new HashMap<>(16));
mapperMap.put(mapper.getTableName(), mapper);
MAPPER_SPI_MAP.put(mapper.getDataSource(), mapperMap);
LOGGER.info("[MapperManager] Load Mapper({}) datasource({}) tableName({}) successfully.",
mapper.getClass(), mapper.getDataSource(), mapper.getTableName());
}
}
}
- Collection mappers = NacosServiceLoader.load(Mapper.class); 这行代码已经可以说明一些问题了,插件jar包就是通过这边给nacos增强连接pg的功能,再通过配置文件选择pg数据库这样就能完整的一条线
结尾
目前是大概了解了nacos是通过怎样的方式把外部插件进行连接,但目前仍然有一些问题需要进一步深挖的:
- nacos如何初始化的数据库连接
- 所有好的框架代码代码都有看名知意,包、类、方法划分清楚的有点,无论nacos源码还是插件源码都可以看到
彩蛋
插件项目为啥 DefaultDatabaseDialect 类管理 数据源? 其实一开始就项目介绍中就有相关体现。此项目最终目的是可以集成对所有数据库的支撑;
三、其他数据库插件开发
可参考nacos-postgresql-datasource-plugin-ext工程,新创建Maven项目,实现AbstractDatabaseDialect类,重写相关的分页操作逻辑与方法,并创建相应的mapper实现,减少了适配的成本。
目前对于Oracle、达梦数据库,仍然需要修改Nacos2.2的主分支代码,因为要兼容默认的命名空间ID为空的查询情况,社区官网未处理。