前两天公司谈绩效,我有讲到,最近在看源码。说到在看Mybatis源码的时候,当领导问到我,Mybatis源码中有没有什么在我们项目中能够借鉴的地方,我一愣。愣是没有想出来。这个问题,发人深省。
谈到源码,对于我这种1-3年的菜鸟来说,会自豪的说我看过这个这个的源码,那个那个的源码。现在想来,太可怕了。我似乎走入了一个误区。看源码的目的到底是什么?从看源码的过程中,我能学到什么?
今天,就来分享下,我看Mybatis源码后的一些思考。这篇重点说一下,动态代理模式,在Mybatis框架中的应用。
了解动态代理的,都知道,正常的动态代理。是这个样子滴
而用过Mybatis的,都会发现,它的动态代理是这样的
显然,Mybatis的动态代理被阉割了。。。dao层是只有接口没有实现类的。为什么这么做呢?因为,Mybatis不需要dao层接口有实现类,为什么呢?下面带大家来深入源码,来发掘这个特点所带来的好处。
干看源码,对整体的调用关系不是非常清晰,我这里贴一张我整理的时序图,虽然啊 虽然很简略,但可以清晰看出代码的关系,如下
我们通过configuration创建了一个sqlSession,调用getMapper对sqlSession进行填充,通过动态代理调用selectOne方法,调用Executor执行query方法,从而完成一次Query的JDBC。下面我们来看具体细节。
当然,我们先看MapperProxy,为什么?因为调用dao层方法时,下一步就是这个类啊···。上源码
//解释下,一个是动态代理接口InvocationHandler,一个是序列化接口Serializable
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
//在sqlSession调用getMapper时,实例化的当前类。
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> 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);
//重点看下,mapperMethod都做了什么呢?
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//此处new了一个MapperMethod,并将method存入了methodCache,Key为method,value为mapperMethod
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
MapperProxy类是Mybatis动态代理的枢纽类。都做了什么呢1、初始化了MapperMethod,并调用了execute方法执行了sql。下面来看Mybatis动态代理的核心类:MapperMethod
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
//与sql相关的解析操作,咳咳后面详细分析
this.command = new SqlCommand(config, mapperInterface, method);
//同样,与调用方法相关操作,后面详细分析
this.method = new MethodSignature(config, mapperInterface, method);
}
//主要看execute方法。分CRUD,我的demo调用的是SELECT的selectOne方法
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 {
//字面意思,将数组参数转成SqlComman类型的参数
Object param = method.convertArgsToSqlCommandParam(args);
//通过sqlSession调用dao方法,并传入相应参数
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
}
今天呢。我们不谈细节,明天接着聊。