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 }
那么,mybatis使用动态代理的方式跟文章JAVA设计模式-动态代理(Proxy)示例及说明使用动态代理的方式,有哪些不同呢?
1,一个接口(UserDao)
2,实现了接口的类(没有)
3,一个调用处理类(构造方法中要传入2中的类的实例对象)(2中没有,自然这里也不会传入对象)
4,调用Proxy.newProxyInstance方法获取代理类实例化对象(有的)
如果你实实在在的明白了JAVA设计模式-动态代理(Proxy)示例及说明这篇文中所叙述的内容,相信这篇文章不难理解。
好了,mybatis源码解析到这里,已经基本接近尾声了,继续探索吧:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码
原创不易,转载请声明出处:https://www.cnblogs.com/zhangchengzi/p/9706395.html
才疏学浅,如有错误,欢迎大家留言评论。
如果大家觉得我的文章写的不错,“关注我”或者“推荐一下”吧,我会继续努力写出更好的文章。
觉得哪里写的不好的,可以留言或者加我微信告诉我。
另外我建立一个技术讨论群,欢迎大家来学习探讨,微信:fcg606808。