2. mybatis为什么直接调用接口方法就可以实现语句的查询

上一篇文章我们了解了什么是mybatis,并通过一个用例实现了一个简单的mybatis查询数据库的数据,在文章的最后针对mybatis如何实现这些功能提出了几个问题。接下来的文章中会对这些问题做一一解答。
本篇文章中解决第一个问题,mybatis为什么调用接口方法就可以实现语句的查询?
实现该功能主要分成两块进行:

  1. 注册Mapper
  • 通过new SqlSessionFactoryBuilder().build(inputStream);构建SqlSessionFactory。在builder方法中将配置文件中的内容转换为Configuration中的对象属性。SqlSessionFactory持有Configuration对象的引用。
  1. 获取Mapper,调用接口方法
    mybatis查询通过以下几个关键类和代码实现
  • 通过sqlSession = factory.openSession();创建SqlSession对象
  • 通过TestMapper testMapper = sqlSession.getMapper(TestMapper.class);获取mapper代理对象
  • 通过testMapper.queryTestDO(1));获取数据库结果集

注册部分关键代码:

XmlConfigBuilder.java

 private void parseConfiguration(XNode root) {
    try {
     ......
      //(本文关注点)解析mappers标签,将mapper注册到MapperRegistry中
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

/**
*解析mappers标签下的mapper标签,可通过标签属性获取资源package、class、url、resource四种方式获取mapper资源
**/
 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
    //遍历获取mappers标签下的所有子标签(package/mapper)
      for (XNode child : parent.getChildren()) {
      //如果mappers下子标签package,则解析name属性该包下的所有类,并注册到MapperRegistry中
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
     	//解析mapper标签, resource、class、url属性有且只能配置一个,解析属性中的内容,注册到MapperRegistry中
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

Configuration.java

  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }
  
  public  void addMapper(Class type) {
    mapperRegistry.addMapper(type);
  }

  public void addMappers(String packageName, Class superType) {
    mapperRegistry.addMappers(packageName, superType);
  }

MapperRegistry.java

/**
*该类主要用于记录注册的Mapper的动态代理对象。
**/
public class MapperRegistry {

  private Configuration config;
  //注册器
  private final Map, MapperProxyFactory> knownMappers = new HashMap, MapperProxyFactory>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

/**
*获取mapper核心代码,即测试用例中getMapper最终调用的地方
**/
  @SuppressWarnings("unchecked")
  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);
    }
  }
  
  public  boolean hasMapper(Class type) {
    return knownMappers.containsKey(type);
  }

/**
*注册mapper核心代码
**/
  public  void addMapper(Class type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
	//注册mapper接口对象, key为接口对象, value为mapper代理工厂,下面我们看下MpperProxyFactory中有哪些东东
        knownMappers.put(type, new MapperProxyFactory(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        //解析接口方法上的注解@Select(Provider) @Update(Provider) @Delete(Provider) @Insert(Provider)
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  /**
   * @since 3.2.2
   */
  public Collection> getMappers() {
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName, Class superType) {
    ResolverUtil> resolverUtil = new ResolverUtil>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set>> mapperSet = resolverUtil.getClasses();
    for (Class mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
  
}

/**
 * @author Lasse Voss
 */
public class MapperProxyFactory {

  private final Class mapperInterface;
  private Map methodCache = new ConcurrentHashMap();

  public MapperProxyFactory(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class getMapperInterface() {
    return mapperInterface;
  }

  public Map getMethodCache() {
    return methodCache;
  }

//哈哈,终于找到了,这里创建了一个Mapper的代理对象,并持有MapperProxy对象的引用
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}
  1. 获取Mapper,调用接口方法部分关键代码分析
          在注册的时候我们已经看到了,调用sqlSession.getMapper(TestMapper.class);方法最终调用的就是MapperRegistry.getMapper(Class type, SqlSession sqlSession)方法,获取TestMapper接口实现类(动态代理生成的类)的对象,该类会实现TestMapper中的所有方法。

下面是动态代理生成的类的部分信息:

public final class $Proxy0 extends Proxy implements Proxy9 {
......
    private static Method m3;
......
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
......
    public final List queryTestDO(int var1) throws  {
        try {
        //通过反射调用MapperProxy类的invoke方法。在invoke方法中实现具体的数据库操作逻辑
            return (List)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
.......

    static {
        try {
......
            m3 = Class.forName("com.sun.proxy.$Proxy9").getMethod("queryTestDO", Integer.TYPE);
......
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

综上所述,调用接口方法其实最终调用的是MapperProxy的invoke方法,现在我们预览下MapperProxy中invoke方法做了哪些东西,接下来我们会分析方法中具体的实现。

public class MapperProxy implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class mapperInterface;
  private final Map methodCache;

  public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //为什么需要加这个判断呢?如果调用的是Object的toString(),equals(),hashCode()等方法的时候,直接反射调用Object对象的方法。否则调用sql执行方法
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行sql
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

}

你可能感兴趣的:(mybatis)