概念:是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
简单代码演示:
抽象接口:
真实角色:
代理角色:
测试使用:
概念:是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
在jdk的api中提供了java.lang.reflect.Proxy它可以帮助我们完成动态代理创建
注意:在java中使用Proxy来完成动态代理对象的创建,只能为目标实现了接口的类创建代理对象。
动态代理是在内存中直接生成代理对象。
InvocationHandler接口:
接口中声明了一个方法
Invoke方法,它是在代理对象调用行为时,会执行的方法,而invoke方法上有三个参数:
简单代码演示:
简单使用:
由上面的介绍可以看出,一般JDK的动态代理基本套路是这样:
但是在MyBatis中却看着好像不是一个“正宗”的动态代理,在Mybatis中被代理的类只有一个Mapper接口,这里取名为TestMapper,在MyBatis中是这样的:
我们定义了TestMapper,在MyBatis中通过配置,可以在Configuration中得到TestMapper,有MapperProxy,也建立了联系:
但是却没有TestMapper的实现类。
在上面的动态代理测试方法中:
至少是与TargetImpl差不多的。
但是在MyBatis中:
这个代理明显与IdCarfMapper完全不一样。结合动态代理的特性,可以猜测下次debug会进入invoke方法:
这里首先会比较当前调用的方法是不是来自Object类,意思就是比如当前的方法是toString()、hashCode()等这些来自Object的方法就直接走method.invoke()方法(这里也说明了框架作者考虑问题的严谨性);
然后回进一步判断isDefaultMethod(),代码注释如下,这里就不过多介绍了,判断的原因和上面差不多:
首先判断是不是default方法:
private boolean isDefaultMethod(Method method) {
return ((method.getModifiers()
//不是抽象并且不是静态的 public方法
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
//并且是接口
&& method.getDeclaringClass().isInterface();
}
通过MethodHandles调用default方法:
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {//获得 MethodHandles.Lookup 的构造器(Lookup私有构造器)
final Constructor constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class> declaringClass = method.getDeclaringClass();
//创建一个 MethodHandles.Lookup 实例
//调用 代理对象的 接口方法 (default方法)
return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
前面的两个健壮性判断完毕后就开始真正的执行SQL干正事了,很明显真正执行SQL方法是不会像之前的动态代理那样简单的method.invoke(),因为在这个invoke方法里面需要传入一个被代理对象,这里明显只有一个IdCardMapper接口,在MapperProxy中是没有接口实例的,而是会先执行cachedMapperMethod()方法,获取MapperMethod,其实cachedMapperMethod()方法也很简单:
private MapperMethod cachedMapperMethod(Method method) {
//先从methodCache中获取MapperMethod
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
而这个methodCache其实就是一个Map:
/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
//methodCache就是一个Map
private final Map methodCache;
那么methodCache是从哪来的呢,其实就是构造的时候传递的一个引用:
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
而这个引用是从哪来的呢,其实就是工厂制造的时候传进去的:
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
那又是从哪来的呢,其实也就是一个Map:
/**
* @author Lasse Voss
*/
public class MapperProxyFactory {
private final Class mapperInterface;
private final Map methodCache = new ConcurrentHashMap();
再来看看是怎么execute()的:
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: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
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 {
//将参数convert
Object param = method.convertArgsToSqlCommandParam(args);
//又回到了sqlSession,使用sqlSession执行
result = sqlSession.selectOne(command.getName(), param);
}
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;
}
再来看看selectOne()方法,其实也是比较简单的:
@Override
public T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
//会调用selectList()方法
List list = this.selectList(statement, parameter);
if (list.size() == 1) {
//只返回一个,取第一个即可
return list.get(0);
} else if (list.size() > 1) {
//这是MyBatis中很常见的一种异常
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
再看看selectList()方法:
@Override
public List selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//委托,单一职责
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
通过上面流程可以看到MyBatis的Mapper执行SQL虽然用到了动态代理,但是不是那种“正规”的动态代理。
继续查看executor.query()方法,在这里出现了一点“事故”,先看看这个executor:
Executor是一个非常重要的接口:
其中BaseExecutor是一个抽象实现类,这里也使用了模板设计模式,这里将一些共性抽取到了BaseExecutor中,这个模板设计模式的使用也是值得学习的。
这里理应应该要执行的是由子类实现的query()方法:
但是进入的是Plugin类的invoke方法,这里又有一层代理:
最后实际执行的是一个Interceptor的拦截的一个方法:
一看到这个Interceptor,其实就恍然大悟了:
这不就是MyBatis的Plugins嘛,因为我的项目中配置了PageHelper,所以这里进入了PageHelper,不过这个也顺便说明了PageHelper是在执行语句之前执行的:
这里PageHelper不是主要的,以后再分析,为了避免干扰,先把PageHepler的引入和配置去掉。
去掉后,正常了,进入了org.apache.ibatis.executor.BaseExecutor#query,BaseExecutor是一个抽象类:
这里首先会获取CacheKey,在MyBatis中的CahcheKey设计的非常好,可以看下,这里就不过多介绍了。
这里的ErrorContext明显是ThreadLocal的一个封装,这个也是一个非常重要的东东,它对异常信息的封装便于使用此框架的人能够快速的排错:
会先走一级缓存,如果一级缓存没有拿到,会执行下面的方法:
这个方法里面有个很有意思的一个方法,localCache会先putObject()一下,这行代码的意义是声明一个占位符,当发送一次查询数据的请求时,设置该占位符告诉其他请求正在查询数据库,请其他请求先阻塞或休眠。当这次请求查询到数据之后,将真正的数据放到占位符的位置,缓存数据。如果其他请求与该次请求查询的数据时一样的,直接从一级缓存中拿数据减少了查询请求对数据库的压力 (org.apache.ibatis.executor.BaseExecutor.DeferredLoad#load org.apache.ibatis.executor.BaseExecutor.DeferredLoad#canLoad),接下来会执行doQuery()方法,doQuery()方法是BaseExecutor中的一个模板方法:
后面就是JDBC的执行流程了:
这里会有一个拦截器链去执行Plugins的拦截:
当sqlsessionFactory获取sqlsession时,产生的ParameterHandler、ResultSetHandler、StatementHandler、Executor都是由org.apache.ibatis.session.Configuration 类的方法 newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor产生的代理对象,而这些代理对象是经过适用规则的plugin层层代理的 。
最后会返回一个StatementHandler:
是不是和上面的Executor非常相似,也说明这个设计模式是多么的重要。
在prepareStatement()方法中,获取了Connection:
接下来会执行handler.
而这个delegate也就是SatementHandler:
接下来看到了我们熟悉的PrepareStatement(是不是也说明MyBatis是防SQL注入的):
执行完成后会由ResultSetHandler处理结果:
再往下:
@Override
public List
进入handlerResultSet方法:
首先会创建一个返回结果对象:
会执行org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, org.apache.ibatis.executor.loader.ResultLoaderMap, java.lang.String)方法,首先会循环遍历为ResultMapping属性赋值。如果是嵌套查询而且配置了延迟加载,其中这里的createProxy()方法会生成一个具有延迟加载功能的代理对象:
在官方文档中对这里也有相关的说明:
这里还要注意的是,这里返回的是一个所有属性都为空的结果:
获取到返回对象后,就开始设值了:
MyBatis的关闭也是很有讲究的,ResultSet就会关闭与ResultSet相关的东东:
最后会执行:
只返回一个结果集就取第一个元素,否则全部返回:
最后代码再返回,会发现这里又会执行closeStatement:
这也是更加说明MyBatis是一层一层关闭的:
最后会将ErrorContext reset一下:
这个是没有异常的情况,如果有异常就会执行catch里面的内容:
如果出现Exception,一目了然: