MyBatis-binding模块与代理模式
MyBatis-binding模块与代理模式
Mybatis通过SqlSession来进行CRUD,其所调用的sql使用id标识存放在xml中,可以通过SqlSession提供的一些方法进行调用,其中一种是传入sql的id与所要使用的参数
1 | public interface SqlSession extends Closeable { |
但这样做有一个缺点,经常用ibatis的话,可能深有体会,如果statement填写错了,只能在运行时才能发现,对于开发来说难免会又写错的时候,总会浪费一些时间。
还有一种更加优雅的调用方式,先定义一个Mapper接口,
1 | public interface ProductMapper { |
建立一个Mapper.xml,接口名对应到Mapper.xml的namespace,接口的方法名对应到xml中的SQL id。
1 | <select id="queryProduct" resultMap="productResultMap"> |
调用getMapper获取ProductMapper对象,并调用queryProduct方法即可。
1 | public interface SqlSession extends Closeable { |
这样,可以在mybatis启动时就进行检测,接口是否有对应的sql id存在,规避了运行时找不到对应sql的风险。
归根结底,getMapper接口最终还是调用的SqlSession下的各个select、update方法,只是mybatis将其包装了一下更加方便实用。但是具体是怎么实现的?
主要思路:使用动态代理,增强Mapper接口中的所有方法。调用方法代理时获取在xml中定义的对应的sql语句,同时获取其方法类型,如select
,update
,delete
等。最终分别调用SqlSession#select
,SqlSession#update
,SqlSession#delete
等方法。
Mapper的绑定与获取主要由binding模块负责。
重要的四个类:
- MapperRegistry
- MapperProxyFactory
- MapperProxy
- MapperMethod
MapperRegistry
该类注册了所有的Mapper接口以及其对应的被代理的方法。
所包含成员有:
config:All-in-One 的Mybatis全局配置
knowMappers:所有被加载的Mapper,key为Mapper的Class对象,Value为生产Mapper代理的工厂类MapperProxyFactory
。
追踪SqlSession的getMapper方法发现,
其最终调用的就是MapperRegistry中的getMapper方法。
MapperRegistry.getMapper
1 | public <T> T getMapper(Class<T> type, SqlSession sqlSession) { |
步骤一,从knowMappers
中获取该接口对应的代理工厂。如果没有则抛出异常。
步骤二,通过代理工厂生成该接口的代理对象。
MapperProxyFactory
MapperProxyFactory的主要作用就是生成Mapper的Proxy对象。
为什么没有在MapperRegistry直接生成代理对象,而要使用工厂模式?工厂模式的作用是为了屏蔽复杂的对象创建过程。这里生成Proxy,需要调用Proxy对象的构造,其构造参数methodCache
也是在Factory中进行初始化的。
成员mapperInterface即是当前需要生产代理类的Mapper的class对象。
成员methodCache用于维护该工厂处理的对应Mapper中的Method与对应的MapperMethodInvoker之间的映射。在使用过程中调用Mapper中的某个方法时,可以拿到该方法对应的具体sql信息。MapperMethodInvoker的具体实现是在MapperProxy中定义的,这里只是新建立相关缓存,并将缓存的引用传递给MapperProxy的构造。
下面看下代理模式是如何具体实践的。
1 | public class MapperProxyFactory<T> { |
通常会调用newInstance(SqlSession sqlSession)
,传入一个SqlSession生成该工厂对应Mapper的代理。
步骤一,通过传入sqlSession
,对应的Mapper的class对象,以及其方法缓存引用,构造一个MapperProxy,这个MapperProxy即是典型的动态代理,实现了InvocationHanler
接口,作为代理对象。
步骤二,通过Proxy.newProxyInstance
生成动态代理对象。
代理对象需要做的,就是重新实现被代理接口的方法,所以会需要一个入参是被代理接口,即mapperInterface,最后一个入参MapperProxy也一定是InvokationHandler的实现类。具体看下MapperProxy。
MapperProxy
见名知义了,它会成为Mapper接口的具体实现。重点在于invoke方法的实现。
1 |
|
每个方法会映射到一个执行器MethodInvoker,并将执行器添加到Cache中,方便下一次调用。default方法会执行DefaultMethodInvoker,而普通方法则是调用的PlainMethodInvoker,期间生成对应的方法抽象,即MapperMethod。
MethodInvoker中包含了MapperMethod,这个MapperMethod就是我们常常使用的接口中的具体方法了,最终调用MethodInvoker.invoke方法。invoke的实现一般就是调用MapperMethod的execute方法,execute中会具体调用select,update,delete,insert相关SqlSession操作。
总结
binding模块是一个典型的动态代理的使用案例,通过面向mapper接口,解决启动时检查statement的正确性,调用sql的行为也变得更为优雅,入参也可以直接定义在方法签名中,而不是一味的使用如SqlSession的select方法,传入一个让人捉摸不透的Object参数(参数解析不属于binding模块)。
可见,使用mapper接口的好处是有很多的,动态代理也让一系列复杂的过程变得对开发人员透明,设计思路十分值得学习。譬如我们在写RPC调用时,很多时候也是面向接口api的,通过Proxy,也可以让一系列非业务逻辑代码变得透明,如同调用本地的方法一般进行rpc,开发也可以更聚焦于业务。