AI智能
改变未来

mybaits源码分析–binding模块(五)


一、binding模块

接下来我们看看在org.apache.ibatis.binding包下提供的Binding模块 ,binding其实在执行sqlSession.getMapper(UserMapper.class);获取接口代理的对象时有用到;

发现这个包里面提供的工具比较少,就几个,先来分别了解下他们的作用,然后在串联起来。

1.1 MapperRegistry

这显然是一个注册中心,这个注册中是用来保存MapperProxyFactory对象的,所以这个注册器中提供的功能肯定是围绕MapperProxyFactory的添加和获取操作,来看看具体的代码逻辑
成员变量:

private final Configuration config;// 记录 Mapper 接口和 MapperProxyFactory 之间的关系private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

addMapper方法

public <T> void addMapper(Class<T> type) {if (type.isInterface()) { // 检测 type 是否为接口if (hasMapper(type)) { // 检测是否已经加装过该接口throw new BindingException(\"Type \" + type + \" is already known to the MapperRegistry.\");}boolean loadCompleted = false;try {// !Map<Class<?>, 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.// 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}

getMapper方法

/*** 获取Mapper接口对应的代理对象*/public <T> T getMapper(Class<T> type, SqlSession sqlSession) {// 获取Mapper接口对应的 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 {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException(\"Error getting mapper instance. Cause: \" + e, e);}}

通过这个方法本质上获取的就是Mapper接口的代理对象。

1.2 MapperProxyFactory

MapperProxyFactory是一个工厂对象,专门负责创建MapperProxy对象。其中核心字段的含义和功能如下:

/*** 负责创建 MapperProxy 对象* @author Lasse Voss*/public class MapperProxyFactory<T> {/*** MapperProxyFactory 可以创建 mapperInterface 接口的代理对象*     创建的代理对象要实现的接口*/private final Class<T> mapperInterface;// 缓存private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();public MapperProxyFactory(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}public Class<T> getMapperInterface() {return mapperInterface;}public Map<Method, MapperMethodInvoker> getMethodCache() {return methodCache;}@SuppressWarnings(\"unchecked\")/*** 创建实现了 mapperInterface 接口的代理对象*/protected T newInstance(MapperProxy<T> mapperProxy) {// 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}}

1.3 MapperProxy

通过MapperProxyFactory创建的MapperProxy是Mapper接口的代理对象,实现了InvocationHandler接口,通过前面讲解的动态代理模式,那么这部分的内容就很简单了。

/*** Mapper 代理对象* @author* @author*/public class MapperProxy<T> implements InvocationHandler, Serializable {private static final long serialVersionUID = -4724728412955527868L;private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;private static final Constructor<Lookup> lookupConstructor;private static final Method privateLookupInMethod;private final SqlSession sqlSession; // 记录关联的 SqlSession对象private final Class<T> mapperInterface; // Mapper接口对应的Class对象// 用于缓存MapperMethod对象,key是Mapper接口方法对应的Method对象,value是对应的MapperMethod对象。‘// MapperMethod对象会完成参数转换以及SQL语句的执行// 注意:MapperMethod中并不会记录任何状态信息,可以在多线程间共享private final Map<Method, MapperMethodInvoker> methodCache;public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}static {Method privateLookupIn;try {privateLookupIn = MethodHandles.class.getMethod(\"privateLookupIn\", Class.class, MethodHandles.Lookup.class);} catch (NoSuchMethodException e) {privateLookupIn = null;}privateLookupInMethod = privateLookupIn;Constructor<Lookup> lookup = null;if (privateLookupInMethod == null) {// JDK 1.8try {lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);lookup.setAccessible(true);} catch (NoSuchMethodException e) {throw new IllegalStateException(\"There is neither \'privateLookupIn(Class, Lookup)\' nor \'Lookup(Class, int)\' method in java.lang.invoke.MethodHandles.\",e);} catch (Exception e) {lookup = null;}}lookupConstructor = lookup;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// toString hashCode equals getClass等方法,无需走到执行SQL的流程if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else {// 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke// 普通方法会走到 PlainMethodInvoker(内部类) 的 invokereturn cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {try {// Java8 中 Map 的方法,根据 key 获取值,如果值是 null,则把后面Object 的值赋给 key// 如果获取不到,就创建// 获取的是 MapperMethodInvoker(接口) 对象,只有一个invoke方法// 根据method 去methodCache中获取 如果返回空 则用第二个参数填充return methodCache.computeIfAbsent(method, m -> {if (m.isDefault()) {// 接口的默认方法(Java8),只要实现接口都会继承接口的默认方法,例如 List.sort()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 {// 创建了一个 MapperMethodreturn new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}});} catch (RuntimeException re) {Throwable cause = re.getCause();throw cause == null ? re : cause;}}private MethodHandle getMethodHandleJava9(Method method)throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {final Class<?> declaringClass = method.getDeclaringClass();return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),declaringClass);}private MethodHandle getMethodHandleJava8(Method method)throws IllegalAccessException, InstantiationException, InvocationTargetException {final Class<?> declaringClass = method.getDeclaringClass();return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);}interface MapperMethodInvoker {Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;}private static class PlainMethodInvoker implements MapperMethodInvoker {private final MapperMethod mapperMethod;public PlainMethodInvoker(MapperMethod mapperMethod) {super();this.mapperMethod = mapperMethod;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {// SQL执行的真正起点return mapperMethod.execute(sqlSession, args);}}private static class DefaultMethodInvoker implements MapperMethodInvoker {private final MethodHandle methodHandle;public DefaultMethodInvoker(MethodHandle methodHandle) {super();this.methodHandle = methodHandle;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return methodHandle.bindTo(proxy).invokeWithArguments(args);}}}

1.4 MapperMethod

MapperMethod中封装了Mapper接口中对应方法的信息,以及SQL语句的信息,可以把MapperMethod看成是配置文件中定义的SQL语句和Mapper接口的桥梁。

属性和构造方法

// statement id (例如:com.gupaoedu.mapper.BlogMapper.selectBlogById) 和 SQL 类型private final SqlCommand command;// 方法签名,主要是返回值的类型private final MethodSignature method;public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {this.command = new SqlCommand(config, mapperInterface, method);this.method = new MethodSignature(config, mapperInterface, method);}

1.4.1 SqlCommand

SqlCommand是MapperMethod中定义的内部类,记录了SQL语句名称以及对应的类型(UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH)

1.4.2 MethodSignature

MethodSignature也是MapperMethod的内部类,在其中封装了Mapper接口中定义的方法相关信息。

private final boolean returnsMany; // 判断返回是否为 Collection类型或者数组类型private final boolean returnsMap; // 返回值是否为 Map类型private final boolean returnsVoid; // 返回值类型是否为 voidprivate final boolean returnsCursor; // 返回值类型是否为 Cursor 类型private final boolean returnsOptional; // 返回值类型是否为 Optional 类型private final Class<?> returnType; // 返回值类型private final String mapKey; // 如果返回值类型为 Map  则 mapKey 记录了作为 key的 列名private final Integer resultHandlerIndex; // 用来标记该方法参数列表中 ResultHandler 类型参数的位置private final Integer rowBoundsIndex; // 用来标记该方法参数列表中 rowBounds 类型参数的位置private final ParamNameResolver paramNameResolver; // 该方法对应的 ParamNameResolver 对象

构造方法中完成了相关信息分初始化操作

/*** 方法签名* @param configuration* @param mapperInterface* @param method*/public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {// 获取接口方法的返回类型Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);if (resolvedReturnType instanceof Class<?>) {this.returnType = (Class<?>) resolvedReturnType;} else if (resolvedReturnType instanceof ParameterizedType) {this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();} else {this.returnType = method.getReturnType();}this.returnsVoid = void.class.equals(this.returnType);this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();this.returnsCursor = Cursor.class.equals(this.returnType);this.returnsOptional = Optional.class.equals(this.returnType);this.mapKey = getMapKey(method);this.returnsMap = this.mapKey != null;// getUniqueParamIndex 查找指定类型的参数在 参数列表中的位置this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);this.paramNameResolver = new ParamNameResolver(configuration, method);}

getUniqueParamIndex的主要作用是 查找指定类型的参数在参数列表中的位置

/*** 查找指定类型的参数在参数列表中的位置* @param method* @param paramType* @return*/private Integer getUniqueParamIndex(Method method, Class<?> paramType) {Integer index = null;// 获取对应方法的参数列表final Class<?>[] argTypes = method.getParameterTypes();// 遍历for (int i = 0; i < argTypes.length; i++) {// 判断是否是需要查找的类型if (paramType.isAssignableFrom(argTypes[i])) {// 记录对应类型在参数列表中的位置if (index == null) {index = i;} else {// RowBounds 和 ResultHandler 类型的参数只能有一个,不能重复出现throw new BindingException(method.getName() + \" cannot have multiple \" + paramType.getSimpleName() + \" parameters\");}}}return index;}

1.4.3 execute方法

最后来看下再MapperMethod中最核心的方法execute方法,这个方法完成了数据库操作

public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) { // 根据SQL语句的类型调用SqlSession对应的方法case INSERT: {// 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来Object param = method.convertArgsToSqlCommandParam(args);// sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法// rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换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()) {// 返回值为空 且 ResultSet通过 ResultHandler处理的方法executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {// 返回值为 单一对象的方法Object param = method.convertArgsToSqlCommandParam(args);// 普通 select 语句的执行入口 >>result = sqlSession.selectOne(command.getName(), param);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;}

在这个方法中所对应一些分支方法都还是比较简单的,看一下其实就懂,我就不想再往里跟了

1.4.4 核心流程串联

首先在映射文件加载解析的位置,XMLMapperBuilder.parse位置

public void parse() {// 总体上做了两件事情,对于语句的注册和接口的注册if (!configuration.isResourceLoaded(resource)) {// 1、具体增删改查标签的解析。// 一个标签一个MappedStatement。 >>configurationElement(parser.evalNode(\"/mapper\"));configuration.addLoadedResource(resource);// 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。// 一个namespace 一个 MapperProxyFactory >>bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}

在bindMapperForNamespace中会完成Mapper接口的注册并调用前面介绍过的addMapper方法然后就是在我们执行

// 4.通过SqlSession中提供的 API方法来操作数据库UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> list = mapper.selectUserList();

这两行代码的内部逻辑,首先看下getMapper方法

public <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {// mapperRegistry中注册的有Mapper的相关信息 在解析映射文件时 调用过addMapper方法return mapperRegistry.getMapper(type, sqlSession);}

然后就是从MapperRegistry中获取对应的MapperProxyFactory对象。

/*** 获取Mapper接口对应的代理对象*/public <T> T getMapper(Class<T> type, SqlSession sqlSession) {// 获取Mapper接口对应的 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 {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException(\"Error getting mapper instance. Cause: \" + e, e);}}

然后根据MapperProxyFactory对象获取Mapper接口对应的代理对象。

/*** 创建实现了 mapperInterface 接口的代理对象*/protected T newInstance(MapperProxy<T> mapperProxy) {// 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}

不清楚可以看前面画的时序图

然后我们再来看下调用代理对象中的方法执行的顺序

List<User> list = mapper.selectUserList();

会进入MapperProxy的Invoker方法中

@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {// toString hashCode equals getClass等方法,无需走到执行SQL的流程if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else {// 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke// 普通方法会走到 PlainMethodInvoker(内部类) 的 invokereturn cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}

然后进入PlainMethodInvoker中的invoke方法

@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {// SQL执行的真正起点return mapperMethod.execute(sqlSession, args);}}

然后会进入到 MapperMethod的execute方法中

public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) { // 根据SQL语句的类型调用SqlSession对应的方法case INSERT: {// 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来Object param = method.convertArgsToSqlCommandParam(args);// sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法// rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换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()) {// 返回值为空 且 ResultSet通过 ResultHandler处理的方法executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {// 返回值为 单一对象的方法Object param = method.convertArgsToSqlCommandParam(args);// 普通 select 语句的执行入口 >>result = sqlSession.selectOne(command.getName(), param);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;}

之后就会根据对应的SQL类型而调用SqlSession中对应的方法来执行操作

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » mybaits源码分析–binding模块(五)