首先简单回顾下代理模式

静态代理

概念:是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

简单代码演示:

抽象接口:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql

真实角色:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_02

代理角色:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_03

测试使用:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_04

动态代理

概念:是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

在jdk的api中提供了java.lang.reflect.Proxy它可以帮助我们完成动态代理创建

注意:在java中使用Proxy来完成动态代理对象的创建,只能为目标实现了接口的类创建代理对象。

动态代理是在内存中直接生成代理对象。

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_05

InvocationHandler接口:

接口中声明了一个方法

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_06

Invoke方法,它是在代理对象调用行为时,会执行的方法,而invoke方法上有三个参数:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_07

简单代码演示:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_08

简单使用:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_09

MyBatis中Mapper的动态代理

由上面的介绍可以看出,一般JDK的动态代理基本套路是这样:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_10

但是在MyBatis中却看着好像不是一个“正宗”的动态代理,在Mybatis中被代理的类只有一个Mapper接口,这里取名为TestMapper,在MyBatis中是这样的:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_11

我们定义了TestMapper,在MyBatis中通过配置,可以在Configuration中得到TestMapper,有MapperProxy,也建立了联系:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_12

但是却没有TestMapper的实现类。

在上面的动态代理测试方法中:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_13

至少是与TargetImpl差不多的。

但是在MyBatis中:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_14

这个代理明显与IdCarfMapper完全不一样。结合动态代理的特性,可以猜测下次debug会进入invoke方法:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_15

这里首先会比较当前调用的方法是不是来自Object类,意思就是比如当前的方法是toString()、hashCode()等这些来自Object的方法就直接走method.invoke()方法(这里也说明了框架作者考虑问题的严谨性);

然后回进一步判断isDefaultMethod(),代码注释如下,这里就不过多介绍了,判断的原因和上面差不多:

首先判断是不是default方法:

