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 {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
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 {
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 {
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;
}
}
}
|