本篇博文是原理分析的第三篇。当使用mapper接口进行CRUD时,其实是其代理对象在发挥作用,SQLsession获取mapper接口的代理对象时序图如下:
【1】DefaultSqlSession
如下代码所示,这里其实是调用了configuration实例的方法。该方法是一个泛型方法,参数有Class<T> type表示你的接口Class对象,比如UserMapper.class---interface com.jane.mapper.UserMapper
@Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
【2】Configuration
configuration有常量成员protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
实例,mapperRegistry 引用了当前configuration。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
MapperRegistry属性和构造方法如下:
public class MapperRegistry { private final Configuration config; //注意这个哦 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public MapperRegistry(Configuration config) { this.config = config; } //.. }
一文中我们可以得知创建SqlSessionFactory时对所有的mapper(xml和接口)进行了解析并为每一个mapper接口创建了MapperProxyFactory对象放入knownMappers 中。
MapperRegistry的addMapper方法如下:
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放入当前解析的mapper对应的MapperProxyFactory实例 knownMappers.put(type, new MapperProxyFactory<>(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. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
【3】MapperRegistry
MapperRegistry的getMapper方法如下所示:
@SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //首先获取MapperProxyFactory final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { //触发MapperProxyFactory的newInstance return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
代码解释如下:
- ① 从knownMappers中获取当前class对象的mapperProxyFactory实例;
- ② 如果不存在则抛出异常;
- ③ 如果存在则执行
mapperProxyFactory.newInstance(sqlSession)
来获取当前mapper
的代理对象
【4】MapperProxyFactory
MapperProxyFactory主要属性和构造方法
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } //... }
可以看到其除了接口的class对象外还维护了一个私有ConcurrentHashMap类型常量methodCache
。
创建实例对象MapperProxy
public T newInstance(SqlSession sqlSession) { //首先实例化得到一个MapperProxy,然后创建得到其代理对象 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
代码解释如下:
① 根据sqlsession、mapperInterface(接口的class对象),以及类型的methodCache创建MapperProxy
② 为Mapper创建代理对象
这里需要注意的是MapperProxy是一个InvocationHandler类型,需要实现Object invoke(Object proxy, Method method, Object[] args)方法。
MapperProxy的主要属性和构造方法
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; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } //... }
InvocationHandler是什么呢?InvocationHandler是由代理实例的调用处理程序实现的接口。也就是说一个类/接口的代理实例的调用处理程序必须实现InvocationHandler接口的invoke方法。可以理解为本文中MapperProxy就是Mapper代理实例的调用处理程序。
InvocationHandler的invoke方法如下:
Object invoke(Object proxy, Method method, Object[] args) proxy:代理实例对象 method:目标方法 args:方法入参
Proxy是什么?Proxy是专门完成代理的操作类,是所有动态代理类的父类,通过此类为一个或多个接口动态地生成实现类。使用 Proxy 生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理
//直接创建一个动态代理对象 static Object newProxyInstance( ClassLoader loader, Class<?>[] interfaces,InvocationHandler h ) loader :定义代理类的类加载器 interfaces:被代理类实现的所有接口 h:代理实例的调用处理程序 该方法将会返回一个代理对象,代理对象有代理调用处理程序--InvocationHandler
根据mapperProxy创建Mapper的代理对象
@SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } //也就是说说触发mapper的方法时会交给mapperProxy来处理
可以看下最终得到的Mapper的代理对象如下(h 表示其是一个InvocationHandler也就是调用处理程序
):
每个代理实例都有一个关联的调用处理程序InvocationHandler。在代理实例上调用方法时,方法调用将被编码并发送到其调用处理程序的invoke
方法。
【5】MapperMethod
上面我们提到了MapperProxyFactory
有常量成员methodCache
,在类加载过程中就进行了初始化。
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
根据MapperProxyFactory
生成MapperProxy
实例时,将ConcurrentHashMap
类型的methodCache 传了过去。
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
MapperProxy
实现了InvocationHandler
接口的invoke
方法,那么在使用Mapper
进行CRUD
时实际会调用对应的MapperProxy
的invoke
方法:
@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); return mapperMethod.execute(sqlSession, args); }
如上代码可以看到其先获取了MapperMethod 实例,然后调用了MapperMethod 实例的execute方法。
cachedMapperMethod方法
private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
代码解释如下:
① 尝试从ConcurrentHashMap类型的methodCache获取当前method对应的MapperMethod
② 如果①没有获取到,则新建MapperMethod实例
③ 将{method=mapperMethod}放入methodCache中
④ 返回MapperMethod 实例
这里先看一下一个Method对象是个什么?
实例化MapperMethod
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())
构造方法如下:
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); }
这里我们可以看到,其新建了SqlCommand
和MethodSignature
实例。
MapperMethod的UML图如下所示
再看一下MapperMethod实例对象
实例化SqlCommandmethod
SqlCommandmethod是MapperMethod静态嵌套类,主要属性是name和SqlCommandType。
public static class SqlCommand { private final String name; private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { String statementName = mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null; if (configuration.hasStatement(statementName)) { ms = configuration.getMappedStatement(statementName); } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35 String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName(); if (configuration.hasStatement(parentStatementName)) { ms = configuration.getMappedStatement(parentStatementName); } } if (ms == null) { if(method.getAnnotation(Flush.class) != null){ name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + statementName); } } else { name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } //... }
构造方法如下解释如下
① mapperInterface.getName() + "." + method.getName()解析获取到statementName ,如com.mybatis.dao.EmployeeMapper.getEmpById;
② 判断configuration实例的Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")成员对象中是否有①中的statementName;
③ 如果存在,则获取statementName对应的MappedStatement;
④ 如果不存在且当前接口Class不是方法的所属Class,则根据方法的所属Class的name与方法名字解析新的statementName
⑤ 如果configuration的Map<String, MappedStatement> mappedStatements成员中存在新的statementName,则返回对应的MappedStatement
⑥ 如果最终得到的MappedStatement为null,则判断方法上面是否有注解@Flush;
⑦ 如果有注解@Flush,则赋值name=null,type = SqlCommandType.FLUSH;
⑧ 如果没有注解@Flush,则抛出异常
⑨ 如果最终得到的MappedStatement不为null,则赋值 name = ms.getId(); type = ms.getSqlCommandType();。
⑩ 如果type为UNKNOWN,则抛出异常
SqlCommandType 是一个枚举类,主要有值 UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
public enum SqlCommandType { UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH; }
hasStatement(statementName)与getMappedStatement(statementName)
configuration.hasStatement(statementName)) ; configuration.getMappedStatement(statementName);
上面我们看到这样两句代码,代码表面本身很好理解,我们跟进去看:
hasStatement代码流程片段如下:
public boolean hasStatement(String statementName) { return hasStatement(statementName, true); } public boolean hasStatement(String statementName, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.containsKey(statementName); }
getMappedStatement代码流程片段如下:
public MappedStatement getMappedStatement(String id) { return this.getMappedStatement(id, true); } public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.get(id); }
可以看到,其都调用了 buildAllStatements();
。那么这个方法是什么呢?如下代码所示,其实就是mybatis提供的快速失败
机制
protected void buildAllStatements() { parsePendingResultMaps(); if (!incompleteCacheRefs.isEmpty()) { synchronized (incompleteCacheRefs) { incompleteCacheRefs.removeIf(x -> x.resolveCacheRef() != null); } } if (!incompleteStatements.isEmpty()) { synchronized (incompleteStatements) { incompleteStatements.removeIf(x -> { x.parseStatementNode(); return true; }); } } if (!incompleteMethods.isEmpty()) { synchronized (incompleteMethods) { incompleteMethods.removeIf(x -> { x.resolve(); return true; }); } } }
在该方法上面有如下注释:
Parses all the unprocessed statement nodes in the cache. It is recommended to call this method once all the mappers are added as it provides fail-fast statement validation.
解析缓存中所有未处理的statement节点。建议在添加所有映射程序后调用此方法,因为它提供fail fast
语句验证。
resolveCacheRef、parseStatementNode、resolve都会抛出异常
那么什么是fail fast 呢?
fail fast即快速失败。这是一种设计思想,即系统如果发现异常立即抛出异常结束任务。与快速失败对应的还有fail save
也就是安全失败,简单解释即为系统发现异常时,并不抛出异常结束程序,而是捕获异常通常并写入到错误日志中。