private boolean isDefaultMethod(Method method) {
return ((method.getModifiers()
//不是抽象并且不是静态的 public方法
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
//并且是接口
&& method.getDeclaringClass().isInterface();
}

通过MethodHandles调用default方法:

private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {//获得 MethodHandles.Lookup 的构造器(Lookup私有构造器)
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
//创建一个 MethodHandles.Lookup 实例
//调用 代理对象的 接口方法 (default方法)
return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}

前面的两个健壮性判断完毕后就开始真正的执行SQL干正事了,很明显真正执行SQL方法是不会像之前的动态代理那样简单的method.invoke(),因为在这个invoke方法里面需要传入一个被代理对象,这里明显只有一个IdCardMapper接口,在MapperProxy中是没有接口实例的,而是会先执行cachedMapperMethod()方法,获取MapperMethod,其实cachedMapperMethod()方法也很简单:

private MapperMethod cachedMapperMethod(Method method) {
//先从methodCache中获取MapperMethod
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}

而这个methodCache其实就是一个Map:

/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
//methodCache就是一个Map
private final Map<Method, MapperMethod> methodCache;

那么methodCache是从哪来的呢,其实就是构造的时候传递的一个引用:

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

而这个引用是从哪来的呢,其实就是工厂制造的时候传进去的:

public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

那又是从哪来的呢,其实也就是一个Map:

/**
* @author Lasse Voss
*/
public class MapperProxyFactory<T> {

private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

再来看看是怎么execute()的:

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//这里没有使用策略模式,因为没必要
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
//将参数convert
Object param = method.convertArgsToSqlCommandParam(args);
//又回到了sqlSession,使用sqlSession执行
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

再来看看selectOne()方法,其实也是比较简单的:

@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
//会调用selectList()方法
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
//只返回一个,取第一个即可
return list.get(0);
} else if (list.size() > 1) {
//这是MyBatis中很常见的一种异常
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

再看看selectList()方法:

@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//委托,单一职责
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

通过上面流程可以看到MyBatis的Mapper执行SQL虽然用到了动态代理,但是不是那种“正规”的动态代理。

继续查看executor.query()方法,在这里出现了一点“事故”,先看看这个executor:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_16

Executor是一个非常重要的接口:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_17


其中BaseExecutor是一个抽象实现类,这里也使用了模板设计模式,这里将一些共性抽取到了BaseExecutor中,这个模板设计模式的使用也是值得学习的。

这里理应应该要执行的是由子类实现的query()方法:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_18

但是进入的是Plugin类的invoke方法,这里又有一层代理:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_19

最后实际执行的是一个Interceptor的拦截的一个方法:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_20

一看到这个Interceptor,其实就恍然大悟了:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_21


这不就是MyBatis的Plugins嘛,因为我的项目中配置了PageHelper,所以这里进入了PageHelper,不过这个也顺便说明了PageHelper是在执行语句之前执行的: 

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_22

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_23

这里PageHelper不是主要的,以后再分析,为了避免干扰,先把PageHepler的引入和配置去掉。

去掉后,正常了,进入了org.apache.ibatis.executor.BaseExecutor#query,BaseExecutor是一个抽象类:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_24

这里首先会获取CacheKey,在MyBatis中的CahcheKey设计的非常好,可以看下,这里就不过多介绍了。

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_25

这里的ErrorContext明显是ThreadLocal的一个封装,这个也是一个非常重要的东东,它对异常信息的封装便于使用此框架的人能够快速的排错:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_26

会先走一级缓存,如果一级缓存没有拿到,会执行下面的方法:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_27

这个方法里面有个很有意思的一个方法,localCache会先putObject()一下,这行代码的意义是声明一个占位符,当发送一次查询数据的请求时,设置该占位符告诉其他请求正在查询数据库,请其他请求先阻塞或休眠。当这次请求查询到数据之后,将真正的数据放到占位符的位置,缓存数据。如果其他请求与该次请求查询的数据时一样的,直接从一级缓存中拿数据减少了查询请求对数据库的压力 (org.apache.ibatis.executor.BaseExecutor.DeferredLoad#load org.apache.ibatis.executor.BaseExecutor.DeferredLoad#canLoad),接下来会执行doQuery()方法,doQuery()方法是BaseExecutor中的一个模板方法:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_28

后面就是JDBC的执行流程了:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_29

这里会有一个拦截器链去执行Plugins的拦截:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_30

当sqlsessionFactory获取sqlsession时,产生的ParameterHandler、ResultSetHandler、StatementHandler、Executor都是由org.apache.ibatis.session.Configuration 类的方法 newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor产生的代理对象,而这些代理对象是经过适用规则的plugin层层代理的 。

最后会返回一个StatementHandler:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_31

是不是和上面的Executor非常相似,也说明这个设计模式是多么的重要。

在prepareStatement()方法中,获取了Connection:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_32

接下来会执行handler.<E>query(stmt, resultHandler):

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_33

而这个delegate也就是SatementHandler:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_34

接下来看到了我们熟悉的PrepareStatement(是不是也说明MyBatis是防SQL注入的):

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_35

执行完成后会由ResultSetHandler处理结果:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_36

再往下:

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
//又看到了熟悉的ErrorContext,不过这个activity的名称与之前的不一样
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

final List<Object> multipleResults = new ArrayList<Object>();

int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);

List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}

String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}

return collapseSingleResultList(multipleResults);
}

进入handlerResultSet方法:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_37

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_38

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_39

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_40

首先会创建一个返回结果对象:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_41

会执行org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, org.apache.ibatis.executor.loader.ResultLoaderMap, java.lang.String)方法,首先会循环遍历为ResultMapping属性赋值。如果是嵌套查询而且配置了延迟加载,其中这里的createProxy()方法会生成一个具有延迟加载功能的代理对象:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_42

在官方文档中对这里也有相关的说明:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_43

这里还要注意的是,这里返回的是一个所有属性都为空的结果:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_44

获取到返回对象后,就开始设值了:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_45

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_46

MyBatis的关闭也是很有讲究的,ResultSet就会关闭与ResultSet相关的东东:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_47

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_48

最后会执行:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_49

只返回一个结果集就取第一个元素,否则全部返回:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_50

最后代码再返回,会发现这里又会执行closeStatement:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_51

这也是更加说明MyBatis是一层一层关闭的:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_52

最后会将ErrorContext reset一下:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_53

MyBatis源码分析(Mapper动态代理的实现及执行流程)_List_54

这个是没有异常的情况,如果有异常就会执行catch里面的内容:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_sql_55

如果出现Exception,一目了然:

MyBatis源码分析(Mapper动态代理的实现及执行流程)_动态代理_56