KeyWords: Mybatis 原理,源码,Mybatis Mapper 接口实现类,代理模式,动态代理,Java动态代理,Proxy.newProxyInstance,Mapper 映射,Mapper 实现程序员
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎全部的 JDBC 代码和手动设置参数以及获取结果集。咱们在使用 Mybaits 进行 ,一般只须要定义几个 Mapper 接口,而后在编写一个 xml 文件,咱们在配置文件中写好 sql , Mybatis 帮咱们完成 Mapper 接口道具体实现的调用。以及将结果映射到 model bean 中。spring
咱们在项目中所编写的众多的 Mapper 类只是一个接口(interface ),根据 Java 的多态性咱们知道,可使用接口接口做为形参,进而在运行时肯定具体实现的对象是什么。可是,对于 Mapper 接口,咱们并无编写其实现类!Mybatis是如何找到其实现类,进而完成具体的 CRUD 方法调用的呢?原理何在?sql
为了弄清楚 Mapper 接口是如何找到实现类的,咱们先回忆一下 Mybatis 是怎么使用的,根据实际的例子,进而一点点的去分析。这里的使用指的是Mybatis 单独使用,而不是整合 spring , 由于整合 spring 的话,还须要涉及 Mapper dao 装载到 spring 容器的问题,spring 帮忙建立数据源配置等问题。数据库
一般咱们使用 Mybatis 的主要步骤是:缓存
上面咱们归纳了使用 Mybatis 的4个步骤。这4个步骤看起来很简单,可是用代码写出来就不少。咱们不妨先记着这4个步骤,再去看代码,会容易点。session
// 1. DataSource dataSource = BlogDataSourceFactory.getBlogDataSource(); TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("development", transactionFactory, dataSource); Configuration configuration = new Configuration(environment); configuration.addMapper(BlogMapper.class);// 添加Mapper接口 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); // 2. SqlSession session = sqlSessionFactory.openSession(); try { // 3. BlogMapper mapper = session.getMapper(BlogMapper.class); // 4. Blog blog = mapper.selectBlog(1); } finally { session.close(); }
在这块代码中,第 1 部分咱们使用了 Java 编码的形式来实现 SqlSessionFactory ,也可使用 xml 。若是使用xml的话,上面的第一部分代码就是这样的:mybatis
String resource = "org/mybatis/example/mybatis-config.xml"; // xml内容就不贴了 InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
咱们本次的目标是弄清楚 “ Mapper 是如何找到实现类的 ”,咱们注意上面代码 3 , 4 的位置:app
// 3. BlogMapper mapper = session.getMapper(BlogMapper.class); // 4. Blog blog = mapper.selectBlog(1);
这里 mapper 能够调用selectBlog(1) 这个方法,说明 mapper 是个对象,由于对象才具备方法行为实现啊。BlogMapper接口是不能实例化的,更没有具体方法实现。咱们并无定义一个类,让它实现BlogMapper接口,而在这里它只是经过调用session.getMapper() 所获得的。由此,咱们能够推断:确定是session.getMapper() 方法内部产生了BlogMapper的实现类。有什么技术能够根据BlogMapper 接口生成了一个实现类呢?想到这里,对于有动态代理
使用经验的程序员来讲,很容易想到,这背后确定是基于动态代理技术,具体怎么实现的呢?下面咱们来根据源码一探究竟。框架
从上面的代码中,咱们知道 BlogMapper 接口的实现类是从session.getMapper中得来的,大概是基于动态代理技术实现。咱们既然可以从SqlSession中获得BlogMapper接口的,那么咱们确定须要先在哪里把它放进去了,而后 SqlSession 才能生成咱们想要的代理类啊。上面代码中有这么一行:
configuration.addMapper(BlogMapper.class);
跟着这个 addMapper 方法的代码实现是这样的:
public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); }
咱们看到这里 mapper 实际上被添加到 mapperRegissry 中。继续跟进代码:
public class MapperRegistry { private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public <T> void addMapper(Class<T> type) { if (type.isInterface()) { // 只添加接口 if (hasMapper(type)) { // 不容许重复添加 throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<T>(type)); // 注意这里 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } }
看到这里咱们知道上面所执行的configuration.addMapper(BlogMapper.class);
其实最终被放到了HashMap中,其名为knownMappers
,knowMappers
是MapperRegistry 类的一个私有属性,它是一个HashMap
。其Key
为当前Class对象,value
为一个MapperProxyFactory
实例。
这里咱们总结一下: 诸如BlogMapper
之类的Mapper接口被添加到了MapperRegistry
中的一个HashMap中。并以 Mapper 接口的 Class 对象做为 Key , 以一个携带Mapper接口做为属性的MapperProxyFactory
实例做为value 。MapperProxyFacory
从名字来看,好像是一个工厂,用来建立Mapper Proxy的工厂。咱们继续往下看。
上面咱们已经知道,Mapper 接口被到注册到了MapperRegistry
中——放在其名为knowMappers 的HashMap属性中,咱们在调用Mapper接口的方法的时候,是这样的:
BlogMapper mapper = session.getMapper(BlogMapper.class);
这里,咱们跟踪一下session.getMapper() 方法的代码实现,这里 SqlSession 是一个接口,他有两个实现类,一个是DefaultSqlSession
,另一个是SqlSessionManager
,这里咱们用的是DefaultSqlSession
. 为何是DefaultSqlSession
呢?由于咱们在初始化SqlSessionFactory的时候所调用的SqlSessionFactoryBuilder
的build()方法里边配置的就是DefaultSqlSession
, 因此,咱们进入到DefaultSession类中,看看它对session.getMapper(BlogMapper.class)
是怎么实现的:
public class DefaultSqlSession implements SqlSession { private Configuration configuration; @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); //最后会去调用MapperRegistry.getMapper } }
如代码所示,这里的 getMapper 调用了 configuration.
MapperRegistry
,而此前咱们已经知道,
MapperRegistry
是存放了一个HashMap的,咱们继续跟踪进去看看,那么这里的get,确定是从这个hashMap中取数据。咱们来看看代码:
public class MapperRegistry { private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();// Mapper 映射 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); try { return mapperProxyFactory.newInstance(sqlSession); // 重点看这里 } catch (Exception e) { } } }
咱们调用的session.getMapper(BlogMapper.class);
最终会到达上面这个方法,这个方法,根据BlogMapper
的class对象,以它为key
在knowMappers
中找到了对应的value
—— MapperProxyFactory(BlogMapper) 对象,而后调用这个对象的newInstance()
方法。根据这个名字,咱们就能猜到这个方法是建立了一个对象,代码是这样的:
public class MapperProxyFactory<T> { //映射器代理工厂 private final Class<T> mapperInterface; private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } // 删除部分代码,便于阅读 @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { //使用了JDK自带的动态代理生成映射器代理类的对象 return (T) Proxy.newProxyInstance( mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
看到这里,就清楚了,最终是经过Proxy.newProxyInstance
产生了一个BlogMapper的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理,这个Proxy.newProxyInstance
方法生成代理类的三个要素是:
- ClassLoader —— 指定当前接口的加载器便可
- 当前被代理的接口是什么 —— 这里就是 BlogMapper
- 代理类是什么 —— 这里就是 MapperProxy
代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。咱们贴出MapperProxy的代码,以下:
public class MapperProxy<T> implements InvocationHandler, Serializable {// 实现了InvocationHandler @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理之后,全部Mapper的方法调用时,都会调用这个invoke方法 if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); // 注意1 } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了缓存 //执行CURD return mapperMethod.execute(sqlSession, args); // 注意2 } }
咱们调用的 Blog blog = mapper.selectBlog(1);
实际上最后是会调用这个MapperProxy
的invoke
方法。这段代码中,if
语句先判断,咱们想要调用的方法是否来自Object类,这里的意思就是,若是咱们调用toString()
方法,那么是不须要作代理加强的,直接还调用原来的method.invoke()就好了。只有调用selectBlog()
之类的方法的时候,才执行加强的调用——即mapperMethod.execute(sqlSession, args);
这一句代码逻辑。
而mapperMethod.execute(sqlSession, args);
这句最终就会执行增删改查了,代码以下:
public Object execute(SqlSession sqlSession, Object[] args) { Object result; if (SqlCommandType.INSERT == command.getType()) { //insert 处理,调用SqlSession的insert Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); } else if (SqlCommandType.UPDATE == command.getType()) { // update Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); } else if (SqlCommandType.DELETE == command.getType()) { // delete Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); } else if (SqlCommandType.SELECT == command.getType()) { // 删除部分代码 } else { throw new BindingException("Unknown execution method for: " + command.getName()); } // 删除部分代码 return result; }
再往下一层,就是执行JDBC那一套了,获取连接,执行,获得ResultSet,解析ResultSet映射成JavaBean。
至此,咱们已经摸清楚了Blog blog = mapper.selectBlog(1);
中,BlogMapper接口调用到获得数据库数据过程当中,Mybaitis 是如何为接口生成实现类的,以及在哪里出发了最终的CRUD调用。实际上,若是咱们在调用Blog blog = mapper.selectBlog(1);
以前,把从slqSession中获得的 mapper
对象打印出来就会看到,输出大概是这样的:
com.sun.proxy.$Proxy17
动态代理没错吧,Java动态代理实在是太美妙了。
上面咱们用层层深刻的方式摸清楚了 Mapper接口是如何找到实现类的。咱们分析了 Mapper接口是如何注册的,Mapper接口是如何产生动态代理对象的,Maper接口方法最终是如何执行的。总结起来主要就是这几个点:
1. Mapper 接口在初始SqlSessionFactory 注册的。 2. Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class value = 建立当前Mapper的工厂。 3. Mapper 注册以后,能够从SqlSession中get 4. SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。 5. 动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用。