MyBatis使用Proxy动态代理实现
我们获取到SQLSession后,会调用getMapper()方法来返回对象实例,那么这块到底是干了什么?
我们跟进源码看一看:
DefaultSqlSession类:
可以看到它是调用了configuration类的getMapper方法。configuration类主要是存放了所有有关的配置信息
它又调用了mapperRegistry的getMapper()方法,我们现在还不知道mapperRegistry这是干什么的,现在我们继续跟进
knownMappers是一个字典类型。从Key的类型上我们可以判断出来是一个类一个动态代理工厂。
可以看到这个方法刚开始就是从这个集合中获取mapper,那么我们什么时候将mapper注册进去的呢?
在解析xml文件的时候就已经处理了
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//
//解析mapper节点
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//这里就是绑定mapper和class的地方
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
通过前面注册了class的全路径,使用mapperProxyFactory来创建mapper接口实现类。这里的mapperProxyFactory会为每一个mapper都对应一个mapperProxyFactory。因为configuration.addMapper(boundType);
里面使用map接口为每一个mapper都创建了一个工厂
向knownMappers这个集合添加了这个Mapper
我们再回到getMapper()方法:
进入这个newInstance方法
可以看到它创建了一个mapper ProXy对象,看到这个是不是就能想到我们的动态代理那个继承Invocation Handler类的那个XXXProXy类是吧。
调用它 的invoke方法
上面代码中可以看到如果是执行Object类的方式那么直接调用method.invoke。如果不是Object的方法,那么执行的是MapperMethod方法。我们知道一个接口没有实现是不能够被实例化的,并且我们在写接口时,确实没有给任何实现,那么Mybatis是怎么帮我们做事的呢?就是上面这段代码。首先通过代理类生成代理对象。当执行接口中的方法时,都是调用这个invoke方法,当你不是调用Object下面的方法,那么统一都执行MapperMethod方法来执行。
从缓存中获得执行方法对应的MapperMethod类实例。如果MapperMethod类实例不存在的情况,创建加入缓存并返回相关的实例。最后调用MapperMethod类的execute方法。
mapperMethod持有接口名,方法名,configuration。而接口名对应mapper标签的namespace;方法名对应
这个类有两个成员变量:1,SqlCommand 2,MethodSignature
我们都会知道节点上有id属性值。那么MyBatis框架会把每一个节点(如:select节点、delete节点)生成一个MappedStatement类。要找到MappedStatement类就必须通过id来获得。有一个细节要注意:代码用到的id = 当前接口类 + XML文件的节点的ID属性。
我们现在查看一下mapper.execute()方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if (SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if (SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if (SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else {
if (SqlCommandType.SELECT != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
从上面的代码可以看出所有的所有的mapper对象实际都是MapperMethod对象,然后MapperMethod持有方法名『全类名.方法名』还有执行的操作『select;update;insert;delete』还有sql的各种参数方法签名。然后执行sql语句
跟进去每个Sqlsession.方法()例如update方法:
发现最终调用的是executor执行器对应的方法。
跟进去这个方法
最终就是调用statement的执行方法。执行了sql语句。
主要就是这几个点:
1. Mapper 接口在初始SqlSessionFactory 注册的。
2. Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class value = 创建当前Mapper的工厂。
3. Mapper 注册之后,可以从SqlSession中get
4. SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。
5. 动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用。