Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码
在文章:mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题:
1,为什么在以前的代码流程中从来没有addmapper,而这里却有getmapper?
2,userdao明明是我们定义的一个接口类,根本没有定义实现类,那这个usermapper是什么?是mybatis自动为我们生成的实现类吗?
为了更好的解释着两个问题,我们需要重新认识configuration这个类。
但是在这之前,你需要了解一个概念(设计模式):java设计模式-动态代理(proxy)示例及说明。否则你可能对接下来的流程一头雾水。
一,再次认识configuration
public class configuration { //映射注册表 protected mapperregistry mapperregistry = new mapperregistry(this); // 获取映射注册表 public mapperregistry getmapperregistry() { return mapperregistry; } //添加到映射注册表 public void addmappers(string packagename, class<?> supertype) { mapperregistry.addmappers(packagename, supertype); } //添加到映射注册表 public void addmappers(string packagename) { mapperregistry.addmappers(packagename); } //添加到映射注册表 public <t> void addmapper(class<t> type) { mapperregistry.addmapper(type); } //从映射注册表中获取 public <t> t getmapper(class<t> type, sqlsession sqlsession) { return mapperregistry.getmapper(type, sqlsession); } //判断映射注册表中是否存在 public boolean hasmapper(class<?> type) { return mapperregistry.hasmapper(type); } }
mapperregistry源码:
public class mapperregistry { private configuration config; //映射缓存 键:类对象,值:映射代理工厂 private final map<class<?>, mapperproxyfactory<?>> knownmappers = new hashmap<class<?>, mapperproxyfactory<?>>(); public mapperregistry(configuration config) { this.config = config; } //从映射注册表中获取 @suppresswarnings("unchecked") 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); } } //判断映射注册表中是否存在 public <t> boolean hasmapper(class<t> type) { return knownmappers.containskey(type); } //添加到映射注册表 public <t> void addmapper(class<t> type) { if (type.isinterface()) { if (hasmapper(type)) { throw new bindingexception("type " + type + " is already known to the mapperregistry."); } boolean loadcompleted = false; try { knownmappers.put(type, new mapperproxyfactory<t>(type)); // it's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. if the type is already known, it won't try. // 处理接口类(例如userdao)中的注解 mapperannotationbuilder parser = new mapperannotationbuilder(config, type); parser.parse(); loadcompleted = true; } finally { if (!loadcompleted) { knownmappers.remove(type); } } } } /** * @since 3.2.2 */ //获取缓存中所有的key,并且是不可修改的 public collection<class<?>> getmappers() { return collections.unmodifiablecollection(knownmappers.keyset()); } /** * @since 3.2.2 */ //添加到映射注册表 public void addmappers(string packagename, class<?> supertype) { resolverutil<class<?>> resolverutil = new resolverutil<class<?>>(); resolverutil.find(new resolverutil.isa(supertype), packagename); set<class<? extends class<?>>> mapperset = resolverutil.getclasses(); for (class<?> mapperclass : mapperset) { addmapper(mapperclass); } } /** * @since 3.2.2 */ //添加到映射注册表 public void addmappers(string packagename) { addmappers(packagename, object.class); } }
在方法getmappers中用到了collections.unmodifiablecollection(knownmappers.keyset());,如果你不了解,可以查阅:collections.unmodifiablemap,collections.unmodifiablelist,collections.unmodifiableset作用及源码解析
在了解了这两个类之后,就来解决第一个问题:1,为什么在以前的代码流程中从来没有addmapper,而这里却有getmapper?
二,addmapper和getmapper
1,关于addmapper,在文章mybatis源码解析,一步一步从浅入深(五):mapper节点的解析中,不知道大家有没有意识到,我少了一个部分没有解读:
对了,就是第四部分的:绑定已经解析的命名空间
代码:bindmapperfornamespace();
是的,addmapper就是在这个方法中用到的。但是前提是,你需要了解java的动态代理。来看看源码:
private void bindmapperfornamespace() { //获取当前命名空间(string:com.zcz.learnmybatis.dao.userdao) 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); } } } }
看到了吧,就在最后一行代码。但是这里并不是简单的保存了一个类对象,而是在mapperregistry中进行了进一步的处理:
1 //添加到映射注册表 2 public <t> void addmapper(class<t> type) { 3 if (type.isinterface()) { 4 if (hasmapper(type)) { 5 throw new bindingexception("type " + type + " is already known to the mapperregistry."); 6 } 7 boolean loadcompleted = false; 8 try { 9 // 在这里,保存的是new mapperproxyfactory实例对象。 10 knownmappers.put(type, new mapperproxyfactory<t>(type)); 11 // it's important that the type is added before the parser is run 12 // otherwise the binding may automatically be attempted by the 13 // mapper parser. if the type is already known, it won't try. 14 // 处理接口类(例如userdao)中的注解 15 mapperannotationbuilder parser = new mapperannotationbuilder(config, type); 16 parser.parse(); 17 loadcompleted = true; 18 } finally { 19 if (!loadcompleted) { 20 knownmappers.remove(type); 21 } 22 } 23 } 24 }
在第10行,保存的是映射代理工厂(mapperproxyfactory)的实例对象。到这里addmapper就解释清楚了。接下来看看getmapper方法。
2,getmapper
调用的地方:在文章mybatis源码解析,一步一步从浅入深(二):按步骤解析源码第三步中。
代码:configuration.<t>getmapper(type, this);
type:是userdao.class
this:sqlsession的实例化对象
从第一部分configuration中可以发现,configuration又调用了mapperregistry的getmapper方法
1 //从映射注册表中获取 2 @suppresswarnings("unchecked") 3 public <t> t getmapper(class<t> type, sqlsession sqlsession) { 4 final mapperproxyfactory<t> mapperproxyfactory = (mapperproxyfactory<t>) knownmappers.get(type); 5 if (mapperproxyfactory == null) 6 throw new bindingexception("type " + type + " is not known to the mapperregistry."); 7 try { 8 return mapperproxyfactory.newinstance(sqlsession); 9 } catch (exception e) { 10 throw new bindingexception("error getting mapper instance. cause: " + e, e); 11 } 12 }
从代码的第4行可以清晰的看到,或者根据类对象从缓存map中,获取到了addmapper中保存的mapperproxyfactory对象实例。但是并没有将这个对象实例直接返回,而是通过调用的mapperproxyfactory的newinstance方法返回的一个userdao实现类。接下来我们i就解释一下第二个问题:2,userdao明明是我们定义的一个接口类,根本没有定义实现类,那这个usermapper是什么?是mybatis自动为我们生成的实现类吗?
三,映射代理类的实现
看过java设计模式-动态代理(proxy)示例及说明这篇文章的同学应该知道这个问题的答案了,usermapper是一个代理类对象实例。是通过映射代理工厂(mapperproxyfactory)的方法newinstance方法获取的。
但是在这里mybatis有一个很巧妙的构思,使得这个的动态代理的使用方法和文章java设计模式-动态代理(proxy)示例及说明中的使用方法有些许不同。
不妨在你的脑海中回顾一下java设计模式-动态代理(proxy)示例及说明中实现动态代理的关键因素:
1,一个接口
2,实现了接口的类
3,一个调用处理类(构造方法中要传入2中的类的实例对象)
4,调用proxy.newproxyinstance方法获取代理类实例化对象
带着这个印象,我们来分析一下mybatis是怎么实现动态代理的。既然usermapper是通过映射代理工厂(mapperproxyfactory)生产出来的,那么我们就看看它的源码:
1 //映射代理工厂 2 public class mapperproxyfactory<t> { 3 // 接口类对象(userdao.class) 4 private final class<t> mapperinterface; 5 // 对象中的方法缓存 6 private map<method, mappermethod> methodcache = new concurrenthashmap<method, mappermethod>(); 7 8 //构造器 9 public mapperproxyfactory(class<t> mapperinterface) { 10 //为接口类对象赋值 11 this.mapperinterface = mapperinterface; 12 } 13 14 public class<t> getmapperinterface() { 15 return mapperinterface; 16 } 17 18 public map<method, mappermethod> getmethodcache() { 19 return methodcache; 20 } 21 22 // 实例化映射代理类 23 @suppresswarnings("unchecked") 24 protected t newinstance(mapperproxy<t> mapperproxy) { 25 return (t) proxy.newproxyinstance(mapperinterface.getclassloader(), new class[] { mapperinterface }, mapperproxy); 26 } 27 28 // 实例化映射代理类 29 public t newinstance(sqlsession sqlsession) { 30 final mapperproxy<t> mapperproxy = new mapperproxy<t>(sqlsession, mapperinterface, methodcache); 31 return newinstance(mapperproxy); 32 } 33 34 }
我们调用的newinstance方法就是第29行的方法。然后这个方法又调用了24行的方法。我们来看25行的代码,是不是很熟悉?proxy.newproxyinstance(类加载器,接口类对象数组,实现了invocationhandler的对象实例 mapperproxy),到这里映射代理类的实例化已经解释清楚了,也就是解决了第二个问题,接下来我们扩展一下:
现在我们还没有看到mapperproxy类的源码,但是我们可以大胆的猜测,类mapperproxy一定是实现了invocationhandler接口,并且也一定实现了invoke方法:
1 // 映射代理 2 public class mapperproxy<t> implements invocationhandler, serializable { 3 private static final long serialversionuid = -6424540398559729838l; 4 private final sqlsession sqlsession; 5 private final class<t> mapperinterface; 6 private final map<method, mappermethod> methodcache; 7 8 //构造方法 9 public mapperproxy(sqlsession sqlsession, class<t> mapperinterface, map<method, mappermethod> methodcache) { 10 this.sqlsession = sqlsession; 11 this.mapperinterface = mapperinterface; 12 this.methodcache = methodcache; 13 } 14 //代理类调用的时候执行的方法 15 public object invoke(object proxy, method method, object[] args) throws throwable { 16 // 检查method方法所在的类是否是object 17 if (object.class.equals(method.getdeclaringclass())) { 18 try { 19 return method.invoke(this, args); 20 } catch (throwable t) { 21 throw exceptionutil.unwrapthrowable(t); 22 } 23 } 24 // 应用缓存 25 final mappermethod mappermethod = cachedmappermethod(method); 26 //执行查询 27 return mappermethod.execute(sqlsession, args); 28 } 29 30 //应用缓存 31 private mappermethod cachedmappermethod(method method) { 32 mappermethod mappermethod = methodcache.get(method); 33 if (mappermethod == null) { 34 mappermethod = new mappermethod(mapperinterface, method, sqlsession.getconfiguration()); 35 methodcache.put(method, mappermethod); 36 } 37 return mappermethod; 38 } 39 }
如果你实实在在的明白了java设计模式-动态代理(proxy)示例及说明这篇文中所叙述的内容,相信这篇文章不难理解。
好了,mybatis源码解析到这里,已经基本接近尾声了,继续探索吧:mybatis源码解析,一步一步从浅入深(二):按步骤解析源码
原创不易,转载请声明出处:https://www.cnblogs.com/zhangchengzi/p/9706395.html
更多干货,请查阅:
上一篇: 教你如何设置微信公众号实现报名功能
下一篇: 刚才