上一篇中,我们详细讨论了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所不能支持的,属于职责分离的一种具体实现方法。
更多内容可以关注我的公众号:程序员向架构师转型。