Mybatis对于我们并不陌生,但他实际工作原理是怎样的呢?希望看完本篇文章,大家能了解一下问题
Mybatis有两处都用了动态代理。一是Mapper接口代理,二是SqlSession代理。具体实现,下面逐一剖析
经历过 MapperScannerConfigurer
的初始化,包路径下的Mapper接口都注册成为的Spring的bean,其BeanName是接口名,BeanClass是 MapperFactoryBean 。 MapperFactoryBean
是工厂Bean,实现了 FactoryBean
这个接口。
public interface FactoryBean<T> { //返回对象的实例 T getObject() throws Exception; //返回对象实例的类型 Class<?> getObjectType(); //是否为单例 boolean isSingleton(); } 复制代码
再看看Spring在获取Bean时的调用链 getBean() -> doGetBean() -> AbstractBeanFactory::getObjectForBeanInstance() -> FactoryBeanRegistrySupport::getObjectFromFactoryBean() -> FactoryBean::getObject()
因此,当IOC调用时,返回的实例不是MapperFactoryBean对象本身,而是getObject()返回的实例,类型是getObjectType()返回的类型。
接下来看看MapperFactoryBean的 getObject()
实现了什么。
@Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } 复制代码
MapperFactoryBean继承了SqlSessionDaoSupport, getSqlSession()
返回的是 SqlSessionTemplate 。SqlSessionTemplate继承了 SqlSeesion
接口
public class SqlSessionTemplate implements SqlSeesion @Override public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); } 复制代码
getConfiguration()最终返回的是DefaultSqlSessionFactory的Configuration(Mybatis属性的大管家)
public Configuration{ public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } } 复制代码
这里出现了MapperRegistry类,下面具体看看它的实现:
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) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } 复制代码
到了这里,终于看到了熟悉的动态代理身影。代理的是Mapper接口,处理类是MapperProxy。
public class MapperProxyFactory<T> { protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { // MapperProxy是 final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } 复制代码
IOC调用Mapper的方法,最终会由MapperProxy使用当前线程的sqlSession执行JDBC操作。
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (method.isDefault()) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } 复制代码
在getMapper()时细心的小伙伴会存在疑问, final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
是怎么来的。这要从 MapperRegistry 类的 addMapper()
方法说起
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<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); // 将注解中的sql转换成sqlStatement parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } 复制代码
addMapper()会将当前接口类作为key,MapperProxyFactory为value存到knownMappers中。MapperProxyFactory会在getMapper()执行时取出,生成代理类。
那addMapper()一定是在getMapper()前调用的,实际调用时机是什么时候呢? 追根溯源, addMapper() <- Configuration::addMapper() <- MapperFactoryBean::checkDaoConfig()
MapperFactoryBean的继承类如下:
再看看DaoSUpport类:
@Override public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { // Let abstract subclasses check their configuration. checkDaoConfig(); ... } 复制代码
结论:SpringBoot启动时会扫描包路径下所有Mapper接口注册成Bean,每个Mapper接口Bean的初始化完成并属性设置完成后,都会调用 checkDaoConfig()
,由此将Mapper加载到knownMappers供IOC时使用。当Spring GetBean时,最终会调用到getMapper(),返回当前Mapper接口的动态代理类作为实现类。