MyBatis中Mapper的动态代理模式
0 疑问
在 Mybatis 中,我们只需要定义 ⌈Mapper接口⌋ 和 ⌈该Mapper接口所对应的XML配置文件⌋,就可以完成对数据库的 “增删查改” 操作,获取结果集。但是,从始至终我们都没有定义 Mapper 接口的实现类,且 Java 中也不允许使用接口来创建实例对象,那么当我们来调用接口中的方法时,到底是由谁来执行这个方法呢?
- 答案就是 Mybatis 借助设计模式中的 ⌈动态代理模式⌋ 帮助我们在底层创建了 Mapper 接口的实现类,从而调用该实现类的方法完成了我们所需要的操作;
- 对设计模式中的 ⌈代理模式⌋ 不熟悉的小伙伴可以看我的另一篇 CSDN 博客:设计模式之代理模式;
- 在此过程中,主要涉及三个至关重要的类:MapperRegistry、MapperProxy 和 MapperMethod。
1 示例
我们以下面的代码为例,深入源码且言简意赅地说明 Mybatis 中的 Mapper 动态代理的实现过程,其中重点是第 5、6 点。
@Test
public void test() throws IOException {
//1.读取MyBatis的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//3.通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//4.创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//5.通过动态代理模式创建UserMapper接口的代理实现类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//6.调用UserMapper接口中的方法
User user = userMapper.getUserById();
System.out.println(user.toString());
}
2 实现过程
2.1 代理对象的生成
- 当执行到示例代码的第 5 点时,Mybatis 在底层借助 MapperRegistry 类生成 UserMapper 接口的具体实现类,即 userMapper 对象的代理对象。下面是 MapperRegistry 类的源码,其中最重要的是
getMapper()
方法,下面对其进行详细解释;
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
//生成代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
public <T> boolean hasMapper(Class<T> type) {
return this.knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(this.knownMappers.keySet());
}
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
Iterator var5 = mapperSet.iterator();
while(var5.hasNext()) {
Class<?> mapperClass = (Class)var5.next();
this.addMapper(mapperClass);
}
}
public void addMappers(String packageName) {
this.addMappers(packageName, Object.class);
}
}
- 从源码中我们可以看到,MapperRegistry 中使用一个 Map 容器 knownMappers 存储已经注册过的 Mapper 接口,该容器的 key 值为 Mapper 接口,value 值为可以生成该 Mapper 接口具体实现类(代理对象)的 MapperProxyFactory 工厂,如下图所示;
- 所以,代理对象的生成过程可以描述为:我们首先调用 SqlSession 对象的
getMapper()
方法(传入 Mapper 接口作为参数)。然后,Mybatis 底层实际去执行 MapperRegistry 类的getMapper()
方法,在该类的 knownMappers 容器中以我们传入的 Mapper 接口为 key 值找到可以生成该 Mapper 接口所对应的代理对象的 MapperProxyFactory 工厂。最后,MapperProxyFactory 工厂通过newInstance()
方法生成 Mapper 接口的代理对象 MapperProxy 类实例,至此进入下一阶段。
2.2 Mapper接口方法的执行
- 在第 5 点中的步骤执行完毕之后,我们成功获得了 UserMapper 接口的代理对象,下面解释在第 6 点的步骤中如何执行 Mapper 接口的
getUserById()
方法。首先是由 UserMapper 接口的代理对象 MapperProxy 类实例在其内部通过反射机制调用invoke()
方法来生成getUserById()
方法所对应的 MapperMethod 类实例对象,由 MapperMethod 类实例作为执行者完成我们的数据库操作;
/*
* 代码有删减,只截取核心代码
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {
//生成getUserById()方法对应的MapperMethod类实例对象
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);
}
}
private static class PlainMethodInvoker implements MapperProxy.MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
this.mapperMethod = mapperMethod;
}
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
//调用MapperMethod的execute()方法完成实际的数据库操作
return this.mapperMethod.execute(sqlSession, args);
}
}
}
- 然后,
getUserById()
方法所对应的 MapperMethod 类实例对象执行自己的execute()
方法,解析 UserMapper 接口的getUserById()
方法,并找到执行对应的 SQL 语句的方法(在 switch…case…中查找)。最终,getUserById()
方法会对应到 “SELECT” 中的executeForMany()
方法,该方法与数据库进行交互,查询到我们所需要的数据,并返回结果,结束整个过程。
/*
* 代码有删减,只截取核心代码
*/
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
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;
}
}
}