本文是我的MyBatis源码分析专栏中第三节的一小部分,作为试读部分,详细讲述了MyBatis是如何通过动态代理创建Dao接口的实现类的。
专栏地址:MyBatis源码分析
专栏字数:14w+
专栏目录:
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 queryAllUsers(); save(User user);}UserDAOImpl implements UserDAO{queryAllUsers(){sqlSession.select("namespace.id",参数)|-Excutor|-StatementHandler|- ParameterHandler , ResultSetHandlerTypeHandler }save(){sqlSession.insert("namespace.id",参数)}
}
来回顾一下动态代理设计模式:

核心逻辑需要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;}@Overridepublic 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());}
}
测试方法如下:
@Testpublic 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 users = userDao.queryAll();users.forEach(System.out::println);}

那如果我们userDao中接口如果有参数呢?Dao中还会有很多方法,上面的例子只有一个方法。
public T getMapper(Class type, SqlSession sqlSession) {final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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.newProxyInstancepublic class MapperProxyFactory {//各种xxxDao的class文件private final Class mapperInterface;//方法缓存private final Map methodCache = new ConcurrentHashMap<>();public MapperProxyFactory(Class mapperInterface) {this.mapperInterface = mapperInterface;}public Class getMapperInterface() {return mapperInterface;}public Map getMethodCache() {return methodCache;}@SuppressWarnings("unchecked")protected T newInstance(MapperProxy mapperProxy) {//核心代码,创建代理,mapperProxy就是具体的代理实现return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {//相当于上面简单例子中的MyMapperProxy中的构造方法final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}}
看完了创建代理的工厂源码,下面该去看看添加的额外方法了:这里是关键代码,我们结合debug来看。
MapperProxy的构造方法如下:
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;//方法缓存this.methodCache = methodCache;}
最开始的方法缓存是空的:

MapperProxy实现了InvocationHandler,所以我们核心看invoke方法的实现:
public class MapperProxy implements InvocationHandler, Serializable {// ....@Overridepublic 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接口中的方法做了封装,然后再invokereturn 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又是什么呢?
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 map = new TreeMap<>();int paramCount = paramAnnotations.length;// get names from @Param annotationsfor (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {if (isSpecialParameter(paramTypes[paramIndex])) {// skip special parameterscontinue;}String name = null;for (Annotation annotation : paramAnnotations[paramIndex]) {if (annotation instanceof Param) {hasParamAnnotation = true;name = ((Param) annotation).value();break;}}if (name == null) {// @Param was not specified.if (useActualParamName) {name = getActualParamName(method, paramIndex);}if (name == null) {// use the parameter index as the name ("0", "1", ...)// gcode issue #71name = String.valueOf(map.size());}}map.put(paramIndex, name);}names = Collections.unmodifiableSortedMap(map);}
分析到这里,我们已经将mapper.xml中的各种参数返回值都封装到了
MapperMethod中,下面也就该找真正执行sqlSession的地方:
在MapperProxy类中,真正执行SqlSession相关操作的代码在mapperMethod.execute中
@Overridepublic Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args);}

进入到execute方法中:这里就是我们熟悉的sqlSession方法:
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 {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}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;}