Mybatis Mapper 动态代理
Mybatis中使用了JDK反射机制和CGLIB代理
反射机制,通过 Class.forName 加载类,通过方法的 invoke 反射调用方法
JDK动态代理#
MyBatis的 Mapper 采用的就是 JDK 动态代理
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyService implements InvocationHandler { private Object target; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(proxy, args); } public Object bind(Object target){ this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }
CGLIB 动态代理#
JDK 动态代理缺陷:必须提供接口才可以使用
package net.sf.cglib.samples; import net.sf.cglib.proxy.*; public class CglibService implements MethodInterceptor { private Object target; public Object newInstance(Object target){ try{ this.target = target; Enhancer e = new Enhancer(); e.setSuperclass(this.target.getClass()); // 回调 e.setCallback(this); // 创建代理对象 return e.create(); }catch( Throwable e ){ e.printStackTrace(); throw new Error(e.getMessage()); } } public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }
构建 SqlSessionFactory 过程#
通过 SqlSesionFactoryBuilder 去构建
- 通过 org.apache.ibatis.builder.xml.XMLConfigBuilder 解析配置的 XML 文件,读出配置参数,将数据存入 org.apache.ibatis.session.Configuration 类
- 使用 Configuration 对象创建 SqlSessionFactory,这是一个接口,默认实现类是 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
构建 Configuration#
- 读入配置文件,包括基础配置的 XML 文件和映射器的 XML 文件
- 初始化基础配置和重要类对象,插件、映射器、ObjectFactory 和 typeHandler 对象
- 提供单例,为后续创建 SessionFactory 服务并提供配置的参数
- 执行重要的对象方法,初始化配置信息
映射器的内部组成#
MappedStatement,保存映射器的一个节点(select|insert|delete|update),包括SQL的id、缓存、resultMap、parameterType、resultType、languageDriver等
SqlSource,提供 BoundSql 对象的地方,是 MappedStatement 的一个属性
BoundSql,建立SQL和参数的地方,3个常用属性:SQL、parameterObject、paramteterMappings
BoundSql 会提供3个主要的属性#
- parameterMappings,这个是参数本身,可以传递简单对象、POJO、Map 或者 @Param 参数
- 传递简单对象(int、String、float、double),传递时将原始数据类型转换为包装数据类型
- 如果没有 @Param 注解,parameterObject 变为一个 Map<String, Object>对象,使用#{param1}或者#{1}去引用参数
- 如果使用 @Param 注解,parameterObject 变为一个 Map<String, Object>对象,使用指定的key获取值
- parameterMappings,是一个List,每一个元素都是 ParameterMapping 对象
映射器动态代理(代码 + 解析)#
MapperProxyFactory
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.apache.ibatis.binding; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.ibatis.binding.MapperProxy.MapperMethodInvoker; import org.apache.ibatis.session.SqlSession; public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap(); // 3.5.5版本的 mybatis, 对在配置 mapper-locations 下的 xml 格式的 mapper 都会依次生成对应的工厂 public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return this.mapperInterface; } public Map<Method, MapperMethodInvoker> getMethodCache() { return this.methodCache; } protected T newInstance(MapperProxy<T> mapperProxy) { return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); } // 当 xml 格式的 mapper 对应的 dao 在 service 中 @Autowired 或者 @Resource 调用该方法 public T newInstance(SqlSession sqlSession) { MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); } }
MapperProxy
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.apache.ibatis.binding; import java.io.Serializable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import org.apache.ibatis.reflection.ExceptionUtil; import org.apache.ibatis.session.SqlSession; public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -4724728412955527868L; private static final int ALLOWED_MODES = 15; private static final Constructor<Lookup> lookupConstructor; private static final Method privateLookupInMethod; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperProxy.MapperMethodInvoker> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperProxy.MapperMethodInvoker> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } // 当调用 dao 中的方法时,调用该方法判断是类还是接口, 如果是类直接执行方法, 如果是接口, 调用 cachedInvoker 执行接口中的方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } } // 这里的 method 就是调用的 dao 中的方法 private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { // 从 methodCache 中获取 invoker, 即第一次调用之后可以复用 MapperProxy.MapperMethodInvoker invoker = (MapperProxy.MapperMethodInvoker)this.methodCache.get(method); return invoker != null ? invoker : (MapperProxy.MapperMethodInvoker)this.methodCache.computeIfAbsent(method, (m) -> { // lamda 表达式, m 就是 method, 判断是否是 default 方法 if (m.isDefault()) { // 如果是 default 方法, 调用 DefaultMethodInvoker 方法 try { return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method)); } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) { throw new RuntimeException(var4); } } else { // 如果不是 default 方法,调用 PlainMethodInvoker 方法 return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration())); } }); } catch (RuntimeException var4) { Throwable cause = var4.getCause(); throw (Throwable)(cause == null ? var4 : cause); } } private MethodHandle getMethodHandleJava9(Method method) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class<?> declaringClass = method.getDeclaringClass(); return ((Lookup)privateLookupInMethod.invoke((Object)null, declaringClass, MethodHandles.lookup())).findSpecial(declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), declaringClass); } private MethodHandle getMethodHandleJava8(Method method) throws IllegalAccessException, InstantiationException, InvocationTargetException { Class<?> declaringClass = method.getDeclaringClass(); return ((Lookup)lookupConstructor.newInstance(declaringClass, 15)).unreflectSpecial(method, declaringClass); } static { Method privateLookupIn; try { privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, Lookup.class); } catch (NoSuchMethodException var5) { privateLookupIn = null; } privateLookupInMethod = privateLookupIn; Constructor<Lookup> lookup = null; if (privateLookupInMethod == null) { try { lookup = Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE); lookup.setAccessible(true); } catch (NoSuchMethodException var3) { throw new IllegalStateException("There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", var3); } catch (Exception var4) { lookup = null; } } lookupConstructor = lookup; } private static class DefaultMethodInvoker implements MapperProxy.MapperMethodInvoker { private final MethodHandle methodHandle; public DefaultMethodInvoker(MethodHandle methodHandle) { this.methodHandle = methodHandle; } public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return this.methodHandle.bindTo(proxy).invokeWithArguments(args); } } private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker { private final MapperMethod mapperMethod; public PlainMethodInvoker(MapperMethod mapperMethod) { this.mapperMethod = mapperMethod; } // 执行 dao 中的方法时, 调用 mapperMethod 的 execute 方法, 传入 sql会话 和 参数 public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return this.mapperMethod.execute(sqlSession, args); } } interface MapperMethodInvoker { Object invoke(Object var1, Method var2, Object[] var3, SqlSession var4) throws Throwable; } }
MapperMethod
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.apache.ibatis.binding; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import org.apache.ibatis.annotations.Flush; import org.apache.ibatis.annotations.MapKey; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ResultMap; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.StatementType; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.ParamNameResolver; import org.apache.ibatis.reflection.TypeParameterResolver; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.session.SqlSession; public class MapperMethod { private final MapperMethod.SqlCommand command; private final MapperMethod.MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new MapperMethod.SqlCommand(config, mapperInterface, method); this.method = new MapperMethod.MethodSignature(config, mapperInterface, method); } // 具体的 Sql 语句对应的执行过程在这里 public Object execute(SqlSession sqlSession, Object[] args) { Object result; Object param; // 这里的类型在第一次调用 dao 生成 MapperMethod 时已经注入, 后面都是复用 // 根据不同类型, SqlSession 调用不同的方法 switch(this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; // 插入、更新、删除都是返回影响的行数, 查询根据返回类型调用不同的查询方法 case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } } private Object rowCountResult(int rowCount) { Object result; if (this.method.returnsVoid()) { result = null; } else if (!Integer.class.equals(this.method.getReturnType()) && !Integer.TYPE.equals(this.method.getReturnType())) { if (!Long.class.equals(this.method.getReturnType()) && !Long.TYPE.equals(this.method.getReturnType())) { if (!Boolean.class.equals(this.method.getReturnType()) && !Boolean.TYPE.equals(this.method.getReturnType())) { throw new BindingException("Mapper method '" + this.command.getName() + "' has an unsupported return type: " + this.method.getReturnType()); } result = rowCount > 0; } else { result = (long)rowCount; } } else { result = rowCount; } return result; } private void executeWithResultHandler(SqlSession sqlSession, Object[] args) { MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(this.command.getName()); if (!StatementType.CALLABLE.equals(ms.getStatementType()) && Void.TYPE.equals(((ResultMap)ms.getResultMaps().get(0)).getType())) { throw new BindingException("method " + this.command.getName() + " needs either a @ResultMap annotation, a @ResultType annotation, or a resultType attribute in XML so a ResultHandler can be used as a parameter."); } else { Object param = this.method.convertArgsToSqlCommandParam(args); if (this.method.hasRowBounds()) { RowBounds rowBounds = this.method.extractRowBounds(args); sqlSession.select(this.command.getName(), param, rowBounds, this.method.extractResultHandler(args)); } else { sqlSession.select(this.command.getName(), param, this.method.extractResultHandler(args)); } } } private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { Object param = this.method.convertArgsToSqlCommandParam(args); List result; if (this.method.hasRowBounds()) { RowBounds rowBounds = this.method.extractRowBounds(args); result = sqlSession.selectList(this.command.getName(), param, rowBounds); } else { result = sqlSession.selectList(this.command.getName(), param); } if (!this.method.getReturnType().isAssignableFrom(result.getClass())) { return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result); } else { return result; } } private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) { Object param = this.method.convertArgsToSqlCommandParam(args); Cursor result; if (this.method.hasRowBounds()) { RowBounds rowBounds = this.method.extractRowBounds(args); result = sqlSession.selectCursor(this.command.getName(), param, rowBounds); } else { result = sqlSession.selectCursor(this.command.getName(), param); } return result; } private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) { Object collection = config.getObjectFactory().create(this.method.getReturnType()); MetaObject metaObject = config.newMetaObject(collection); metaObject.addAll(list); return collection; } private <E> Object convertToArray(List<E> list) { Class<?> arrayComponentType = this.method.getReturnType().getComponentType(); Object array = Array.newInstance(arrayComponentType, list.size()); if (!arrayComponentType.isPrimitive()) { return list.toArray((Object[])((Object[])array)); } else { for(int i = 0; i < list.size(); ++i) { Array.set(array, i, list.get(i)); } return array; } } private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) { Object param = this.method.convertArgsToSqlCommandParam(args); Map result; if (this.method.hasRowBounds()) { RowBounds rowBounds = this.method.extractRowBounds(args); result = sqlSession.selectMap(this.command.getName(), param, this.method.getMapKey(), rowBounds); } else { result = sqlSession.selectMap(this.command.getName(), param, this.method.getMapKey()); } return result; } 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; 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.TYPE.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 = this.getMapKey(method); this.returnsMap = this.mapKey != null; this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class); this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class); this.paramNameResolver = new ParamNameResolver(configuration, method); } public Object convertArgsToSqlCommandParam(Object[] args) { return this.paramNameResolver.getNamedParams(args); } public boolean hasRowBounds() { return this.rowBoundsIndex != null; } public RowBounds extractRowBounds(Object[] args) { return this.hasRowBounds() ? (RowBounds)args[this.rowBoundsIndex] : null; } public boolean hasResultHandler() { return this.resultHandlerIndex != null; } public ResultHandler extractResultHandler(Object[] args) { return this.hasResultHandler() ? (ResultHandler)args[this.resultHandlerIndex] : null; } public Class<?> getReturnType() { return this.returnType; } public boolean returnsMany() { return this.returnsMany; } public boolean returnsMap() { return this.returnsMap; } public boolean returnsVoid() { return this.returnsVoid; } public boolean returnsCursor() { return this.returnsCursor; } public boolean returnsOptional() { return this.returnsOptional; } private Integer getUniqueParamIndex(Method method, Class<?> paramType) { Integer index = null; Class<?>[] argTypes = method.getParameterTypes(); for(int i = 0; i < argTypes.length; ++i) { if (paramType.isAssignableFrom(argTypes[i])) { if (index != null) { throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters"); } index = i; } } return index; } public String getMapKey() { return this.mapKey; } private String getMapKey(Method method) { String mapKey = null; if (Map.class.isAssignableFrom(method.getReturnType())) { MapKey mapKeyAnnotation = (MapKey)method.getAnnotation(MapKey.class); if (mapKeyAnnotation != null) { mapKey = mapKeyAnnotation.value(); } } return mapKey; } } public static class SqlCommand { private final String name; private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { String methodName = method.getName(); Class<?> declaringClass = method.getDeclaringClass(); MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { if (method.getAnnotation(Flush.class) == null) { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } this.name = null; this.type = SqlCommandType.FLUSH; } else { this.name = ms.getId(); this.type = ms.getSqlCommandType(); if (this.type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + this.name); } } } public String getName() { return this.name; } public SqlCommandType getType() { return this.type; } private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) { String statementId = mapperInterface.getName() + "." + methodName; if (configuration.hasStatement(statementId)) { return configuration.getMappedStatement(statementId); } else if (mapperInterface.equals(declaringClass)) { return null; } else { Class[] var6 = mapperInterface.getInterfaces(); int var7 = var6.length; for(int var8 = 0; var8 < var7; ++var8) { Class<?> superInterface = var6[var8]; if (declaringClass.isAssignableFrom(superInterface)) { MappedStatement ms = this.resolveMappedStatement(superInterface, methodName, declaringClass, configuration); if (ms != null) { return ms; } } } return null; } } } public static class ParamMap<V> extends HashMap<String, V> { private static final long serialVersionUID = -2212268410512043556L; public ParamMap() { } public V get(Object key) { if (!super.containsKey(key)) { throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet()); } else { return super.get(key); } } } }
作者:BigBender
出处:https://www.cnblogs.com/BigBender/p/15633867.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!