MyBatis 参数处理 - 接口代理机制源码分析 本章最后一节 @Param 源码分析
中,老杜简单提了下 JDK 的动态代理,以及代理模式中的一些概念,如代理对象,代理方法,目标对象,目标方法,还提到了,代理方法可以在目标方法的基础上添加一些代码,代理方法最终还是要调用目标对象的目标方法。
在学习 Java 基础时,并没有学习到 JDK 动态代理相关的内容,这里就把课给补上。
另外,老杜在追 @Param
的源码时,也涉及到了 MyBatis 接口代理机制一些相关源码的细节,因此在研究完 JDK 动态代理之后,再回过头来看看 MyBatis 的接口动态代理的实现细节,追一遍 MyBatis 接口代理的源码。
静态代理和动态代理 MyBatis 接口代理机制底层是使用 Java 动态代理技术实现的,建议先了解 Java 动态代理相关知识,可以阅读:
代理机制/代理模式下,不论是静态代理还是动态代理,都存在这些概念:代理对象,代理方法,目标对象,目标方法。下面先以静态代理为例,说明这些概念的含义。
JDK 动态代理只能基于接口,静态代理也大都是基于接口的,比如多线程中的 Runnable
接口,实现 Runnable
接口的实现类,还是需要传递给 Thread
类代理执行。
假设存在如下接口 Calculator
。
1 2 3 4 public interface Calculator { int add (int a, int b) ; int subtract (int a, int b) ; }
该接口存在实现类 CalculatorImpl
:
1 2 3 4 5 6 7 8 9 10 11 12 public class CalculatorImpl implements Calculator { @Override public int add (int a, int b) { return a + b; } @Override public int subtract (int a, int b) { return a - b; } }
如果是静态代理的话,需要静态代理类,实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class StaticCalculatorProxy implements Calculator { private Calculator target; public StaticCalculatorProxy (Calculator target) { this .target = target; } @Override public int add (int a, int b) { System.out.println("目标方法的前置增强代码" ); int result = target.add(a, b); System.out.println("目标方法的后置增强代码" ); return result; } @Override public int subtract (int a, int b) { System.out.println("目标方法的前置增强代码" ); int result = target.subtract(a, b); System.out.println("目标方法的后置增强代码" ); return result; } }
静态代理的使用如下:
1 2 3 4 5 6 7 public class StaticProxyTest { public static void main (String[] args) { Calculator cImpl = new CalculatorImpl (); Calculator cProxy = new StaticCalculatorProxy (cImpl); int result = cProxy.add(1 , 2 ); } }
CalculatorImpl
就是目标类/实现类,其对象实例 cImpl
就是目标对象。StaticCalculatorProxy
就是(静态)代理类,其对象实例 cProxy
就是代理对象,构建代理对象实例 cProxy
时,需要传入相应的目标对象 cImpl
。cProxy.add(1, 2)
就是代理对象 cProxy
调用代理方法 add
,代理方法中除了代理增强的部分,还会调用目标对象(cProxy
中持有 cImpl
)的目标方法 add
,来完成目标实现的部分。
代理类和目标类/实现类,都需要实现同一接口,确保方法定义统一,便于代理实现。
接口中的方法,在代理类中的实现称为代理方法,在目标类/实现类中的实现称为目标方法。
代理方法是对目标方法的增强,因此代理方法中一般包含两部分:代理增强的部分 + 目标实现的部分。
为了完成代理方法中目标实现的部分,代理对象中,需要持有相应的目标对象。
静态代理需要为每个目标类/实现类,都创建相应的一个静态代理类。
如果需要对大量的目标类/实现类作代理增强,静态代理下就需要创建一大批静态代理类,而代理增强的代码又往往是较为通用的,此时使用静态代理就显得比较繁琐重复了。
可以考虑使用动态代理,我们无需声明式的创建代理类,而是在运行时在内存中直接生成代理。
如果使用动态代理的话,其核心是定义好代理方法的调用处理,即定义好一个调用处理器。一般来说,不论调用的是哪个代理对象的哪个代理方法,代理方法无非都是:执行一些通用的代理增强,完成目标方法的调用,这套固定的调用处理流程我们定义一次就好。通过实现 InvocationHandler
完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class GenericProxy implements InvocationHandler { private Object target; public GenericProxy (Object target) { this .target = target; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName() + "方法开始执行..." ); Object result = method.invoke(target, args); System.out.println(result); System.out.println(method.getName() + "方法执行结束..." ); return result; } }
动态代理的使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class DynamicProxyTest { public static void main (String[] args) { Calculator cImpl = new CalculatorImpl (); Calculator cProxy = (Calculator) Proxy.newProxyInstance( cImpl.getClass().getClassLoader(), cImpl.getClass().getInterfaces(), new GenericProxy (cImpl) ); int result = cProxy.add(1 , 2 ); A aImpl = new AImpl (); A aProxy = (A) Proxy.newProxyInstance( aImpl.getClass().getClassLoader(), aImpl.getClass().getInterfaces(), new GenericProxy (aImpl) ); aProxy.doSomething(); } }
可以看到,CalculatorImpl
和 AImpl
两个实现类/目标类,在为其生成代理类时,它们都共用一个调用处理器 GenericProxy
!
补充说明,以下是调用处理器以匿名内部类的方式实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class DynamicProxyTest { public static void main (String[] args) { Calculator cImpl = new CalculatorImpl (); Calculator cProxy = (Calculator) Proxy.newProxyInstance( cImpl.getClass().getClassLoader(), cImpl.getClass().getInterfaces(), new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName() + "方法开始执行..." ); Object result = method.invoke(cImpl, args); System.out.println(result); System.out.println(method.getName() + "方法执行结束..." ); return result; } } ); int result = cProxy.add(1 , 2 ); } }
在匿名内部类的 invoke
方法中直接使用局部变量 cImpl
,这样构建出的匿名内部类中将包含一个 cImpl
属性,也即 cProxy
中持有匿名内部类方式实现的调用处理器 InvocationHandler
,调用处理器中又持有一个目标对象 cImpl
。
MyBatis 接口代理机制的目标类/实现类 回到 MyBatis 的接口代理机制,我们之前说了,它底层也是使用 JDK 动态代理实现的。
上面的示例中无论是静态代理还是动态代理,每个代理对象中都持有一个目标对象,也即每个代理类都有对应的目标类/实现类。
而在 MyBatis 中,我们只需要定义接口如 AccountMapper
,却无需定义目标类/实现类,这是为什么呢?
其实可以认为,MyBatis 中的 DefaultSqlSession
类,就是所谓的目标类/实现类。因为 MyBatis 已经帮我们定义好了,所以无需我们额外定义。看下面的两行代码:
1 2 AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);Account fromAct = mapper.selectByActno(fromActno);
mapper
指向动态生成的代理对象,selectByActno()
则是代理方法,该代理方法要完成的工作是:
根据 AccountMapper
接口的全类名和代理方法名 selectByActno
,确定要执行的 SQL 语句以及该 SQL 语句的类型。
根据 AccountMapper
接口的类字节码信息,获取 selectByActno
方法的方法签名,尤其是其中的返回值类型。
根据代理方法 selectByActno
所关联的 SQL 语句的类型(SELECT
)和方法签名(返回值是 List<Account>
),选择执行 sqlSession.selectList()
然后将结果返回。
显然,对于其他代理方法来说,其要完成的工作也基本固定如上,其实这就可以抽象出调用处理器 InvocationHandler
的实现逻辑:
代理方法 selectByActno()
其实主要目的是完成查询操作,因此可以认为 sqlSession.selectList()
就是代理方法中的目标实现调用部分,即通过目标对象 sqlSession
调用目标方法 selectList()
。
其他工作,比如确定要执行的 SQL 语句和 SQL 语句类型,确定方法签名,根据 SQL 语句类型和方法签名动态选择执行 sqlSession
的不同方法,都可以认为是代理方法中的代理增强部分。
另外,使用 JDK 动态代理生成代理对象时,除了调用处理器以外,需要提供目标类/实现类的接口信息和目标对象,而 sqlSession.getMapper(AccountMapper.class)
中的 sqlSession
正好能对应目标对象,AccountMapper.class
正好是接口信息。
一切都说得通,唯一有些对不上的地方是,如果把 sqlSession
视为目标对象的话,其相应的 DefaultSqlSession
类并没有实现 AccountMapper
接口。
我们一定要让代理类和目标类/实现类,实现同一套接口吗?我认为是不一定的。
代理类和目标类/实现类实现同一套接口,其最终目的不还是为了便于代理实现吗?接口统一了代理类和目标类的方法定义,因此在调用代理对象的代理方法时,根据接口中的方法信息,能够直接确定到对应的目标对象的目标方法。
回到 MyBatis,虽然代理类和 DefaultSqlSession
没有实现同一个接口 AccountMapper
,但是已经存在一套完善的机制,可以建立其代理方法和目标方法的映射关系。
因此,可以说,MyBatis 的接口代理机制,是对 JDK 动态代理的更灵活的运用。
MyBatis 接口代理机制源码分析 准备工作:从配置文件解析开始 SqlSessionFactoryBuilder
的 build
方法的主要工作是:解析核心配置文件,产生包含完整配置信息的 Configuration
对象,然后根据该对象构造 SqlSessionFactory
工厂对象。
1 2 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder () .build(Resources.getResourceAsReader("mybatis-config.xml" ));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public SqlSessionFactory build (Reader reader) { return build(reader, null , null ); } public SqlSessionFactory build (Reader reader, String environment, Properties properties) { XMLConfigBuilder parser = new XMLConfigBuilder (reader, environment, properties); return build(parser.parse()); } public SqlSessionFactory build (Configuration config) { return new DefaultSqlSessionFactory (config); }
Configuration
包含了很多和 SQL Mapper 相关的属性,这里看其中比较重要的两个:
Map<String, MappedStatement> mappedStatements
:解析所有 XxxMapper.xml
文件的主要产出,key
为 sqlId
,value
为 sqlId
对应标签中的所有信息的封装。
MapperRegistry mapperRegistry
:对所有的 XxxMapper
接口进行注册。该对象中包含一个 Map 集合属性 Map<Class<?>, MapperProxyFactory<?>> knownMappers
,其中 key
为 XxxMapper
接口的 Class
对象,value
为接口对应的 Mapper 代理工厂,用于生成相应接口的动态代理。
mapperRegistry
的完成注册的细节如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public Configuration parse () { parseConfiguration(parser.evalNode("/configuration" )); return configuration; } private void parseConfiguration (XNode root) { mapperElement(root.evalNode("mappers" )); } private void mapperElement (XNode parent) throws Exception { for (XNode child : parent.getChildren()) { if ("package" .equals(child.getName())) { String mapperPackage = child.getStringAttribute("name" ); configuration.addMappers(mapperPackage); } else { } } } public void addMappers (String packageName) { mapperRegistry.addMappers(packageName); } public void addMappers (String packageName) { addMappers(packageName, Object.class); } public void addMappers (String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil <>(); resolverUtil.find(new ResolverUtil .IsA(superType), packageName); Set<Class<? extends Class <?>>> mapperSet = resolverUtil.getClasses(); for (Class<?> mapperClass : mapperSet) { addMapper(mapperClass); } } public <T> void addMapper (Class<T> type) { knownMappers.put(type, new MapperProxyFactory <>(type)); }
准备工作的最后一步,研究一下 knownMappers
的值部分,MapperProxyFactory
的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class MapperProxyFactory <T> { 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") protected T newInstance (MapperProxy<T> mapperProxy) { 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); } }
MapperProxyFactory
类的 Map<Method, MapperMethodInvoker> methodCache
属性值得重点关注,methodCache
属性主要在创建 MapperProxy
调用处理器时用到,其具体作用需要结合 MapperProxy
类的源码来理解。
目前准备工作已经做好了,总结如下:
MyBatis 进行核心配置文件解析后,会产生一个 Configuration
对象保存所有配置信息,并放在 SqlSessionFactory
对象中,该 Configuration
对象中与 Mapper 相关的有两个属性:mappedStatements
和 mapperRegistry
,我们深入研究了一下后者。
mapperRegistry
属性底层维护了一个 Map 集合,对于配置文件中配置的每个 XxxMapper
接口,使用 XxxMapper
接口的 Class
对象作为 key
,使用基于 XxxMapper
接口构建的 MapperProxyFactory
工厂对象作为 value
,键值对 put
到该 Map 集合中完成接口信息的注册操作。后续需要生成接口代理时,将从该 Map 集合中取出对应的 MapperProxyFactory
来进行生产。
一个 XxxMapper
对应一个 MapperProxyFactory
对象,工厂对象中持有对应的 XxxMapper
接口信息,对外提供 newInstance()
方法生成接口的动态代理对象,但需要传入目标对象参数 sqlSession
,底层使用 JDK 动态代理技术实现。
sqlSession.getMapper(XxxMapper.class)
的源码分析1 StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Override public <T> T getMapper (Class<T> type) { return configuration.getMapper(type, this ); } public <T> T getMapper (Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } @SuppressWarnings("unchecked") public <T> T getMapper (Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); return mapperProxyFactory.newInstance(sqlSession); } public T newInstance (SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy <>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } @SuppressWarnings("unchecked") protected T newInstance (MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class [] { mapperInterface }, mapperProxy); }
有了准备工作的基础,以上代码理解起来应该没有任何难度:
生成接口代理,需要提供目标对象 sqlSession
和 XxxMapper.class
sqlSession
对象中持有一个 Configuration
对象,从而持有 MapperRegistry
对象,从而持有 knownMappers
Map 集合
XxxMapper
接口早在核心配置文件解析时,就已经注册到 knowMappers
中,因此可以直接通过 XxxMapper.class
获取到接口对应的 MapperProxyFactory<XxxMapper>
工厂对象,通过工厂对象的 newInstance(sqlSession)
即可生成接口的动态代理对象。
MyBatis 底层是使用 JDK 动态代理技术来实现接口代理机制的,而 JDK 动态代理中,最关键的就是调用处理器的实现。现在,是时候继续深入去看 MyBatis 的调用处理器 MapperProxy
的源码了。
先看 MapperProxy
源码的第一部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class MapperProxy <T> implements InvocationHandler , Serializable { private final SqlSession sqlSession; private final Class<T> mapperInterface; 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; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } }
代理对象不一定总是调用接口方法,也有可能调用顶级父类 Object
的方法,比如 toString
方法。所以 invoke
方法中,需要对被调用的方法 Method method
分两种情况进行处理。
我们重点看第二种情况,method
是 XxxMapper
接口中的方法,为什么这种情况下,method
不能直接 invoke(sqlSession)
呢?
因为目标对象 sqlSession
所属的 DefaultSqlSession
类并没有实现 XxxMapper
接口,假设 method
指向 XxxMapper.selectXxxById
方法,显然 sqlSession
本身是没有 selectXxxById
方法的,自然无法直接反射执行。
我们真正要执行的是目标对象 sqlSession
中的如 selectOne
这样的目标方法,暂且称这些方法为 mapperMethod
,那么所谓的处理就是,确定 method
实际要执行的 mapperMethod
是什么。
cachedInvoker(method)
的工作大致就是如此,只不过另外还包含了缓存的逻辑,从 MapperProxyFactory
传入的 methodCache
就是在这里排上用场的。
再来看 MapperProxy
源码的第二部分,我们要深入 cachedInvoker(method)
了。
1 2 3 4 5 6 7 8 9 10 private MapperMethodInvoker cachedInvoker (Method method) throws Throwable { return MapUtil.computeIfAbsent(methodCache, method, m -> { return new PlainMethodInvoker (new MapperMethod (mapperInterface, method, sqlSession.getConfiguration())); }); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static <K, V> V computeIfAbsent (Map<K, V> map, K key, Function<K, V> mappingFunction) { V value = map.get(key); if (value != null ) { return value; } return map.computeIfAbsent(key, mappingFunction); } default V computeIfAbsent (K key, Function<? super K, ? extends V> mappingFunction) { Objects.requireNonNull(mappingFunction); V v; if ((v = get(key)) == null ) { V newValue; if ((newValue = mappingFunction.apply(key)) != null ) { put(key, newValue); return newValue; } } return v; }
我们已经看到 cachedInvoker(method)
中方法缓存 methodCache
的作用了,但其实我们更关心没有缓存的情况下,method
是如何转换处理的。
method
通过以下语句,转换为 MapperMethodInvoker
:
1 return new PlainMethodInvoker (new MapperMethod (mapperInterface, method, sqlSession.getConfiguration());
我们先来看 MapperProxy
源码的第三部分,了解 MapperMethodInvoker
和 PlainMethodInvoker
分别是什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class MapperProxy <T> implements InvocationHandler , Serializable { 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; } @Override public Object invoke (Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); } } }
可以看到,PlainMethodInvoker
只是简单地在 MapperMethod
上又包了一层,invoke
内部还是调用 MapperMethod
的 execute
方法来完成操作。
看来问题的关键,还是在 MapperMethod
类中。
这里先问一个问题,MapperMethod
是否指向 sqlSession
的某个具体函数呢?
通过 MapperMethod
的构造函数,以及 execute
方法都可以看出来,答案是否定的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class MapperMethod { 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); } public Object execute (SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); 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: { break ; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { } else if (method.returnsMany()) { } else if (method.returnsMap()) { } else if (method.returnsCursor()) { } else { } break ; } return result; } }
MapperMethod
并不指向 sqlSession
中的某个具体函数,而是在真正执行 execute
时,才根据构造初始化时的解析结果(sql 语句类型,返回值类型等),选择适当的函数来执行。
因此,cacheInvoker(method)
返回的 MapperMethodInvoker
,其内部包装的 MapperMethod
其实并不是接口方法 method
对应到 sqlSession
中的某个具体方法。
不过直接将 MapperMethod
理解为指向 sqlSession
的某个具体方法也没有问题。这样在理解 MapperProxy
的 invoke
方法时,会更加形象:如果 method
是 XxxMapper
接口的方法,通过 cachedInvoker(method)
获取到对应的 mapperMethod
再反射执行。
1 2 3 4 5 6 7 8 9 @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } }
mapper.selectXxxByXxxx
源码分析1 List<Student> students = mapper.selectByNameAndSex2("张三" , '男' );
动态代理对象 mapper
执行代理方法 selectByNameAndSex2
,会触发调用处理器 MapperProxy
中的 invoke
方法
1 2 3 4 5 6 7 8 9 @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } }
第一次执行 cachedInvoker(method)
时,由于 method
还未被加入 methodCache
,因此会进入 MapUtil.computeIfAbsent
的匿名内部类中,进行 method
到 mapperMethodInvoker
的转换
1 2 3 4 5 6 7 private MapperMethodInvoker cachedInvoker (Method method) throws Throwable { return MapUtil.computeIfAbsent(methodCache, method, m -> { return new PlainMethodInvoker (new MapperMethod (mapperInterface, method, sqlSession.getConfiguration())); }); }
cachedInvoker(method)
获取到 mapperMethodInvoker
后,调用 invoke
方法。
1 2 cachedInvoker(method).invoke(proxy, method, args, sqlSession);
mapperMethodInvoker.invoke
底层实际还是调用 mapperMethod.execute
,StudentMapper.selectByNameAndSex2
对应的 Sql 语句类型是 SELECT
,返回值类型是 List<Student>
,因此最终实际会调用 sqlSession.selectList
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Override public Object invoke (Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); } public Object execute (SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case SELECT: if (method.returnsMany()) { result = executeForMany(sqlSession, args); } break ; } return result; } private <E> Object executeForMany (SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectList(command.getName(), param); return result; }
同样的接口方法 selectByNameAndSex2
再执行一次。
1 List<Student> students2 = mapper.selectByNameAndSex2("李四" , '女' );
第二次进入 cacheInvoker(method)
时,由于 methodCache
中已存在 method
对应的 mapperMethodInvoker
,因此不会进入 MapUtil.computeIfAbsent
的匿名内部类中,而是直接获取到并执行 invoke
。
1 2 3 4 5 6 7 8 9 @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } }