本文分析下mapper接口被动态代理的过程。本系列文章,都是基于springboot的,因为在实际的生成环境中,大多都是这么用的。
上篇文章《Mybatis源码(一)利用springboot集成&Mapper接口加载过程》中,我们最后知道,mapper接口在注册bean定义的过程中,在AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions中,修改了bean定义,mapper接口最终都被注册为了MapperFactoryBean类型。也就是说所有mapper接口都成为了MapperFactoryBean,但是他们的对象是不相等的。这个bean内部有个 private Class mapperInterface;属性,每个对象虽然类型一样,但是这个属性值是不一样的。
看下MapperFactoryBean的类图:
1. Mapper接口实例化MapperFactoryBean
从spring的生命周期我们知道,bean注册后,在使用的时候就会被实例化,mapper接口也是如此。动态代理,就是发生在这个实例化的过程中。
spring实例化bean的过程,参考文章《spring5源码阅读(三)BeanFactory#getBean(String name)》的最后一节。
并且,动态代理是发生在mybatis的代码中,而非spring中;因为mybatis脱离spring也是可以单独使用的。mybatis的动态代理,也是基于jdk的动态代理实现。
现在加如我们在一个controller中引用了一个UsersMapper接口:
@Resource
private UsersMapper usersMapper;
那么当spring实例化controller的时候没必然就会先实例化依赖的bean,也就是usersMapper。
1. spring实例化代码位置:
AbstractBeanFactory#doGetBean() ->>getObjectForBeanInstance()
2. MapperFactoryBean#getObject()
因为mapper接口bean都被注册为了MapperFactoryBean,所以实例化对象是最终调用MapperFactoryBean#getObject()方法:
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
看下getSqlSession()方法:
public SqlSession getSqlSession() {
return this.sqlSession;
}
3. SqlSessionTemplate类
上篇文章中,我们提到了两个属性 sqlSessionTemplate 和 sqlSessionFactory,其中SqlSessionTemplate就是sqlSession接口的实现类。
那么这俩属性是如何被注入的呢?
上篇文章最后我们知道mapper在注册定义的时候,设置了属性注入类型是AbstractBeanDefinition.AUTOWIRE_BY_TYPE,这样spring就会扫描目标bean中的set方法,并对相关属性进行属性注入。
通过类图发现MapperFactoryBean继承了类SqlSessionDaoSupport,看下类结构:
正好有两个set方法,这样spring就能注入sqlSessionTemplate 和 sqlSessionFactory这俩属性。这样spring在实例化mapper bean后,再填充属性的时候,就能把这俩属性注入进去。
继续上文getSqlSession().getMapper(this.mapperInterface),getSqlSession()返回的就是sqlSessionTemplate,所以getMapper() 就在SqlSessionTemplate中:
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
SqlSessionFactory类
还得在看下getConfiguration()方法:
@Override
public Configuration getConfiguration() {
return this.sqlSessionFactory.getConfiguration();
}
看,用的是sqlSessionFactory类,上面说的两个属性都用上了,至于这俩属性是如何被实例化的,放在后续文章中分析。
Configuration类
继续看getMapper()方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
- Configuration:mybatis核心类,类似spring的beanfactory容器,存放了mybaits的属性配置,以及mapper的注册信息。Configuration的初始化是在SqlSessionFactory初始化的时候,比较复杂。mapper的xml文件,也是在这个过程中被扫描到。
- mapperRegistry:Configuration中的一个属性,内部存放了mapper接口和MapperProxyFactory的映射关系。
knownMappers.put(type, new MapperProxyFactory(type));
MapperProxyFactory就是用来创建代理类的。
2. MapperProxyFactory创建代理类
继续上文的mapperRegistry.getMapper(type, sqlSession);方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);
}
}
继续进入newInstance方法:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
到这基本就清楚了,就是利用的jdk的动态代理类Proxy实现。
代理类是MapperProxy,打开看看比较核心的invoke方法:
@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 (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用mapper接口方法时,最终就是这里执行的sql
return mapperMethod.execute(sqlSession, args);
}