上一篇中,我們詳細討論了ShardingConnection對象。我們知道ShardingSphere的設計目標是實現一套與JDBC規範完全兼容的框架體系,因此對JDBC規範的重寫是關鍵。我們知道JDBC規範中的幾個重要概念包括DataSource、Connection、Statement、PreparedStament。而ShardingSphere通過適配器(Adapter)設計模式包裝了自己的實現類,如ShardingDataSource、ShardingConnection、ShardingStatement、ShardingPreparedStament。在org.apache.shardingsphere.shardingjdbc.jdbc.adapter包中包含了所有與Adapter相關的實現類:
我們繼續以上一篇中介紹的ShardingConnection爲例來介紹Adapter模式的具體應用,這是ShardingConnection的另一條類層結構支線,即作爲一種Connection對象所應該具備的基本結構,如下圖所示:
我們首先來看AbstractConnectionAdapter,ShardingConnection直接繼承了它。我們在AbstractConnectionAdapter中發現了一個cachedConnections屬性,它是一個Map對象,該對象其實緩存了這個經過封裝的ShardingConnection背後真實的Connection對象。如果我們對一個AbstractConnectionAdapter重複使用,那麼這些cachedConnections也會一直被緩存,直到調用close方法。可以從AbstractConnectionAdapter的getConnections方法中理解具體的操作過程,如下所示:
public final List<Connection> getConnections(final ConnectionMode connectionMode, final String dataSourceName, final int connectionSize) throws SQLException {
//獲取DataSource
DataSource dataSource = getDataSourceMap().get(dataSourceName);
Preconditions.checkState(null != dataSource, "Missing the data source name: '%s'", dataSourceName);
Collection<Connection> connections;
//根據數據源從cachedConnections中獲取connections
synchronized (cachedConnections) {
connections = cachedConnections.get(dataSourceName);
}
//如果connections多於想要的connectionSize,則只獲取所需部分
List<Connection> result;
if (connections.size() >= connectionSize) {
result = new ArrayList<>(connections).subList(0, connectionSize);
} else if (!connections.isEmpty()) {//如果connections不夠
result = new ArrayList<>(connectionSize);
result.addAll(connections);
//創建新的connections
List<Connection> newConnections = createConnections(dataSourceName, connectionMode, dataSource, connectionSize - connections.size());
result.addAll(newConnections);
synchronized (cachedConnections) {
//將新創建的connections也放入緩存中進行管理
cachedConnections.putAll(dataSourceName, newConnections);
}
} else {//如果緩存中沒有對應dataSource的Connections,同樣進行創建並放入緩存中
result = new ArrayList<>(createConnections(dataSourceName, connectionMode, dataSource, connectionSize));
synchronized (cachedConnections) {
cachedConnections.putAll(dataSourceName, result);
}
}
return result;
}
這段代碼有三個判斷,流程上比較簡單,參考註釋即可。這裏需要關注的是其中的createConnections方法,如下所示:
private List<Connection> createConnections(final String dataSourceName, final ConnectionMode connectionMode, final DataSource dataSource, final int connectionSize) throws SQLException {
if (1 == connectionSize) {
Connection connection = createConnection(dataSourceName, dataSource);
replayMethodsInvocation(connection);
return Collections.singletonList(connection);
}
if (ConnectionMode.CONNECTION_STRICTLY == connectionMode) {
return createConnections(dataSourceName, dataSource, connectionSize);
}
synchronized (dataSource) {
return createConnections(dataSourceName, dataSource, connectionSize);
}
}
這裏調用了抽象方法createConnection(另一個createConnections方法就是批量調用了createConnection方法),該方法需要AbstractConnectionAdapter的子類進行實現:
protected abstract Connection createConnection(String dataSourceName, DataSource dataSource) throws SQLException;
同時,我們看到對於創建的Connection對象,都需要執行如下語句:
replayMethodsInvocation(connection);
這句話比較難以理解,讓我們來到定義它的地方,即WrapperAdapter類。從命名上看,它是一個包裝器的適配類,實現了JDBC中的Wrapper接口。我們在該類中找到了如下所示的一對方法定義:
public final void recordMethodInvocation(final Class<?> targetClass, final String methodName, final Class<?>[] argumentTypes, final Object[] arguments) {
jdbcMethodInvocations.add(new JdbcMethodInvocation(targetClass.getMethod(methodName, argumentTypes), arguments));
}
public final void replayMethodsInvocation(final Object target) {
for (JdbcMethodInvocation each : jdbcMethodInvocations) {
each.invoke(target);
}
}
這兩個方法都用到了JdbcMethodInvocation類,它的定義如下所示:
public class JdbcMethodInvocation {
@Getter
private final Method method;
@Getter
private final Object[] arguments;
public void invoke(final Object target) {
method.invoke(target, arguments);
}
}
顯然,這裏用到了反射技術根據傳入的method和arguments對象執行對應方法。瞭解了JdbcMethodInvocation類的原理之後,我們就不難理解recordMethodInvocation和replayMethodsInvocation方法的作用。其中,recordMethodInvocation用於記錄需要執行的方法和參數,而replayMethodsInvocation則根據這些方法和參數通過反射技術進行執行。
對於執行replayMethodsInvocation,我們必須先找到recordMethodInvocation的調用入口。通過代碼的調用關係,我們看到在AbstractConnectionAdapter和AbstractStatementAdapter中集中對其進行了調用:
通過上圖,我們知道在AbstractConnectionAdapter中的setAutoCommit、setReadOnly和setTransactionIsolation這三個方法中存在對recordMethodInvocation的調用,比方說如下所示的setReadOnly中的調用:
//調用recordMethodInvocation方法記錄方法調用的元數據
recordMethodInvocation(Connection.class, "setReadOnly", new Class[]{boolean.class}, new Object[]{readOnly});
當完成各種recordMethodInvocation方法的調用之後,我們就可以通過在新創建的Connection對象上執行replayMethodsInvocation方法,該方法會遍歷所有通過recordMethodInvocation傳入的方法和參數並依次進行執行,也就是說相當於執行了如上所示的setReadOnly等方法。這樣做的好處是我們可以先把相應的操作存儲起來,等真正有Connection對象的時候再進行方法的調用,設計上非常巧妙。
然後,我們再以setReadOnly方法爲例,來看它的完整實現,如下所示:
@Override
public final void setReadOnly(final boolean readOnly) throws SQLException {
this.readOnly = readOnly;
//調用recordMethodInvocation方法記錄方法調用的元數據
recordMethodInvocation(Connection.class, "setReadOnly", new Class[]{boolean.class}, new Object[]{readOnly});
forceExecuteTemplate.execute(cachedConnections.values(), new ForceExecuteCallback<Connection>() {
@Override
public void execute(final Connection connection) throws SQLException {
connection.setReadOnly(readOnly);
}
});
}
這裏還看到了一個新的模板類,即ForceExecuteTemplate,該類與ForceExecuteCallback是一對。它們的作用也是通過回調的方法來執行定製化的邏輯,在這個場景中,就是強制對cachedConnections中所有緩存的Connection執行setReadOnly方法。
另一方面,從類層關係上,我們看到AbstractConnectionAdapter直接繼承的是AbstractUnsupportedOperationConnection而不是WrapperAdapter,而在AbstractUnsupportedOperationConnection中都是一批直接拋出異常的方法,這樣做的目的就是明確哪些操作是AbstractConnectionAdapter及其子類ShardingConnection所不能支持的,屬於職責分離的一種具體實現方法。
更多內容可以關注我的公衆號:程序員向架構師轉型。