mybatis源码解析之Configuration加载(五)
概述
前面几篇文章主要看了mybatis配置文件configuation.xml中<setting>,<environments>标签的加载,接下来看一下mapper标签的解析,先来看下标签的配置:
1 <mappers> 2 <!-- 方式一,mapper类和xml文件可以不再同一个目录下 --> 3 <!-- <mapper resource="mapper/newsMapper.xml" /> --> 4 5 <!-- 方式二,必须保证mapper类和xml文件在同一个目录下 --> 6 <!--<package name="com.mybatis.read.dao" /> --> 7 8 <!-- 方式三,必须保证mapper类和xml文件在同一个目录下 --> 9 <mapper class="com.mybatis.read.dao.NewsMapper" /> 10 </mappers>
常用的主要就有上面三种方式,指定mapper的xml文件,指定package,指定mapper文件,我们来具体解析下。
解析
我们看下具体开始解析的代码:
1 private void mapperElement(XNode parent) throws Exception { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 if ("package".equals(child.getName())) { 5 String mapperPackage = child.getStringAttribute("name"); 6 configuration.addMappers(mapperPackage); 7 } else { 8 String resource = child.getStringAttribute("resource"); 9 String url = child.getStringAttribute("url"); 10 String mapperClass = child.getStringAttribute("class"); 11 if (resource != null && url == null && mapperClass == null) { 12 ErrorContext.instance().resource(resource); 13 InputStream inputStream = Resources.getResourceAsStream(resource); 14 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 15 mapperParser.parse(); 16 } else if (resource == null && url != null && mapperClass == null) { 17 ErrorContext.instance().resource(url); 18 InputStream inputStream = Resources.getUrlAsStream(url); 19 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); 20 mapperParser.parse(); 21 } else if (resource == null && url == null && mapperClass != null) { 22 Class<?> mapperInterface = Resources.classForName(mapperClass); 23 configuration.addMapper(mapperInterface); 24 } else { 25 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); 26 } 27 } 28 } 29 } 30 }
这段代码就是针对上面不同的配置进行解析加载,这里面还有一种url的情况,是通过url加载外部的资源,不常用。
我们先来看配置了package的情形,
第5行代码,获取包名,第6行代码调用configuration的addMappers方法,具体看下:
1 public void addMappers(String packageName) { 2 mapperRegistry.addMappers(packageName); 3 }
configuation中又调用了mapperRegistry的addMappers方法:
1 public void addMappers(String packageName) { 2 addMappers(packageName, Object.class); 3 }
在看下addMappers方法:
1 public void addMappers(String packageName, Class<?> superType) { 2 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); 3 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); 4 Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); 5 for (Class<?> mapperClass : mapperSet) { 6 addMapper(mapperClass); 7 } 8 }
上面这段代码主要就是利用ResolverUtil遍历package下面的所有class文件,看下find方法:
1 public ResolverUtil<T> find(Test test, String packageName) { 2 String path = getPackagePath(packageName); 3 4 try { 5 List<String> children = VFS.getInstance().list(path); 6 for (String child : children) { 7 if (child.endsWith(".class")) { 8 addIfMatching(test, child); 9 } 10 } 11 } catch (IOException ioe) { 12 log.error("Could not read package: " + packageName, ioe); 13 } 14 15 return this; 16 }
很明显,上面就是根据包名获取下面的class,然后调用addIfMatching方法:
1 protected void addIfMatching(Test test, String fqn) { 2 try { 3 String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.'); 4 ClassLoader loader = getClassLoader(); 5 if (log.isDebugEnabled()) { 6 log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); 7 } 8 9 Class<?> type = loader.loadClass(externalName); 10 if (test.matches(type)) { 11 matches.add((Class<T>) type); 12 } 13 } catch (Throwable t) { 14 log.warn("Could not examine class '" + fqn + "'" + " due to a " + 15 t.getClass().getName() + " with message: " + t.getMessage()); 16 } 17 }
这个方法主要就是根据前面传过来的class,去加载这个类,注意这边用的类加载器是线程上下文类加载器,然后判断这个类的类型是不是Object类,是的话,就放入matches这个set中,都遍历完成之后,符合要求的class就在这个set中,怎么获取呢?看下getClass方法:
1 public Set<Class<? extends T>> getClasses() { 2 return matches; 3 }
这边调用getClass方法时就返回了这个Set,我们接着看mapperRegistry的addMappers方法,
1 public void addMappers(String packageName, Class<?> superType) { 2 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); 3 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); 4 Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); 5 for (Class<?> mapperClass : mapperSet) { 6 addMapper(mapperClass); 7 } 8 }
按照上面的分析,第4行代码,我们已经得到的复合条件的class,下面就是遍历这些class,调用addMapper方法:
1 public <T> void addMapper(Class<T> type) { 2 if (type.isInterface()) { 3 if (hasMapper(type)) { 4 throw new BindingException("Type " + type + " is already known to the MapperRegistry."); 5 } 6 boolean loadCompleted = false; 7 try { 8 knownMappers.put(type, new MapperProxyFactory<T>(type)); 9 // It's important that the type is added before the parser is run 10 // otherwise the binding may automatically be attempted by the 11 // mapper parser. If the type is already known, it won't try. 12 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); 13 parser.parse(); 14 loadCompleted = true; 15 } finally { 16 if (!loadCompleted) { 17 knownMappers.remove(type); 18 } 19 } 20 } 21 }
这段代码首先判断这个class的类型是不是接口,不是则直接跳过,是的话,判断这个class对应的mapper文件是不是已经加载了,是的话,则抛出异常,否则看下第8行代码,将当前class类作为key,new一个MapperProxyFactory作为value放入knownMappers这个map中,这边这个MapperProxyFactory我们后面会详细介绍,看第12行代码,这边new了一个MapperAnnotationBuilder,看下第13行代码,调用了MapperAnnotationBuilder的parse方法:
1 public void parse() { 2 String resource = type.toString(); 3 if (!configuration.isResourceLoaded(resource)) { 4 loadXmlResource(); 5 configuration.addLoadedResource(resource); 6 assistant.setCurrentNamespace(type.getName()); 7 parseCache(); 8 parseCacheRef(); 9 Method[] methods = type.getMethods(); 10 for (Method method : methods) { 11 try { 12 // issue #237 13 if (!method.isBridge()) { 14 parseStatement(method); 15 } 16 } catch (IncompleteElementException e) { 17 configuration.addIncompleteMethod(new MethodResolver(this, method)); 18 } 19 } 20 } 21 parsePendingMethods(); 22 }
这边我们只要关注第4行代码,第5行代码之后都是针对注解做的处理,具体看下:
1 private void loadXmlResource() { 2 // Spring may not know the real resource name so we check a flag 3 // to prevent loading again a resource twice 4 // this flag is set at XMLMapperBuilder#bindMapperForNamespace 5 if (!configuration.isResourceLoaded("namespace:" + type.getName())) { 6 String xmlResource = type.getName().replace('.', '/') + ".xml"; 7 InputStream inputStream = null; 8 try { 9 inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); 10 } catch (IOException e) { 11 // ignore, resource is not required 12 } 13 if (inputStream != null) { 14 XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); 15 xmlParser.parse(); 16 } 17 } 18 }
这边首先再次确认xml文件有没有被加载过,没有的话继续加载,在当前目录下根据mapper的名字寻找xml文件,然后加载成流,之后交给XmlMapperBuilder处理,其实到这边,xml就已经加载完成了,后续都是处理一些使用了注解的东西,使用注解我们暂且不看。
我们再来看下方法三:
mapperClass不为空,走第三个if:
1 private void mapperElement(XNode parent) throws Exception { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 if ("package".equals(child.getName())) { 5 String mapperPackage = child.getStringAttribute("name"); 6 configuration.addMappers(mapperPackage); 7 } else { 8 String resource = child.getStringAttribute("resource"); 9 String url = child.getStringAttribute("url"); 10 String mapperClass = child.getStringAttribute("class"); 11 if (resource != null && url == null && mapperClass == null) { 12 ErrorContext.instance().resource(resource); 13 InputStream inputStream = Resources.getResourceAsStream(resource); 14 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 15 mapperParser.parse(); 16 } else if (resource == null && url != null && mapperClass == null) { 17 ErrorContext.instance().resource(url); 18 InputStream inputStream = Resources.getUrlAsStream(url); 19 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); 20 mapperParser.parse(); 21 } else if (resource == null && url == null && mapperClass != null) { 22 Class<?> mapperInterface = Resources.classForName(mapperClass); 23 configuration.addMapper(mapperInterface); 24 } else { 25 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); 26 } 27 } 28 } 29 } 30 }
第22行代码,根据名称加载mapper类,然后调用configuation的addMapper方法,这个我们上面已经分析过了,其实这种情况跟用package的很相似,可以说是一种特殊情况,package里面会有很多的mapper。
我们再看下方法一:
resource不为空:
1 if (resource != null && url == null && mapperClass == null) { 2 ErrorContext.instance().resource(resource); 3 InputStream inputStream = Resources.getResourceAsStream(resource); 4 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 5 mapperParser.parse(); 6 }
这边跟上面那个部分一样的,我们下一篇文章就主要看下这个解析过程。