mybatis mapper的注册一共分为两个 阶段:初始化 knownMappers,向knownMappers中put数据
1.初始化 knownMappers
前置条件如下(测试代码):
@BeforeAll
static void setUp() throws Exception {
// create a SqlSessionFactory
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
执行步骤:
第一步:
第二步:加载配置文件
第三步:构建XMLConfigBuilder
第四步:构建Configuration对象
第五步:构造MapperRegistry对象
第六步:构建knownMappers对象
2.向knownMappers中put数据
具体代码如下:
第一步:解析配置文件中的configuration标签
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
第二步:解析配置文件中的mappers标签
private void parseConfiguration(XNode root) { try { // issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); //-------------解析配置文件中的mappers标签-----------// mapperElement(root.evalNode("mappers")); //-------------解析配置文件中的mappers标签-----------// } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
第三步:判断是是否配置了package标签
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"); //----如果配置了package标签那就直接通过name属性去注册mapper-----// configuration.addMappers(mapperPackage); } else { ...... }(通过package去注册mapper比通过namespace简单,我们主要讲以namespace去注册)
否则走else的逻辑
{ 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); //根据配置文件去加载mapper文件 try(InputStream inputStream = Resources.getResourceAsStream(resource)) { XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //通过此处去注册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()); //通过此处去注册mapper 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."); } }
一般我们会在配置文件中配置如下代码:
<mappers> <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml" /> </mappers>
(当然mapper中还有两个属性:url 和class url 主要是通过远程去联网读取文件并注册,calss则是直接通过你给的路径去加载类,其实他们主要的目的就一个 找到与xml文件对应的mapper接口并转化为MapperProxyFactory并将其注册到knowmappers中)
此时mybati会先根据我们配置的 resource路径去加载该配置文件,然后解析其中的namespace属性,通过此属性去找到对应的接口,进而完成mapper的注册。
具体流程如下:
加载mapper文件:
//根据配置文件去加载mapper文件 try(InputStream inputStream = Resources.getResourceAsStream(resource)) { XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //解析mapper文件 mapperParser.parse(); }
解析mapper文件:
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); //通过此处去注册mapper bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
注册mapper(关键代码已有中文注释):
private void bindMapperForNamespace() { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { //通过类的全路劲名去加载类 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { // ignore, bound type is not required } if (boundType != null && !configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); //注册mapper configuration.addMapper(boundType); } } }
此时我们可以走到注册mapper的最终的地方:
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 { //------------------------最终注册的是该mapper的代理工厂类--------// //注入该接口对应的代理工厂类 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. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
最终注册的是该mapper的代理工厂类
至此便完成了mapper的注册。
总结一下:mapper注册其实他们主要的目的就一个 找到与xml文件对应的mapper接口并转化为MapperProxyFactory并将其注册到knowmappers中。