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.输出传输 (RPCDubbo 
             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

MyBatis精简版--实现接口代理方式实现Mapper(Dao) 和动态SQL

使用动态代理实现dao接口

mybatis中dao接口与mapper关联的理解

mybatis--动态代理实现