Mybatis为什么要使用动态代理对SqlSession进行增强?
在ibatis时代,是直接通过sqlSession进行调用的
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserInfo userInfo = sqlSession.selectOne("org.apache.ibatis.atest.UserInfoMapper.selectById", 1);
这种方式有什么缺点呢?
调用的方法有可能写错,实际要执行的sql并没有配置
传入的参数有可能写错,因为入参是Object类型
返回值有可能写错,因为返回值也是Object类型
但是这些问题在编译的时候并不会暴露,只有在运行的时候才会暴露
鉴于ibatis的这些问题,Mybatis使用了动态代理减少了错误的发生,并且让api变的更简洁易用
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
UserInfo userInfo = mapper.selectById(1);
代理的主要流程如下图所示
创建代理对象
用上一节的例子来debug动态代理的过程,一步一步追
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
UserInfo userInfo = mapper.selectById(1);
之前的文章说过sqlSessionFactory的实现类是DefaultSqlSessionFactory,所以openSession返回的是DefaultSqlSession,追一下
DefaultSqlSession#getMapper方法
org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
org.apache.ibatis.session.Configuration#getMapper
org.apache.ibatis.binding.MapperRegistry#getMapper
org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
整体逻辑并不复杂,利用MapperProxyFactory生成MapperProxy,而MapperProxy实现了InvocationHandler,所以返回的代理类就是MapperProxy,所以当执行Mapper接口的返回时,会首先进入MapperProxy#invoke方法
同时MapperProxy包装了SqlSession,从这我们就能猜到,后续MapperProxy肯定是利用调用的方法,拼装sql对应的id,然后交给SqlSession来执行查询
执行动态代理方法
当执行Mapper接口的任何方法时,都会进入MapperProxy#invoke方法(MapperProxy实现了InvocationHandler接口)
org.apache.ibatis.binding.MapperProxy#invoke
如果执行的是Object类中的方法,直接反射执行即可,否则执行cachedInvoker方法得到MapperMethodInvoker,然后调用其invoke方法org.apache.ibatis.binding.MapperProxy#cachedInvoker
org.apache.ibatis.util.MapUtil#computeIfAbsent
把要执行的方法封装为MapperMethodInvoker,并通过ConcurrentHashMap缓存下来,MapUtil#computeIfAbsent就是一个简单的工具类,利用computeIfAbsent来保证线程安全
如果是接口的默认方法则封装为DefaultMethodInvoker
否则封装为PlainMethodInvoker(一般情况,我们都不会对接口提供default方法,所以我们看一下PlainMethodInvoker#invoke的逻辑)
调用MapperMethod#execute方法,和我们之前分享的参数处理器和SqlSession的执行流程接上了哈,至此整个执行流程分析完毕
参考博客
[1]https://www.jianshu.com/p/46c6e56d9774