Mybatis源码解析(七):Mapper代理原理
创始人
2024-03-13 16:44:15
0

Mybatis源码系列文章

手写源码(了解源码整体流程及重要组件)

Mybatis源码解析(一):环境搭建

Mybatis源码解析(二):全局配置文件的解析

Mybatis源码解析(三):映射配置文件的解析

Mybatis源码解析(四):sql语句及#{}、${}的解析

Mybatis源码解析(五):SqlSession会话的创建

Mybatis源码解析(六):缓存执行器操作流程

Mybatis源码解析(六):查询数据库主流程

Mybatis源码解析(七):Mapper代理原理


目录

  • 前言
  • 一、环境准备
  • 二、引入映射配置文件方式
  • 三、\标签的解析
    • 1、通过包路径获取Mapper接口
    • 2、注解方式mapper接口的解析
    • 3、xml和mapper接口需要同包同名的原因?
  • 四、Mapper接口代理对象的生成
  • 五、代理对象执行接口方法的流程
  • 总结


前言

  • 文章主要围绕着如下几个点,展开源码解析:
    • ;是如何进行解析的?
    • sqlSession.getMapper(UserMapper.class);是如何生成的代理对象?
    • mapperProxy.findById(1);是怎么完成的增删改查操作?

一、环境准备

  • java代码
@Test
public void test2() throws IOException {// 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中 注意:配置文件并没有被解析InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");// 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);// 3. (1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession();// 4. JDK动态代理生成代理对象UserMapper mapperProxy = sqlSession.getMapper(UserMapper.class);// 5.代理对象调用方法User user = mapperProxy.findUserById(100);System.out.println("MyBatis源码环境搭建成功....");sqlSession.close();
}
  • 核心配置文件sqlMapConfig.xml




  • 实体映射配置文件UserMapper.xml




二、引入映射配置文件方式

先说个结论,后续源码验证:如果不指定xml,则会在Mapper接口同目录下寻找

  • 方式一: 指定xml,缺点需要每个映射xml都要手动添加
  • 方式二: 没有指定xml,会从同目录下寻找xml,缺点也是需要每个Mapper接口都要手动添加
  • 方式三: 没有指定xml,会从同目录下寻找xml,会遍历此包下所有Mappe接口

三、标签的解析

  • 为什么单独讲这个标签?
    1. 这个标签是多种引入映射文件的最佳选,也是工作中必用的
    2. 创建代理类工厂,为以后通过Mapper接口类生成代理实现类做准备
  • 标签在核心配置文件的标签下
  • 方式一也会创建代理类工厂,不过是在解析xml文件后,方式三是先创建代理类工厂,再解析xml
  • Mybatis源码解析(三):映射配置文件的解析:这篇单独讲了指定配置文件的解析

进入解析标签方法

  • 子标签的解析在Mybatis源码解析(三):映射配置文件的解析里详细讲了,这里讲下子标签的解析
  • 获取包名,调用addMappers方法
private void mapperElement(XNode parent) throws Exception {if (parent != null) {// 获取标签的子标签for (XNode child : parent.getChildren()) {// 子标签if ("package".equals(child.getName())) {// 获取mapper接口和mapper映射文件对应的package包名String mapperPackage = child.getStringAttribute("name");// 将包下所有的mapper接口以及它的代理工厂对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂configuration.addMappers(mapperPackage);} else {// 子标签// 获取子标签的resource属性String resource = child.getStringAttribute("resource");// 获取子标签的url属性String url = child.getStringAttribute("url");// 获取子标签的class属性String mapperClass = child.getStringAttribute("class");// 它们是互斥的if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);try(InputStream inputStream = Resources.getResourceAsStream(resource)) {// 专门用来解析mapper映射文件XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());// 通过XMLMapperBuilder解析mapper映射文件mapperParser.parse();}} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);try(InputStream inputStream = Resources.getUrlAsStream(url)){XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());// 通过XMLMapperBuilder解析mapper映射文件mapperParser.parse();}} else if (resource == null && url == null && mapperClass != null) {Class mapperInterface = Resources.classForName(mapperClass);// 将指定mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}
}

进入configuration的addMappers方法

public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);
}
  • mapperRegistry对象中核心属性就是knownMappers
    • key:Mapper接口的Class对象
    • value:Mapper接口代理类工厂
public class MapperRegistry {private final Configuration config;private final Map, MapperProxyFactory> knownMappers = new HashMap<>();...
}

进入mapperRegistry的addMappers方法

public void addMappers(String packageName) {addMappers(packageName, Object.class);
}

1、通过包路径获取Mapper接口

  • resolverUtil.find方法:加载包路径下Mapper接口
  • mapperSet:mapper接口Class对象集合
  • addMapper方法:将Mapper接口添加到上面所说的Map集合knownMappers中
public void addMappers(String packageName, Class superType) {ResolverUtil> resolverUtil = new ResolverUtil<>();// 根据package名称,加载该包下Mapper接口文件(不是映射文件)resolverUtil.find(new ResolverUtil.IsA(superType), packageName);// 获取加载的Mapper接口Set>> mapperSet = resolverUtil.getClasses();for (Class mapperClass : mapperSet) {// 将Mapper接口添加到MapperRegistry中addMapper(mapperClass);}
}

resolverUtil.find方法

  • getPackagePath方法:将包名-com.xc.mapper转换为资源路径-com/xc/mapper(.替换成/)
  • children:获取资源路径下的资源,如下
    在这里插入图片描述
  • addIfMatching方法:将Mapper接口的Class对象添加到matches集合中
public ResolverUtil find(Test test, String packageName) {String path = getPackagePath(packageName);try {List children = VFS.getInstance().list(path);for (String child : children) {if (child.endsWith(".class")) {addIfMatching(test, child);}}} catch (IOException ioe) {log.error("Could not read package: " + packageName, ioe);}return this;
}

resolverUtil.getClasses()

private Set> matches = new HashSet<>();
...  
public Set> getClasses() {return matches;
}

addMapper方法

  • 循环遍历matches集合,将所有Mapper接口Class对象添加到knownMappers
    • key:Mapper接口的Class对象
    • value:Mapper接口代理类工厂
  • 创建注解解析Builder,调用parse解析方法(xml的解析也包含在内)
public  void addMapper(Class type) {if (type.isInterface()) {// 如果Map集合中已经有该mapper接口的映射,就不需要再存储了if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {// 将mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂knownMappers.put(type, new MapperProxyFactory<>(type));// 用来解析注解方式的mapper接口MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);// 解析注解方式的mapper接口parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}
}

2、注解方式mapper接口的解析

  • loadXmlResource方法:xml文件的解析
  • parseStatement方法:原理其实和Mybatis源码解析(三):映射配置文件的解析差不多
    • 不同点:xml解析标签内的id一致,就是通过方法名匹配标签id获取MappedStatement
    • command.getType():是