MyBatis是如何为Dao接口创建实现类的
Posted 结构化思维wz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis是如何为Dao接口创建实现类的相关的知识,希望对你有一定的参考价值。
本文是我的MyBatis源码分析专栏中第三节的一小部分,作为试读部分,详细讲述了MyBatis是如何通过动态代理创建Dao接口的实现类的。
专栏地址:MyBatis源码分析
专栏字数:14w+
专栏目录:
文章目录
SqlSession.getMapper如何设计的?
UserDAO userDAO = SqlSession.getMapper(UserDAO.class);
//UserDAO接口的实现类的对象
//疑问? UserDAO接口实现类 在哪里? 我们写的是xml文件呀? --> 这是因为动态字节码技术(Spring 的 AOP 也是用了动态字节码技术)
//动态字节码技术 ---> 类 在JVM 运行时创建 ,JVM运行结束后,消失了
动态自己码技术如何创建的实现类呢?
1. 如何 创建 UserDAO XXXDAO接口的实现类
代理 (动态代理)应用场景:
a. 为原始对象(目标)增加【额外功能】
b. 远程代理 1.网络通信 2.输出传输 (RPC)Dubbo
c. 接口实现类,我们看不见实实在在的类文件,但是运行时却能体现出来。无中生有
Proxy.newProxyIntance(ClassLoader,Interface,InvocationHandler)
SqlSession.getMapper应用了代理模式:
debugSqlSession.getMappe
方法可以发现雀氏是个代理对象:
实现类的逻辑
interface UserDAO
List<User> queryAllUsers();
save(User user);
UserDAOImpl implements UserDAO
queryAllUsers()
sqlSession.select("namespace.id",参数)
|-Excutor
|-StatementHandler
|- ParameterHandler , ResultSetHandler
TypeHandler
save()
sqlSession.insert("namespace.id",参数)
手写Mapper接口的实现类代理
来回顾一下动态代理设计模式:
核心逻辑需要SqlSession来实现,SqlSession操作JDBC又需要statement,
namespace.id
: 可以通过接口class和方法名拿到。(接口的方法名不就是mapper标签中的id吗?)所以代理类的构造方法如下设计:
public class MyMapperProxy implements InvocationHandler
private SqlSession sqlSession;
private Class daoClass;
public MyMapperProxy(SqlSession sqlSession,Class daoClass)
this.sqlSession = sqlSession;
this.daoClass = daoClass;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
System.out.println("namespace:"+daoClass.getName()+"."+method.getName());
return sqlSession.selectList(daoClass.getName()+"."+method.getName());
测试方法如下:
@Test
public void testPorxy() throws IOException
InputStream inputStream = Resources.getResourceAsStream("MyBatis/MyBatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
SqlSession sqlSession = factory.openSession();
Class[] interfaces = new Class[]UserDao.class;
//创建代理
UserDao userDao = (UserDao) Proxy.newProxyInstance(
MybatisTest.class.getClassLoader(),
interfaces,
new MyMapperProxy(sqlSession,UserDao.class));
List<User> users = userDao.queryAll();
users.forEach(System.out::println);
那如果我们userDao中接口如果有参数呢?Dao中还会有很多方法,上面的例子只有一个方法。
源码探究
public <T> T getMapper(Class<T> type, SqlSession sqlSession)
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null)
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try
//核心代码
return mapperProxyFactory.newInstance(sqlSession);
catch (Exception e)
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
mapperProxyFactory.newInstance(sqlSession);
就相当于上面例子中Proxy.newProxyInstance
MapperProxyFactory核心代码分析:
public class MapperProxyFactory<T>
//各种xxxDao的class文件
private final Class<T> mapperInterface;
//方法缓存
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface)
this.mapperInterface = mapperInterface;
public Class<T> getMapperInterface()
return mapperInterface;
public Map<Method, MapperMethodInvoker> getMethodCache()
return methodCache;
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy)
//核心代码,创建代理,mapperProxy就是具体的代理实现
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] mapperInterface , mapperProxy);
public T newInstance(SqlSession sqlSession)
//相当于上面简单例子中的MyMapperProxy中的构造方法
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
MapperProxy
看完了创建代理的工厂源码,下面该去看看添加的额外方法了:这里是关键代码,我们结合debug来看。
MapperProxy的构造方法如下:
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache)
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
//方法缓存
this.methodCache = methodCache;
最开始的方法缓存是空的:
MapperProxy实现了InvocationHandler,所以我们核心看invoke方法的实现:
public class MapperProxy<T> implements InvocationHandler, Serializable
// ....
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
try
//如果是Object中的方法直接执行,例如toString、equals、wait...
if (Object.class.equals(method.getDeclaringClass()))
return method.invoke(this, args);
else
//核心代码,对dao接口中的方法做了封装,然后再invoke
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
catch (Throwable t)
throw ExceptionUtil.unwrapThrowable(t);
// ....
这里通过debug来看一下cachedInvoker(method)
的作用:
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable
try
//computeIfAbsent() 方法对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hashMap 中。
return MapUtil.computeIfAbsent(methodCache, method, m ->
//判断是不是默认方法
if (m.isDefault())
try
if (privateLookupInMethod == null)
return new DefaultMethodInvoker(getMethodHandleJava8(method));
else
return new DefaultMethodInvoker(getMethodHandleJava9(method));
catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e)
throw new RuntimeException(e);
else
//不是默认方法
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
);
catch (RuntimeException re)
Throwable cause = re.getCause();
throw cause == null ? re : cause;
debug发现执行完这个方法,methodCache中有了我们正在调用的userDao中的方法:
那么这个mapperMethod
又是什么呢?
MapperMethod
public class MapperMethod
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config)
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
实际上MapperMethod就是封装了两个非常核心的成员变量:
-
SqlCommand :sql命令
private final String name; //标签id(namespace.id) private final SqlCommandType type; //Sql命令类型:这条sql是查询还是插入? public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) if (method.getAnnotation(Flush.class) != null) name = null; type = SqlCommandType.FLUSH; else throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); else //核心代码 name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) throw new BindingException("Unknown execution method for: " + name);
-
MethodSignature 方法签名(返回值、分页、参数…)
public static class MethodSignature private final boolean returnsMany; private final boolean returnsMap; private final boolean returnsVoid; private final boolean returnsCursor; private final boolean returnsOptional; private final Class<?> returnType; private final String mapKey; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver; public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); if (resolvedReturnType instanceof Class<?>) this.returnType = (Class<?>) resolvedReturnType; else if (resolvedReturnType instanceof ParameterizedType) this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType(); else this.returnType = method.getReturnType(); this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray(); this.returnsCursor = Cursor.class.equals(this.returnType); this.returnsOptional = Optional.class.equals(this.returnType); this.mapKey = getMapKey(method); this.returnsMap = this.mapKey != null; this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); //@param 中的参数名是如何解析的,在这一步 this.paramNameResolver = new ParamNameResolver(configuration, method);
ParamNameResolver方法:处理@Param
public ParamNameResolver(Configuration config, Method method)
this.useActualParamName = config.isUseActualParamName();
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++)
if (isSpecialParameter(paramTypes[paramIndex]))
// skip special parameters
continue;
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex])
if (annotation instanceof Param)
hasParamAnnotation = true;
name = ((Param) annotation)以上是关于MyBatis是如何为Dao接口创建实现类的的主要内容,如果未能解决你的问题,请参考以下文章
MyBatis精简版--实现接口代理方式实现Mapper(Dao) 和动态SQL
MyBatis精简版--实现接口代理方式实现Mapper(Dao) 和动态SQL