前言
至此为止,我们Mybatis的基本内容都已经讲的差不多了,以下的内容纯属一些个人分享和见解啦。
动态代理之前我们在 Marco’s Java【设计模式】之【代理模式】有讲解过,但是我们并没有详细的说明动态代理的实际用途,那我们学习过Mybatis之后,应该对动态代理有一定的认知啦。动态代理实际上就是代理了我们接口的实现类,就好比我们之前在代理模式提到的案例,Alibaba代售平台就是我们的代理,而火车站的直接售票点就是我们的实现类,实现了TrainTicket接口,这样做的好处就是当我们想代理的类有多个的时候,只用在代理类中添加相应的实现类,而不用去修改别的代码,并且在代理的invoke方法中,这就好比Alibaba代售平台不仅可以代售火车票,还可以代售演唱会门票,而我们只用将代售演唱会门票的实现类引用进来即可。
我们还可以将一些流程重复的东西放在里面,总而言之也就提高了代码的复用性,以及减少了类与类之间的耦合度,可谓是一箭双雕!那么在讲解Mybatis的动态代理之前呢,我分享一点我之前遇到的一个代理对象为null的"BUG"疑案。
疑案解密之代理对象为空灵异事件
之所以我把BUG用引号引起来的原因是因为我以为它是BUG,但其实是我自己没有钻研透彻代理的机制,而产生的误解,不过搞懂了这个问题之后,对动态代理的理解可以说是上升了一个档次了。
我相信各位在写代码最讨厌遇到的应该是值为null吧?就算不是最讨厌的,我觉得应该是排前三了。毕竟我熬夜敲完代码,本应该好好的睡一觉,结果天上飘来一串红字NullPointException
,顿时想死的心都有了…
依旧是夜深人静的晚上,我一个喝着咖啡,瞧着代码,突然我闲来无事,在敲代理案例的时候,打印了一下我的代理对象,控制台一直显示为null?
这个还不奇怪,奇怪的是打印为null,但是我的代理明明调用了实现类的方法,并且方法还执行了啊?吓得我一哆嗦…这不是见鬼了?
我们来重现一下当时的案发现场,首先是我们的接口和它的实现方法,也是我们的目击证人,证人说当天晚上它自己被JVM加载进来,并且正常工作,没有发现有什么问题?TrainTicket可以为它作证!
public interface TrainTicket {
void searchTicket(Map<String, String> map);
void buyTicket();
}
class RailwayTicket implements TrainTicket {
@Override
public void buyTicket() {
System.out.println("buy tickets");
}
@Override
public void searchTicket(Map<String, String> map) {
System.out.println(map.get("name") + "search tickets");
}
}
好,接下来审问我们的代理类AlibabaTicketHandler,疑点最大的犯罪嫌疑人,它反馈当天晚上,他确实是引用了一下我们的实现类RailwayTicket,并且双方达成一致,只能用借用他的searchTicket方法,但是引用之后,没有其他人为他作证后续做了什么。
public class AlibabaTicketHandler<T> implements InvocationHandler {
T obj;
@SuppressWarnings("unchecked")
public T newInstance(T obj) {
this.obj = obj;
ClassLoader classLoader = obj.getClass().getClassLoader();
Class<?>[] interfaces = obj.getClass().getInterfaces();
T obj2 = (T) Proxy.newProxyInstance(classLoader, interfaces, this);
return obj2;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("searchTicket")) {
method.invoke(obj, args);
}
return null;
}
}
最后是我们的测试类Client,它举报案件的目击证人,正是它反馈,把钱给了代理,交易也执行了,但是代理跑路了?怎么也找不到。
public class Client {
public static void main(String[] args) {
TrainTicket railwayTicket = new RailwayTicket();
AlibabaTicketHandler<TrainTicket> alibabaProxy = new AlibabaTicketHandler<TrainTicket>();
TrainTicket trainTicket = alibabaProxy.newInstance(railwayTicket);
Map<String, String> map = new HashMap<>();
map.put("name", "marco");
trainTicket.searchTicket(map);
}
}
那就很奇怪了,在我的印象里,如果说一个对象为空,那根本就调用不了方法啊?
这里打印了null,但是很显然,方法确实被调用了。
为了找出根源所在,我们使用工具,帮我们还原一下案发线程,和作案手法,在Client测试类中添加下面这句话
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
,这句话是什么意思呢?
大家有没有想过一个问题,我们得newInstance方法怎么返回得代理对象,代理对象在哪里生成?
按照常理来说,代理是必须要实现被代理对象接口才能调用它的方法,那么我根本没看到Proxy类啊?
其实使用动态代理生成得代理类被"隐藏"起来了,隐藏在我们系统得内存中,newInstance方法相当于我们在内存中创建了一个代理对象,那么System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
就是在内存创建Proxy之后拦截这个对象,并写入硬盘中。
那么知道原理之后我们来打印看看吧~
public class Client {
public static void main(String[] args)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
TrainTicket railwayTicket = new RailwayTicket();
AlibabaTicketHandler alibabaProxy = new AlibabaTicketHandler();
TrainTicket trainTicket = (TrainTicket) alibabaProxy.newInstance(railwayTicket);
System.out.println(trainTicket);
}
}
调用上面得方法后会在src得同级目录下生成以下文件夹,那么我们使用反编译工具打开$Proxy0.class看看
package com.sun.proxy;
import com.sxt.proxy.TrainTicket;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Map;
public final class $Proxy0
extends Proxy
implements TrainTicket
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void searchTicket(Map paramMap)
throws
{
try
{
this.h.invoke(this, m3, new Object[] { paramMap });
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void buyTicket()
throws
{
try
{
this.h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("com.sxt.proxy.TrainTicket").getMethod("searchTicket", new Class[] { Class.forName("java.util.Map") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.sxt.proxy.TrainTicket").getMethod("buyTicket", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
好,案发现场被还原了,那么我们就看看问题出在哪里,之前我们问题是为什么打印了trainTicket代理对象,但是值为null?
TrainTicket trainTicket = (TrainTicket) alibabaProxy.newInstance(railwayTicket);
System.out.println(trainTicket);
我们先想想打印trainTicket调用了什么方法?
没错,调用了toString()方法,那们我们找找看Proxy有没有重写toString()方法,欸,好像有一个
public final String toString()
throws
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
我们发现toString()默认是返回了这么个东西(String)this.h.invoke(this, m2, null);
看着很眼熟对吧?这就是我们的的Handler的invoke方法,那我们反过头来看看之前的invoke方法有没有什么问题
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("searchTicket")) {
method.invoke(obj, args);
}
return null;
}
我们发现这里好像没有返回有效的值,返回的是一个null!
那我们加上返回值看看结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = object = method.invoke(obj, args);
return object;
}
终于破案了!原来并不是什么"灵异事件",咱们还是要相信科学,哈哈~
Mybatis动态代理
以前呢,很怕跟源码觉得源码好难啊,别人那么专业,而我是个菜鸡,看不懂,算了~
现在觉得曾今的我还是太年轻的,能看源码并且看懂源码真的是一件非常享受的事情,就算是逼着自己把源码看个100遍,也要搞懂别人框架里面代码的结构,不说完全弄懂吧,至少有个印象,那么对于你今后编程的思想上是会有很大帮助的~
不扯dan了,本节的核心内容就是带大家了解下Mybatis的底层结构,以及怎么去实现动态代理,操作JDBC数据库的。那么我们就从SqlSession为切入点来查看Mybatis的源码啦~
打开SqlSession的实现类DefaultSqlSession我们会发现有两个很重要的类Configuration配置类(存储的是Mybatis基本配置文件mybatis.config.xml的信息),和sql语句执行类Executor。接下来点开Configuration找到我们今天的主角MapperRegistry
MapperRegistry通过名字翻译过来就是映射注册的意思,因此我们所有的Mapper.xml的信息全部都储存在这个注册表里面,代理能够顺利的执行的第一个前提就是Mapper.xml能够被顺利解析出来!
package org.apache.ibatis.binding;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
//注册Mapper接口的,提取并存储Mapper.xml信息,以及生成代理对象的类
public class MapperRegistry {
//全局配置文件对象
private Configuration config;
//MapperProxyFactory用于创建Mapper代理对象的工厂,它的Key是mapper的类型对象,Value是MapperProxyFactory对象
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
//通过getMapper()获取生成的代理对象
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//通过Mapper的接口类型的key 去Map当中查找mapperProxyFactory,如果mapperProxyFactory为null,抛出异常
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不为null,则创建一个当前接口的代理对象 并且传入sqlSession
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//判断是否注册这个Mapper接口类型
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
//注册Mapper接口
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
//如果已经有了该Mapper接口,那么添加失败抛出异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//否则将Mapper接口的类对象作为key,MapperProxyFactory做为value,存储到Map集合中
knownMappers.put(type, new MapperProxyFactory<T>(type));
//注解的解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
//获取所有的mapper
public Collection<Class<?>> getMappers() {
//Collections.unmodifiableCollection这个可以得到一个集合的镜像
//它的返回结果不可直接被改变,否则会提示
return Collections.unmodifiableCollection(knownMappers.keySet());
}
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
//通过包名扫描下面所有接口
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
}
我们发现当mapper接口已经被注册,并且mapperProxyFactory不为null时,会通过mapperProxyFactory创建一个当前接口的代理对象 并mapperProxyFactory.newInstance(sqlSession),那么接下来我们就转战MapperProxyFactory!
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.session.SqlSession;
//创建Mapper接口代理对象的工厂类MapperProxyFactory
public class MapperProxyFactory<T> {
//具体传进来的Mapper接口的Class对象
private final Class<T> mapperInterface;
//接口下方法的缓存 key是Method方法对象 value是接口中封装的方法对象
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//创建了一个代理类,第一个参数是类加载器,第二个参数是存放Mapper接口的Class对象的数组
//第三个对象是mapperProxy,下面会讲到
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//在这里传入sqlSession 创建一个Mapper接口的代理类,是对外提供的newInstance方法
public T newInstance(SqlSession sqlSession) {
//创建MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
//调用上面的protected的newInstance方法 返回一个接口的代理类
return newInstance(mapperProxy);
}
}
浏览过上面的代码,我们发现核心的类是MapperProxy,也就是Mapper代理类,Mybatis动态代理也就是被该类控制的,因此我们点开MapperProxy的源码接着看。
我们发现Mybatis的代理实现的是JDK的动态代理InvocationHandler
package org.apache.ibatis.binding;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
//实现了JDK动态代理的接口 InvocationHandler
//在invoke方法中实现了代理方法调用的细节
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
//接口中方法的缓存 通过MapperProxyFactory传递过来的,用于创建MapperProxy对象
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方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断当前被调用的方法是不是继承Object的方法,如toString()等,这些方法直接调用不需要做处理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
//如果不是基础方法,则存放在缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用mapperMethod.execute 核心的地方就在这个方法里,这个方法对才是真正对SqlSession进行的包装调用
return mapperMethod.execute(sqlSession, args);
}
//缓存处理
private MapperMethod cachedMapperMethod(Method method) {
//这里使用的是lambda表达式取代了匿名内部类,作用是判断这个方式是不是缺席(存在)的
return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
methodCache.put(method, mapperMethod);
return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
});
}
}
通过上面的源码我们不难发现,当被调用的方法不是Object的方法且执行方法所对应的MapperMethod类的实例不存在的时候,会加入到缓存中,并且创建MapperMethod实例对象,那么MapperMethod实例对象又会执行execute方法,因此当我们执行invoke方法的时候,并没有直接的去调用相关数据库操作的方法,而是交给了我们的MapperMethod对象来处理。
package org.apache.ibatis.binding;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;
//MapperMethod是整个代理机制的核心类,它针对于mapper中的各个操作对象,诸如insert、select做了封装
public class MapperMethod {
//SqlCommand是一个静态内部类,它封装了SQL标签的类型 insert delete update select
private final SqlCommand command;
//MethodSignature 也是一个内部类 封装了方法的参数信息以及返回类型信息等
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
//该方法是对SqlSession的包装调用
public Object execute(SqlSession sqlSession, Object[] args) {
//定义返回结果,因为无法判断具体的返回值类型,因此声明为Object类型
Object result;
//INSERT操作
if (SqlCommandType.INSERT == command.getType()) {
//处理参数,将参数转为SqlCommandParam,这里的param实则上是一个map,key是genericParamName,value则是它对应的值
Object param = method.convertArgsToSqlCommandParam(args);
//调用sqlSession的insert方法
result = rowCountResult(sqlSession.insert(command.getName(), param));
//UPDATE操作
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
//DELETE操作
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
//SELECT操作
} else if (SqlCommandType.SELECT == command.getType()) {
//如果返回void 并且参数有resultHandler对象
//则调用 void select(String statement, Object parameter, ResultHandler handler)方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
//当结果值为多个时调用这个方法
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
//如果返回类型是Map,则调用executeForMap方法
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
//如果以上都不满足则查询单个对象,以上这些方法大家应该都见过,是sqlSession直接调用执行的方法
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
//如果以上信息都不匹配,抛出方法名称不匹配异常
throw new BindingException("Unknown execution method for: " + command.getName());
}
//如果返回值为空,并且方法返回值类型是基础类型,且不是void则抛出异常
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;
}
//用于处理并统计受影响的行数,这里划分的比较细,分为Integer、Long、Boolean、void来解析
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long) rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = (rowCount > 0);
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
//这里的MappedStatement实质上就是xml标签中的insert、select、delete、update标签生成的对象
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
//通过xml文件的id获取MappedStatement对象
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + 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.");
}
Object param = method.convertArgsToSqlCommandParam(args);
//当有RowBounds分页查询对象的时候的处理
if (method.hasRowBounds())
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
//返回多行结果时调用sqlSession.selectList方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
//如果参数含有rowBounds则调用分页的查询
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
//如果没有分页查询对象rowBounds时则调用普通查询
result = sqlSession.<E>selectList(command.getName(), param);
}
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
//如果返回对象为数组,则调用convertToArray转化为数组
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
//否则转化为声明的集合
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
metaObject.addAll(list);
return collection;
}
@SuppressWarnings("unchecked")
private <E> E[] convertToArray(List<E> list) {
E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size());
array = list.toArray(array);
return array;
}
//当结果类型为map的时候执行的方法
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
public static class ParamMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -2212268410512043556L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
}
return super.get(key);
}
}
通过上面的源码我们可以发现MapperMethod有两个匿名内部类:SqlCommand、MethodSignature
SqlCommand封装了SQL标签 insert delete update select 的操作类型和它的id信息
MethodSignature 也是一个内部类,它封装了方法的参数信息以及返回类型信息等,接下来我们继续来分析这两个匿名内部类的结构吧~
//封装了mappedStatement对象具体执行的动作
public static class SqlCommand {
//xml标签的id,或者说是对应接口的方法名称
private final String name;
//insert update delete select的具体操作类型
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
//拿到方法的完全限定名 比如 com.marco.UserDao.queryAll
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
//获取MappedStatement对象,也就是我们的JDBC操作标签的对象,就好比下面的insert标签
//<select id="queryAll" resultType="List">
// select * from u_user
//</select>
if (configuration.hasStatement(statementName)) {
//根据方法的完全限定名查询这个mappedStatement,如果有,则返回mappedStatement对象
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) {
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
//如果mappedStatement为空则抛出异常
if (ms == null) {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
name = ms.getId();//xml中操作标签的id
type = ms.getSqlCommandType();//xml中操作的类型
//判断SQL标签类型,如果是UNKNOWN就抛异常
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
}
//封装了接口当中方法的参数类型和返回值类型等信息
public static class MethodSignature {
//是否返回多个结果
private final boolean returnsMany;
//返回值是否是Map
private final boolean returnsMap;
//返回值是否是void
private final boolean returnsVoid;
//返回值类型
private final Class<?> returnType;
//mapKey
private final String mapKey;
//resultHandler类型参数的位置
private final Integer resultHandlerIndex;
//rowBound类型参数的位置
private final Integer rowBoundsIndex;
//用来存放参数信息
private final SortedMap<Integer, String> params;
//该参数是否已经被命名
private final boolean hasNamedParameters;
//属性的初始化
public MethodSignature(Configuration configuration, Method method) throws BindingException {
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.hasNamedParameters = hasNamedParams(method);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
//处理传入的参数
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
//这里传入的参数要确保向后兼容性
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
public boolean hasRowBounds() {
return (rowBoundsIndex != null);
}
public RowBounds extractRowBounds(Object[] args) {
return (hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null);
}
public boolean hasResultHandler() {
return (resultHandlerIndex != null);
}
public ResultHandler extractResultHandler(Object[] args) {
return (hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null);
}
public String getMapKey() {
return mapKey;
}
public Class<?> getReturnType() {
return returnType;
}
public boolean returnsMany() {
return returnsMany;
}
public boolean returnsMap() {
return returnsMap;
}
public boolean returnsVoid() {
return returnsVoid;
}
//获取独有的参数的索引
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 {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
String paramName = String.valueOf(params.size());
if (hasNamedParameters) {
paramName = getParamNameFromAnnotation(method, i, paramName);
}
params.put(i, paramName);
}
}
return params;
}
private String getParamNameFromAnnotation(Method method, int i, String paramName) {
final Object[] paramAnnos = method.getParameterAnnotations()[i];
for (Object paramAnno : paramAnnos) {
if (paramAnno instanceof Param) {
paramName = ((Param) paramAnno).value();
}
}
return paramName;
}
private boolean hasNamedParams(Method method) {
boolean hasNamedParams = false;
final Object[][] paramAnnos = method.getParameterAnnotations();
for (Object[] paramAnno : paramAnnos) {
for (Object aParamAnno : paramAnno) {
if (aParamAnno instanceof Param) {
hasNamedParams = true;
break;
}
}
}
return hasNamedParams;
}
}
}
源码预览完毕,我们也可以有个结论,关于Mybatis的动态代理如何执行可以划分为几个步骤
1)解析XML中的返回值ResultMap,Insert、Select、Delete、Update为一个个的对象,其中Insert、Select、Delete、Update统称为MappedStatement对象,将这些对象存放在MappedMethod容器中
2)当我们getMapper拿到代理对象之后,通过传入的接口类型以及通过反射解析调用的接口方法,可以组成方法的完全限定名
3)通过完全限定名,我们可以找到对应的MappedStatement对象,获取到它的操作类型,id以及里面的sql语句
4)解析传入的参数和原始sql语句,根据操作类型生成相应的方法,在这一步就模拟的创建生成了一个实现了被调用的接口的实现类的实现方法
5)执行相应的方法,其实就是解析xml中的JDBC操作标签,然后调用sqlSession中的方法
6)通过MethodSignature解析传入的参数值,以及返回的参数值,封装相应的方法去适配返回值
看似我们是调用了接口的方法,实则上是简介的调用的sqlSession的方法,那么这样的话,就算没有实现类和实现方法,我们也可以实现方法的调用,这也正是Mybatis的魅力所在!