[上一篇]:Mybatis源码解析之配置解析
从菜菜的Mybatis源码解析之Spring获取Mapper过程中知道了Spring与MyBatis如何连接起来的,这篇菜菜将介绍MyBatis的重中之重MapperProxy
问题
为什么MyBatis只用写接口不用写实现就可以运行起来?
思考
既然Spring与MyBatis连接起来了,那么通过MyBatis中的这些Mapper接口注入的类都是由Spring进行管理的,所以需要知道Spring中这些Mapper接口对应的Bean到底是什么。
揭秘答案-源码
Mybatis源码解析之Spring获取Mapper过程这篇里说了设置了这些Mapper接口的class对象或是类的全限定名为MapperFactoryBean ,这里再贴出源码
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
下面需要看下MapperFactoryBean的源码
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
private Class mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
//intentionally empty
}
public MapperFactoryBean(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
//主要看这个方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class getObjectType() {
return this.mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class getMapperInterface() {
return mapperInterface;
}
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
public boolean isAddToConfig() {
return addToConfig;
}
}
从MapperFactoryBean源码中看出,它继承了SqlSessionDaoSupport并实现了Spring中的FactoryBean,SqlSessionDaoSupport是一个抽象的支持类,用来为你提供SqlSession,将在Mybatis源码解析之SqlSession来自何方
中讲解。因为实现了FactoryBean接口所以MapperFactoryBean是一个FactoryBean。
我们知道Spring实例化一个类有两个时机,一个是在初始化的时候getBean实例化,第二个是对设置了lazy-init属性的在第一次向Spring索要Bean的时候getBean实例化,最终都是调用Spring中的getBean方法
MyBatis中设置Mapper接口的class对象为MapperFactoryBean,而MapperFactoryBean又是一个FactoryBean所以在实例化调用getBean最终会调用MapperFactoryBean中的getObject()方法,将改方法返回的对象作为实例化Bean,我们先看下getObeject的调用链(初始化是从refresh中的finishBeanFactoryInitialization开始进入preInstantiateSingletons再开始的,需要一层一层看),再看getObject()的方法
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这里的getSqlSession()会得到SqlSessionTemplate,为什么是SqlSessionTemplate,详见Mybatis源码解析之SqlSession来自何方
//SqlSessionDaoSupport里的方法,返回的是SqlSessionTemplate
public SqlSession getSqlSession() {
return this.sqlSession;
}
查看SqlSessionTemplate中的getMapper源码,是从Configuration中getMapper
//SqlSessionTemplate中部分方法
@Override
public T getMapper(Class type) {
return getConfiguration().getMapper(type, this);
}
@Override
public Configuration getConfiguration() {
return this.sqlSessionFactory.getConfiguration();
}
Configuration中获取Mapper源码,又是从mapperRegistry获取的,mapperRegistry相当于mapper的注册中心,这里能获取到所有的mapper信息
//Configuration 中getMapper
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry中getMapper,看到这里答案快浮出水面了,通过MapperProxy的工厂MapperProxyFactory创建代理对象
//MapperRegistry 中的getMapper
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
MapperProxyFactory的具体实现源码
public class MapperProxyFactory {
private final Class mapperInterface;
private final Map methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class getMapperInterface() {
return mapperInterface;
}
public Map getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
答案至此终于揭开,最终在Spring中这些接口对应的对象为JDK动态代理生成的Mapper代理类MapperProxy。我们在程序中拿到的Mapper对象也是MapperProxy。我们继续看下MapperProxy的源代码
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
在我们利用Mapper进行数据库的相关操作时会调用MapperProxy的invoke()方法,该方法先判断接口方法是否有实现类,如果有,调用实现类的方法,如果没有(MyBatis的Mapper没有实现类)就调用MapperMethod的execute方法
通过看MapperMethod源码发现最终还是调用sqlSession中的相关方法,sqlSession再委托给Excutor去执行,关于Excutor菜菜还没写相关文章,请自行搜索
如有错误,欢迎各位大佬斧正!
[下一篇]:Mybatis源码解析之SqlSession来自何方