剖析框架代码结构的系统方法(下)

当面对Dubbo、Spring Cloud、Mybatis等开源框架时,我们可以采用一定的系统性的方法来快速把握它们的代码结构。这些系统方法包括对架构演进过程、核心执行流程、基础架构组成和可扩展性设计等维度的讨论。


在上一讲中,我们已经讨论了架构演进过程和核心执行流程这两个系统方法,今天的内容将围绕基础架构组成和可扩展性设计这两个维度展开讨论。

基础架构组成与代码结构

正如任何一个框架都存在一个核心流程一样,任何一个框架也都有一个作为内核的基础架构。正如前面提到的以Dubbo为代表的RPC框架或以MyBatis为代表的ORM框架,可以认为前者的基础架构就是网络通信、序列化、传输协议等组件,而后续则包含数据库连接管理、SQL执行等基础架构组件。Dubbo和MyBatis等框架只不过是对这些组件在不同层次上的封装。而这些组件在技术发展的过程中并没有发生重大变化,所以我们只需要掌握它们的实现原理,就可以做到触类旁通。一旦遇到基于这些组件构建的开源框架,也就可以掌握对应的设计思路和方法。

那么,如何把基础架构与现实中的具体实现框架对应起来呢?接下来,我们同样也是以RPC架构为例展开讨论。

RPC基础架构

到现在为止,RPC架构已经足足存在和发展了40余年,最早是作为RFC 674草案在1974年发布的。RPC是一种非常基础的架构,诸如Dubbo等框架都是RPC架构的一种具体实现方式。在学习这些框架的代码结构时,我们可以基于RPC基础架构来逐步进行理解。在此之前,我们先来看一下RPC基础架构的组成。

RPC基础架构包括一个服务器端组件和一个客户端组件,如下图所示。


从上图中,我们发现RPC基础架构呈现一种非常对称的结构,其中,可以看到客户端组件与职责包括如下图所示。


而对应的,服务端组件与职责则包括如下图所示。


RPC基础架构的特点在于概念和语义清晰明确,过程调用简洁且提供通用的通信机制和可扩展的序列化方式。

从RPC基础架构扩展到Dubbo框架

在接下来的内容中,我们将结合Dubbo框架探讨RPC组件如何在Dubbo中进行设计和实现。

Dubbo中的网络通信

我们在上讲介绍Dubbo整体架构的演进过程中提到了Remoting模块,该模块包含的组件如下图所示。


在上图中,我们把封装了请求和响应的Exchange组件称为信息交换层,而把负责具体的网络通信过程的Transport组件称为网络传输层。从功能职责上讲,Exchange偏向于Dubbo对自身通信过程的抽象和封装,跟网络通信关系不大,具体网络传输工作都是由Transport负责完成。因此,基于RPC基础架构,我们需要更多应该关注的是Transport层。事实上,Dubbo网络通信的底层也是基于Netty、Mina等框架进行了封装和扩展。

Dubbo中的传输协议

在ISO/OSI网络7层模型中,我们可以在每一层中添加一些自定义的信息,从而形成定制化的传输协议。Dubbo框架就基于这一点实现了一个私有的Dubbo协议。


可以看到,在会话层中,Dubbo协议添加了一个128位的消息头,包括Magic Code属性、序列化Id等属性。Dubbo之所以实现这套协议的目的,是为了提高数据序列化和传输的效率。

Dubbo中的远程调用

我们知道,在传统的服务调用过程中,我们一般采用的是单向模式和请求应答模式这两种调用模式。而在Dubbo中,远程调用存在三种的调用方式,即异步有返回、异步无返回以及异步变同步,其中异步变同步是默认的实现方式。具体来说是这样的代码执行流程。代码1。

protected Result doInvoke(final Invocation invocation) throws Throwable {

        …

        try {           

            if (isOneway) {//单向调用               

            } else if (isAsync) {//异步调用                

            } else {//同步调用                

            }

        }

}

在上述方法中,我们可以清晰看到代码执行的三条路径。


