Mybatis Mapper 接口工作原理 1 - knownMappers 初始化

knownMappers 是 MyBatis 中 MapperRegistry 的一个核心属性,用于存储所有注册的 Mapper 接口和对应的 MapperProxyFactory。它的初始化过程涉及 MyBatis 的配置解析和 Mapper 接口的注册。下面我们从源码级别详细分析 knownMappers 的初始化过程。


1. knownMappers 的定义

knownMappersMapperRegistry 中的一个 Map,定义如下:

public class MapperRegistry {
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
}
  • knownMappers:存储 Mapper 接口和对应的 MapperProxyFactory
  • MapperProxyFactory:用于创建 Mapper 接口的代理对象。

2. knownMappers 的初始化过程

knownMappers 的初始化过程分为以下几个步骤:

2.1 MyBatis 配置文件的解析

MyBatis 在启动时会解析配置文件(如 mybatis-config.xml),并将配置信息加载到 Configuration 对象中。Configuration 是 MyBatis 的核心配置类,负责管理所有的配置信息。

2.1.1 解析 <mappers> 标签

在 MyBatis 配置文件中,<mappers> 标签用于注册 Mapper 接口或 XML 文件。例如:

<mappers>
    <mapper class="com.example.mapper.UserMapper"/>
</mappers>

MyBatis 通过 XMLConfigBuilder 解析配置文件,并调用 mapperElement 方法处理 <mappers> 标签:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    // 加载 XML 文件
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    // 加载 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) {
                    // 加载 Mapper 接口
                    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.addMapper():将 Mapper 接口注册到 Configuration 中。

2.2 Configuration.addMapper() 的源码分析

Configuration.addMapper() 方法用于将 Mapper 接口注册到 Configuration 中,并最终注册到 MapperRegistryknownMappers 中:

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            knownMappers.put(type, new MapperProxyFactory<>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}
  • knownMappers.put(type, new MapperProxyFactory<>(type)):将 Mapper 接口和对应的 MapperProxyFactory 注册到 knownMappers 中。
  • MapperAnnotationBuilder:用于解析 Mapper 接口上的注解(如 @Select@Insert 等)。

2.3 MapperProxyFactory 的创建

MapperProxyFactory 是用于创建 Mapper 接口代理对象的工厂类。它的构造函数如下:

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
}
  • mapperInterface:Mapper 接口的类型。

2.4 MapperAnnotationBuilder.parse() 的源码分析

MapperAnnotationBuilder 用于解析 Mapper 接口上的注解,并将其转换为 MyBatis 的 MappedStatement

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        loadXmlResource();
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        parseCache();
        parseCacheRef();
        for (Method method : type.getMethods()) {
            if (!canHaveStatement(method)) {
                continue;
            }
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                || method.getAnnotation(Insert.class) != null
                || method.getAnnotation(Update.class) != null
                || method.getAnnotation(Delete.class) != null) {
                parseStatement(method);
            }
        }
        parsePendingMethods();
    }
}
  • parseStatement():解析方法上的注解,并创建 MappedStatement
  • MappedStatement:封装了 SQL 语句的配置信息。

3. 总结

knownMappers 的初始化过程可以概括为以下步骤:

  1. 解析配置文件

    • MyBatis 解析配置文件中的 <mappers> 标签,加载 Mapper 接口或 XML 文件。
  2. 注册 Mapper 接口

    • 调用 Configuration.addMapper() 方法,将 Mapper 接口注册到 MapperRegistryknownMappers 中。
  3. 创建 MapperProxyFactory

    • 为每个 Mapper 接口创建 MapperProxyFactory,并将其存储到 knownMappers 中。
  4. 解析注解

    • 使用 MapperAnnotationBuilder 解析 Mapper 接口上的注解,并创建 MappedStatement

通过以上流程,knownMappers 完成了初始化,并存储了所有注册的 Mapper 接口和对应的 MapperProxyFactory。这些信息在后续的 Mapper 接口代理对象创建和 SQL 查询中起到了关键作用。

posted @   CyrusHuang  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示