1 前言
上节我们回顾了下 Mapper 接口的解析存放以及代理的入口和创建代理的过程,那么这节我们就来看下 MapperProxy 的代理执行逻辑。
2 源码分析
2.1 invoke 代理逻辑
Mapper 接口方法的代理逻辑首先会对拦截的方法进行一些过滤,以决定是否执行后续的操作。对应的代码如下:
/** * MapperProxy */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 如果方法是定义在 Object 类中的,则直接调用 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { /** * 从缓存中获取 MapperMethodInvoker 对象,若缓存未命中,则创建 MapperMethodInvoker 对象 * 然后执行 */ return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
那么我们继续看下 MapperMethodInvoker 的创建过程。
2.2 MapperMethodInvoker 的创建
首先会从缓存中取,注意一下这里的缓存是从 MapperProxyFactory里传过来的,也就是缓存是定义在 MapperProxyFactory 里的,key是方法,value是方法的执行器,我们看下创建的源码:
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { // 先从缓存中取,有的话直接返回 MapperMethodInvoker invoker = methodCache.get(method); if (invoker != null) { return invoker; } return methodCache.computeIfAbsent(method, m -> { // 就是判断我们当前接口中的方法是不是 default的 if (m.isDefault()) { try { if (privateLookupInMethod == null) { return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } else { /** * 首先会创建 MapperMethod对象 * 再创建 PlainMethodInvoker 执行器 */ return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } }
抛开 defult 的方法,我们的接口方法都是会先创建 MapperMethod 方法,然后作为参数创建的 PlainMethodInvoker 执行器,那么接下里我们看看 MapperMethod 的创建过程。
3 MapperMethod 对象的创建
MapperMethod里边有两个属性 SqlCommand 、MethodSignature,创建 MapperMethod 对象的构造方法里也就是创建这两个对象的值,我们看下源码:
/** * MapperMethod * @param mapperInterface 我们的 Mapper 接口 * @param method 当前执行的方法 * @param config SqlSession 里的 Configuration 对象 */ public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); }
那么我们就来看下创建 SqlCommand 的过程。
3.1 SqlCommand 的创建
我们来看下 SqlCommand的创建:
public static class SqlCommand { private final String name; private final SqlCommandType type; /** * @param configuration SqlSession 中的 configuration * @param mapperInterface 我们的 mapper 接口全类名 * @param method 当前的方法 */ public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { // 获取到方法的名字 final String methodName = method.getName(); // 方法的来源类 final Class<?> declaringClass = method.getDeclaringClass(); // 解析 MappedStatement对象,我们在解析的时候讲过 我们的增删改查语句最后都会被封装进 MappedStatement 对象中 MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { // 看看方法上边有没有 Flush 注解 if (method.getAnnotation(Flush.class) != null) { // 设置上名称和类型 name = null; type = SqlCommandType.FLUSH; } else { // 找不到你的 MappedStatement 并且方法上边没有 Flush 注解就报找不到 throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { // 设置上名称和类型 name = ms.getId(); type = ms.getSqlCommandType(); // 不知道你语句是什么类型的情况下报错,也就是不知道你要干什么 if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } }
可以看到 SqlCommand 就是检查你的 MappedStatement 对象是否存在以及是增删改查还是Flush,做一些基本的检查工作哈。
3.2 MethodSignature 的创建
MethodSignature 即方法签名,顾名思义,该类保存了一些和目标方法相关的信息。比如 目标方法的返回类型,目标方法的参数列表信息等。下面,我们来分析一下 MethodSignature 的构造方法。
public static class MethodSignature { private final boolean returnsMany; private final boolean returnsMap; private final boolean returnsVoid; private final boolean returnsCursor; private final boolean returnsOptional; private final Class<?> returnType; private final String mapKey; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver; /** * @param configuration SqlSession 中的 configuration * @param mapperInterface 我们的 mapper 接口全类名 * @param method 当前的方法 */ public MethodSignature(Configuration configuration, Class<?> mapperInterface, Me // 解析方法的返回类型 Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapp // 返回类型是实体类 比如 OrderPo、Map不带泛型的 if (resolvedReturnType instanceof Class<?>) { this.returnType = (Class<?>) resolvedReturnType; // 返回类型是参数化类型 例如List、String这种不是参数化类型,凡是这种带有泛型的类型如Collection<String>、Map<String } else if (resolvedReturnType instanceof ParameterizedType) { this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRaw } else { this.returnType = method.getReturnType(); } // 是不返回 void this.returnsVoid = void.class.equals(this.returnType); // 是不是返回多个 this.returnsMany = configuration.getObjectFactory().isCollection(this.returnTy // 游标的 this.returnsCursor = Cursor.class.equals(this.returnType); // 返回 Optional 的 this.returnsOptional = Optional.class.equals(this.returnType); // 解析 @MapKey 注解,获取注解内容 this.mapKey = getMapKey(method); this.returnsMap = this.mapKey != null; // RowBounds 分页参数 获取分页参数 RowBounds 在参数中的索引位置 发现有多个分页参数的话会报错 this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); // 获取 ResultHandler 在参数中的索引位置 也是一样有多个的话会报错 this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); // 解析我们的参数 this.paramNameResolver = new ParamNameResolver(configuration, method); } }
上面的代码用于检测目标方法的返回类型,以及解析目标方法参数列表。其中,检测返 回类型的目的是为避免查询方法返回错误的类型。比如我们要求接口方法返回一个对象,结 果却返回了对象集合,这会导致类型转换错误。关于返回值类型的解析过程先说到这,下面 分析参数列表的解析过程。
/** * @param config SqlSession 中的 Configuration * @param method 当前执行的方法 */ public ParamNameResolver(Configuration config, Method method) { /** * 是否使用真实的名字 * 默认是true 因为 config 中默认 useActualParamName = true */ this.useActualParamName = config.isUseActualParamName(); // 获取方法的参数列表 final Class<?>[] paramTypes = method.getParameterTypes(); // 获取方法的注解 final Annotation[][] paramAnnotations = method.getParameterAnnotations(); // 集合 final SortedMap<Integer, String> map = new TreeMap<>(); int paramCount = paramAnnotations.length; // get names from @Param annotations // 遍历参数列表 for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { /** * 发现是 RowBounds 或者 ResultHandler 就跳过 * RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz); */ if (isSpecialParameter(paramTypes[paramIndex])) { continue; } String name = null; // 获取参数的注解 for (Annotation annotation : paramAnnotations[paramIndex]) { // 当发现参数有 @Param 注解的话,取注解中的值为名字 if (annotation instanceof Param) { hasParamAnnotation = true; name = ((Param) annotation).value(); break; } } /** * 如果没有@Param注解或者注解上的值是空的话,并且开启了 useActualParamName 会去取你参数的名字了 * 这也是为什么我们有的时候不需要写@Param的原因 */ if (name == null) { if (useActualParamName) { /** * 比如 int getOne(String orderNo) 那么名字就是 orderNo * 此种方式要求 JDK 版本为 1.8+,且要求编译时加入 -parameters 参数,否则获取到的参数名仍然是 arg1, arg2, ..., argN */ name = getActualParamName(method, paramIndex); } // 名字还是空的话 直接用0 1 2 3 ... 表示了 if (name == null) { // use the parameter index as the name ("0", "1", ...) /** * 注意这里是 map.size() 不是 paramIndex * 因为如果参数列表中包含 RowBounds 或 ResultHandler,这两个参数会被忽略掉,这样将导致名称不连续。 * 比如参数列表 (int p1, int p2, RowBounds rb, int p3) * - 期望得到名称列表为 ["0", "1", "2"] * - 实际得到名称列表为 ["0", "1", "3"] */ name = String.valueOf(map.size()); } } // 放进集合 map.put(paramIndex, name); } names = Collections.unmodifiableSortedMap(map); }
4 MapperMethod 执行前奏
我们的 MapperMethod 外边包了一层 PlainMethodInvoker,invoke 其实就是执行我们的 MapperMethod 的execute:
// PlainMethodInvoker invoke cachedInvoker(method).invoke(proxy, method, args, sqlSession); // PlainMethodInvoker public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { /** * 执行 mapperMethod 的 execute * sqlSession 当前的会话对象 * args 方法参数 */ return mapperMethod.execute(sqlSession, args); }
我们来具体看下 MapperMethod 的execute:
/** * MapperMethod * @param mapperInterface 我们的 Mapper 接口 * @param method 当前执行的方法 * @param config SqlSession 里的 Configuration 对象 */ public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 根据语句类型 case switch (command.getType()) { case INSERT: { // 对传入的参数进行转换以及我们解析方法的参数名进行匹配 Object param = method.convertArgsToSqlCommandParam(args); // 执行插入操作,rowCountResult 方法用于处理返回 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()) { // 方法没返回值,但是有ResultHandler 说明通过ResultHandler 来获取查询结果 executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { // 执行查询操作,并返回多个结果 result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { // 执行查询操作,并将结果封装在 Map 中返回 result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { // 执行查询操作,并返回一个 Cursor 对象 result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); // 执行查询操作,并返回一个结果 result = sqlSession.selectOne(command.getName(), param); // 如果返回类型是Optional的,用Optional包装一下 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; }
整体的一个执行过程,就是根据 SqlCommand 的类型,进行不同的操作,我们这里先看下参数的匹配:
// MapperMethod public Object convertArgsToSqlCommandParam(Object[] args) { // 调用我们创建的MapperMethod时创建的参数解析器进行匹配 return paramNameResolver.getNamedParams(args); }
/** * @param args 方法的实际传参 * @return */ public Object getNamedParams(Object[] args) { // 参数的个数 final int paramCount = names.size(); // 如果传参为空或者参数长度为0就直接返回 if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { /** * 如果只有一个参数,并且不含@Param注解 * 并且 value 是 Collection List 或者 Array 的话 * 转换为ParamMap key就是我们平时forEach常见的 collection list array */ Object value = args[names.firstKey()]; return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null); } else { final Map<String, Object> param = new ParamMap<>(); int i = 0; for (Map.Entry<Integer, String> entry : names.entrySet()) { // 添加 <参数名, 参数值> 键值对到 param 中 param.put(entry.getValue(), args[entry.getKey()]); // 比如 param1, param2,... paramN final String genericParamName = GENERIC_NAME_PREFIX + (i + 1); // 检测 names 中是否包含 genericParamName,不包含的话塞值 // 包含的情况就是当我们显式将参数名称配置为 param1,即 @Param("param1") if (!names.containsValue(genericParamName)) { // 添加 <param*, value> 到 param 中 param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } }
convertArgsToSqlCommandParam 该方法最终调用了 ParamNameResolver 的 getNamedParams 方法。
5 小结
这节我们大概看了下代理执行的前奏准备工作,下节我们针对不同的 SqlCommand进行逐一的分析,有理解不对的地方欢迎指正哈。
标签:Mapper,Object,args,param,final,源码,参数,Mybatis,method From: https://www.cnblogs.com/kukuxjx/p/17182479.html