前面我們花了兩篇內容介紹了ShardingSphere中的微內核模式。微內核是一種插件化的架構模式,通過SPI實現動態加載系統擴展點機制,在ShardingSphere應用非常廣泛,在本系列後續的很多主題中都會涉及到該模式的具體應用場景。
今天我們繼續回到ShardingSphere的SQL解析引擎的主流程,我們回顧SQLParseEngine中的核心邏輯,即通過解析獲取一個SQLStatement對象,核心代碼就是下面這句:
SQLStatement result = new SQLParseKernel(ParseRuleRegistry.getInstance(), databaseTypeName, sql).parse();
我們看到這裏有一個ParseRuleRegistry對象,該類就是一個規則註冊表,保存着各種解析規則信息。ParseRuleRegistry類在《ShardingSphere源碼解析之SQL解析引擎(二)》中已經有所介紹,該類位於shardingsphere-sql-parser-engine工程的org.apache.shardingsphere.sql.parser.core.rule.registry包中。我們同樣關注ParseRuleRegistry中的核心變量,首先是如下所示的三個Loader類:
private final ExtractorRuleDefinitionEntityLoader extractorRuleLoader = new ExtractorRuleDefinitionEntityLoader();
private final FillerRuleDefinitionEntityLoader fillerRuleLoader = new FillerRuleDefinitionEntityLoader();
private final SQLStatementRuleDefinitionEntityLoader statementRuleLoader = new SQLStatementRuleDefinitionEntityLoader();
我們在《ShardingSphere源碼解析之SQL解析引擎(一)》中知道整個SQL解析引擎需要完成SQLSegment的提取(SQLSegmentExtractor)以及SQLStatement的填充(SQLStatementFiller),而這些都依賴於SQLStatementRule的解析。所以在ParseRuleRegistry中就包含了針對如上所示的SQLStatementRule、ExtractorRule和FillerRule的三種RuleDefinitionEntityLoader,而它們都實現了RuleDefinitionEntityLoader接口,我們來看一下這個接口定義,如下所示:
public interface RuleDefinitionEntityLoader {
RuleDefinitionEntity load(String ruleDefinitionFile);
}
可以看到這個接口從基於XML的規則定義文件加載規則定義實體RuleDefinitionEntity,前面提到的三種RuleDefinitionEntityLoader都實現了這個接口。我們先來看SQLStatementRuleDefinitionEntityLoader,如下所示:
public final class SQLStatementRuleDefinitionEntityLoader implements RuleDefinitionEntityLoader {
@Override
@SneakyThrows
public SQLStatementRuleDefinitionEntity load(final String sqlStatementRuleDefinitionFile) {
InputStream inputStream = SQLStatementRuleDefinitionEntityLoader.class.getClassLoader().getResourceAsStream(sqlStatementRuleDefinitionFile);
Preconditions.checkNotNull(inputStream, "Cannot load SQL statement rule definition file: %s, ", sqlStatementRuleDefinitionFile);
return (SQLStatementRuleDefinitionEntity) JAXBContext.newInstance(SQLStatementRuleDefinitionEntity.class).createUnmarshaller().unmarshal(inputStream);
}
}
在這裏我們發現了JAXBContext對象,顯然用到了JAXB。JAXB(Java Architecture for XML Binding) 是一個業界的標準,是一項可以根據XML Schema產生Java類的技術。該過程中,JAXB也提供了將XML實例文檔反向生成Java對象樹的方法,並能將Java對象樹的內容重新寫到XML實例文檔。從另一方面來講,JAXB提供了快速而簡便的方法將XML模式綁定到Java表示,從而使得Java開發者在Java應用程序中能方便地結合XML數據和處理函數。關於JAXB的介紹不是我們的重點,這裏不做具體展開。
在ShardingSphere的shardingsphere-sql-parser-engine工程中,提供了基於JAXB的各種實體定義以及RuleDefinitionEntityLoader的實現,如下所示:
以SQLStatementRuleDefinitionEntity爲例,我們看到它的定義如下所示:
@XmlRootElement(name = "sql-statement-rule-definition")
@Getter
public final class SQLStatementRuleDefinitionEntity implements RuleDefinitionEntity {
@XmlElement(name = "sql-statement-rule")
private Collection<SQLStatementRuleEntity> rules = new LinkedList<>();
}
這裏出現了JAXB中的XML註解@XmlRootElement和@XmlElement。然後我們再看其中的SQLStatementRuleEntity定義,如下所示:
@XmlAccessorType(XmlAccessType.FIELD)
@Getter
public final class SQLStatementRuleEntity {
@XmlAttribute(required = true)
private String context;
@XmlAttribute(name = "sql-statement-class", required = true)
private String sqlStatementClass;
@XmlAttribute(name = "extractor-rule-refs", required = true)
private String extractorRuleRefs;
}
這裏用到了@XmlAttribute註解。通過這些註解我們就能根據XML文件自動將配置項轉化爲Java對象。
JAXB是根據文件名獲取XML然後再進行解析,我們在RuleDefinitionFileConstant類中找到了獲取XML文件名的定義,如下所示:
public static String getSQLStatementRuleDefinitionFile(final String databaseTypeName) {
return Joiner.on('/').join(ROOT_PATH, databaseTypeName.toLowerCase(), SQL_STATEMENT_RULE_DEFINITION_FILE_NAME);
}
該文件是根據數據庫類型進行獲取,在shardingsphere-sql-parser-mysql工程中,我們找到了該路徑,發現用於SQL解析的三個配置文件都是放在這個路徑之下:
我們打開sql-statement-rule-definition.xml文件,截取其中的一條配置項,如下所示:
<sql-statement-rule-definition>
<sql-statement-rule context="select" sql-statement-class="org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement" extractor-rule-refs="tableReferences, columns, selectItems, where, predicate, groupBy, orderBy, limit, subqueryPredicate, lock" />
</sql-statement-rule-definition>
可以看到這裏的配置項屬性與SQLStatementRuleDefinitionEntity和SQLStatementRuleEntity是一致的。其他的RuleDefinitionEntity和RuleEntity也是一樣的處理方式。
最後,我們回到ParseRuleRegistry,發現如下代碼:
static {
NewInstanceServiceLoader.register(SQLParserEntry.class);
instance = new ParseRuleRegistry();
}
在這裏,我們發現SQLParserEntry的註冊過程實際上發生在ParseRuleRegistry的這個靜態方法中。當系統啓動時,ShardingSphere就把SQLParserEntry相關的SPI實現類加載到了內存中。
然後,在ParseRuleRegistry的構造函數中,我們也發現了通過各種RuleDefinitionEntityLoader加載RuleDefinitionEntity的入口,如下所示:
private ParseRuleRegistry() {
initParseRuleDefinition();
}
private void initParseRuleDefinition() {
ExtractorRuleDefinitionEntity generalExtractorRuleEntity = extractorRuleLoader.load(RuleDefinitionFileConstant.getExtractorRuleDefinitionFile());
FillerRuleDefinitionEntity generalFillerRuleEntity = fillerRuleLoader.load(RuleDefinitionFileConstant.getFillerRuleDefinitionFile());
for (SQLParserEntry each : NewInstanceServiceLoader.newServiceInstances(SQLParserEntry.class)) {
String databaseTypeName = each.getDatabaseTypeName();
fillerRuleDefinitions.put(databaseTypeName, createFillerRuleDefinition(generalFillerRuleEntity, databaseTypeName));
sqlStatementRuleDefinitions.put(databaseTypeName, createSQLStatementRuleDefinition(generalExtractorRuleEntity, databaseTypeName));
}
}
這裏,我們再次看到了通過NewInstanceServiceLoader加載SQLParserEntry的SPI實現類的過程,用於獲取所需的DatabaseTypeName。然後根據 DatabaseTypeName找到對應的數據庫並加載該數據庫對應的配置文件。
請注意,在ParseRuleRegistry中,我們只找到瞭如下所示的兩個變量定義,以及它們對外暴露領域對象SQLStatementRule和SQLSegmentFiller的方法:
private final Map<String, FillerRuleDefinition> fillerRuleDefinitions = new HashMap<>();
private final Map<String, SQLStatementRuleDefinition> sqlStatementRuleDefinitions = new HashMap<>();
public SQLStatementRule getSQLStatementRule(final String databaseTypeName, final String contextClassName) {
return sqlStatementRuleDefinitions.get(databaseTypeName).getSQLStatementRule(contextClassName);
}
public Optional<SQLSegmentFiller> findSQLSegmentFiller(final String databaseTypeName, final Class<? extends SQLSegment> sqlSegmentClass) {
return Optional.fromNullable(fillerRuleDefinitions.get(databaseTypeName).getFiller(sqlSegmentClass));
}
爲什麼沒有單獨的ExtractorRuleDefinition呢?這是因爲SQLStatementRuleDefinition中包含了ExtractorRuleDefinition,也就是說SQLStatementRule中包含了SQLSegmentExtractor的定義,這點從sql-statement-rule-definition.xml的“extractor-rule-refs”配置項定義以及SQLStatementRule的變量定義中就能得到明確:
<sql-statement-rule context="select" sql-statement-class="org.apache.shardingsphere.sql.parser.sql.statement.dml.SelectStatement" extractor-rule-refs="tableReferences, columns, selectItems, where, predicate, groupBy, orderBy, limit, subqueryPredicate, lock" />
public final class SQLStatementRule {
private final String contextName;
private final Class<? extends SQLStatement> sqlStatementClass;
private final Collection<SQLSegmentExtractor> extractors;
}
顯然SQLStatementRule中包含了SQLSegmentExtractor的一個集合,而理解這種設計的實際上需要把握ShardingSphere中的SQL解析過程,這是接下來我們要搞清楚的內容。
至此,我們從解析規則的角度對ParseRuleRegistry進行了分析。這塊內容涉及一大批JAXB工具類的定義,內容雖多,但並不複雜。核心類之間的關係如下圖所示:
ParseRuleRegistry中存儲的規則是ShardingSphere中進行SQL解析的基礎數據,我們在後面的文章中繼續介紹解析引擎時會反覆引用今天所講的內容。