title:MapperMethod
date:2017年11月25日22:20:53
categories:Mybatis
我们接着上面继续分析
@Test
public void testSelect() {
SqlSession session = null;
try {
session = MybatisUtil.getCurrentSession();
UserDao userDao = session.getMapper(UserDao.class);
List list = new ArrayList();
list.add(1);
list.add(3);
list.add(25);
List userList = userDao.queryList(list);
System.out.println(JSON.toJSONString(userList));
} catch (Exception e) {
// TODO: handle exception
} finally {
if (session != null)
session.close();
}
}
我们都这一句代码进行DEBUG
List userList = userDao.queryList(list);
首先,我们会进入MapperProxy这个类
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果方法是Object类自带的方法,比如没有被重写的equals toString, hashcode 等,还是执行原来的方法
// getDeclaringClass()返回表示声明由此 Method 对象表示的方法的类或接口的 Class 对象。
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//如果不是object的自带方法,先去 Map methodCache中找是否已经存在这个method了,没有就将method封装成MapperMethod存进mehtodCache中然后返回MapperMethod。
final MapperMethod mapperMethod = cachedMapperMethod(method);
//然后执行MapprMehtod的execute方法
return mapperMethod.execute(sqlSession, args);
}
我们看看cachedMapperMethod
private MapperMethod cachedMapperMethod(Method method) {
//当method是第一次调用时,我们无法在methodCache中得到MapperMethod,我们就得新创建一个MapperMethod
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//创建一个MapperMethod,这里是关键。
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
终于到了我们重点要讲解的MapperMethod类了
我们先看MapperMethod构造方法,初始化了他的两个属性,command和method
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
这两个属性非常的重要,要详细的介绍的话需要大量的篇幅,所以先放在一旁。
我们先看具体的执行方法execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
}
//很明显我们的SqlCommandType是SELECT类型
else if (SqlCommandType.SELECT == command.getType()) {
//是否返回类型是void类型并且Method参数列表中包含resultHandler,具体的判断在文末分析
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
}
//我们调用的方法的返回类型是List,会走这个分支。我们看下method.returnsMany的具体实现吧。
//这里的method是MapperMethod的属性MethodSignature对象
else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
executeForMany
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
//这个方法我们非常有必要分析一下,这个方法封装了我们调用的Mapper接口Method的参数列表对应实参
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
//终于到了我们的DefaultSqlSession的方法了
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
convertArgsToSqlCommandParam
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
//没有参数
if (args == null || paramCount == 0) {
return null;
}
//有一个参数,而且不是@Param注解修饰的参数
else if (!hasNamedParameters && paramCount == 1) {
//直接返回形参对应的实参
return args[params.keySet().iterator().next()];
}
/*
否则
多个参数或者一个参数但是使用@param注解
就要按以下方法操作
*/
else {
final Map param = new ParamMap
这里我们需要了解params对象到底是怎样得来的,他的具体实现是怎样的。
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
private SortedMap getParams(Method method, boolean hasNamedParameters) {
final SortedMap params = new TreeMap();
final Class>[] argTypes = method.getParameterTypes();
//如果是@param修饰的,则put的Value是@param(value)中的Value
//如果不是,则put的Value就是当前parms这个Map的size
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;
}
下面是分析上述方法时用到的Method和Class类中的方法介绍:
Method
getParameterTypes
public Class>[] getParameterTypes()按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。如果底层方法不带参数,则返回长度为 0 的数组。
返回:
此对象所表示的方法的参数类型
Class
isAssignableFrom
public boolean isAssignableFrom(Class> cls)判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。如果是则返回 true;否则返回 false。如果该 Class 表示一个基本类型,且指定的 Class 参数正是该 Class 对象,则该方法返回 true;否则返回 false。
特别地,通过身份转换或扩展引用转换,此方法能测试指定 Class 参数所表示的类型能否转换为此 Class 对象所表示的类型。有关详细信息,请参阅 Java Language Specification 的第 5.1.1 和 5.1.4 节。
参数:
cls - 要检查的 Class 对象
返回:
表明 cls 类型的对象能否赋予此类对象的 boolean 值
抛出:
NullPointerException - 如果指定的 Class 参数为 null。
从以下版本开始:
JDK1.1
getComponentType
public Class> getComponentType()返回表示数组组件类型的 Class。如果此类不表示数组类,则此方法返回 null。
返回:
如果此类是数组,则返回表示此类组件类型的 Class
从以下版本开始:
JDK1.1
另请参见:
Array
hasNamedParams判断Mapper接口中的指定方法中的参数列表中是否有@Param注解修饰
private boolean hasNamedParams(Method method) {
boolean hasNamedParams = false;
/*
getParameterAnnotations()
返回表示按照声明顺序对此 Method 对象所表示方法的形参进行注释的那个数组的数组。
*/
final Object[][] paramAnnos = method.getParameterAnnotations();
for (Object[] paramAnno : paramAnnos) {
for (Object aParamAnno : paramAnno) {
//看是否参数有@Param注解修饰
if (aParamAnno instanceof Param) {
hasNamedParams = true;
break;
}
}
}
return hasNamedParams;
}
}
getParameterAnnotations
public Annotation[][] getParameterAnnotations()返回表示按照声明顺序对此 Method 对象所表示方法的形参进行注释(应该是注解把)的那个数组的数组。(如果底层方法没有参数,则返回长度为零的数组。如果该方法有一个或多个参数,则为每个不带注释的参数返回长度为零的嵌套数组。)返回数组中包含的注释对象是可序列化的。此方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
返回:
表示按声明顺序对此 Method 对象所表示方法的形参进行注释的那个数组的数组
//select语句,Method的返回类型如果是void而且参数列表中有参数类型是ResultHandler
if (method.returnsVoid() && method.hasResultHandler()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
public boolean hasResultHandler() {
return (resultHandlerIndex != null);
}
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
getUniqueParamIndex分析
//原来是个很简单的方法就是判断是否参数列表中是否有参数类型为ResultHandler的,有就返回index,没有就是null
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;
}
1.MapperMethod的execute方法的作用
首先是将Method方法的实参封装成Object对象
然后调用DefaultSqlSession中的方法,将上述的Object对象以及command.getName() (name = ms.getId(); SqlCommand对象中的name即是该Method对应XML配置文件中SQL的的唯一标示)作为参数 传入方法中,上述两个参数是必须要传递的
本例是使用了selectList方法
result = sqlSession.selectList(command.getName(), param);
2.Mybatis为什么要设计MapperMethod类呢?
其实经过这一分析,如果熟悉设计模式的人就会发现,这是使用了命令模式。
什么是命令模式呢,命令模式就是让请求发送者与接收者解耦
命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
在命令模式结构图中包含如下几个角色:
● Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
● ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。
● Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
● Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。
命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。在最简单的抽象命令类中只包含了一个抽象的execute()方法,每个具体命令类将一个Receiver类型的对象作为一个实例变量进行存储,从而具体指定一个请求的接收者,不同的具体命令类提供了execute()方法的不同实现,并调用不同接收者的请求处理方法。
这里的调用者就是MapperProxy
接受者是DefaultSqlSession
而具体的命令类是MapperMethod