在Dubbo,如果是单向调用,会直接返回RpcResult空对象。如果是异步调用,则先创建一个Future对象把它嵌入到当前线程的上下文RpcContext中。这样,后续用户能够在合适的时候自己从RpcContext获取Future对象,并通过Future对象的get方法获取结果。而如果是同步,则在创建完Future对象之后直接调用它的get方法进行阻塞调用。

可以看到,Dubbo在基础的RPC架构上分布从网络通信、传输协议以及远程调用这三个方面进行了扩展。而我们只要掌握这些组件的基本原理,那么Dubbo中的实现过程就不会显得难以理解了。

可扩展性设计与代码结构

接下来,我们要讨论的话题是如何在框架中预留可扩展点。让我们先从可扩展点的基本实现方式开始讲起。

可扩展点

我了实现可扩展点,我们首先想到的是使用设计模式和架构模式。在常见的设计模式中有很多与扩展性相关,尤其是创建型模式和结构型模式。而以管道-过滤器、回调为代表的架构模式也能够减小组件之间的耦合程度,从而提高系统的扩展性。

在Java世界中,也提供了一些可扩展点技术体系,包括以jigsaw为代表模块化技术以及服务提供接口SPI机制等。

MyBatis TypeHandler机制

今天,让我们再来讨论MyBatis框架。MyBatis中的TypeHandler机制也采用类似的实现方式来提供扩展性。

我们知道在原生的JDBC编程模型中,想要获取SQL执行的结果,我们一般从ResultSet中提取值并赋值给相应的自定义对象,这个过程重复性高且类型转换容易出错。MyBatis通过一种精妙的方法处理这一操作,其中最重要的就是使用了TypeHandler接口。

我们首先来看看这个TypeHandler接口的定义,如下所示。

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

可以看到这这里一共定义了四个方法,除了第一个方法setParameter()是在数据库操作前执行参数处理之外,另外三个都是在数据库操作完成后对返回值的提取。按照我们对接口里所声明的方法的研究,应该大致能猜测出TypeHandler接口的主要应用场景。

BaseTypeHandler是TypeHandler的基本实现,是一个抽象类,MyBatis中内置的一系列TypHandler都继承了该类。而我们在实现自定义TypHandler时也是如此。

来举一个例子,我们希望将数据库中的varchar类型与Java语言中的String[]之间实现相互转换,例如将“book1, book2”转换为String[] books = {"book1", "book2"},反之亦然。


这里,我们就可以通过定义一个CustomStringTypeHandler来实现上述操作,如下所示。

@MappedTypes({String[].class})

@MappedJdbcTypes({JdbcType.VARCHAR})

public class CustomStringTypeHandler extends BaseTypeHandler<String[]>{   

    @Override

    public void setNonNullParameter(PreparedStatement ps, int i, String[] parameter, JdbcType jdbcType) throws SQLException {

        //将数组转换为字符串

StringBuffer result = new StringBuffer();

        for (String value : parameter) {

            result.append(value).append(",");

        }

        result.deleteCharAt(result.length() - 1);

        ps.setString(i, result.toString());

    }

    @Override

    public String[] getNullableResult(ResultSet rs, String columnName) throws SQLException {

        return this.getStringArray(rs.getString(columnName));

    }

    @Override

    public String[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {

        //将字符串转换为数组

return this.getStringArray(rs.getString(columnIndex));

    }

    @Override

    public String[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {

        //将字符串转换为数组

return this.getStringArray(cs.getString(columnIndex));

    }

    private String[] getStringArray(String columnValue) {

        //将字符串转换为数组

if (columnValue == null)

            return null;

        return columnValue.split(",");

    }

}

上述代码都是自解释的,我们实现了数组和字符串之间的自动转换。接下来我们只要在MyBatis中添加对这个自定义CustomStringTypeHandler类的配置即可。通过这个示例,我们明白通过简单实现TypeHandler接口就能对现有MyBatis进行扩展,MyBatis为此提供了一个非常灵活的扩展点。

今天的内容关注与两种剖析代码结构的方法,即基础架构组成和可扩展性设计。同样,我们采用Dubbo和MyBatis这两个主流开源框架讲述如何应用这些主题来具体剖析框架的代码结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值