上一篇我们了解到了MappedStatement类就是mapper.xml中的一个sql语句,而Configuration初始化的时候会加载所有的mapper接口类,而本篇再分析下是如何将mapper接口和xml进行绑定的。
先从上一篇的源码开始分析:
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));//加载指定的mapper接口 9 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);//? 10 parser.parse(); 11 loadCompleted = true; 12 } finally { 13 if (!loadCompleted) { 14 knownMappers.remove(type); 15 } 16 } 17 } 18 }
如果猜的没错的话,那么第9行和第10行就是解析xml并初始化MappedStatement对象的代码了。那么就先看看MapperAnnotationBuilder(mapper注解构造类)
MapperAnnotationBuilder类的构造方法如下:
1 public MapperAnnotationBuilder(Configuration configuration, Class<?> type) { 2 String resource = type.getName().replace('.', '/') + ".java (best guess)"; 3 this.assistant = new MapperBuilderAssistant(configuration, resource); 4 this.configuration = configuration; 5 this.type = type; 6 7 sqlAnnotationTypes.add(Select.class); 8 sqlAnnotationTypes.add(Insert.class); 9 sqlAnnotationTypes.add(Update.class); 10 sqlAnnotationTypes.add(Delete.class); 11 12 sqlProviderAnnotationTypes.add(SelectProvider.class); 13 sqlProviderAnnotationTypes.add(InsertProvider.class); 14 sqlProviderAnnotationTypes.add(UpdateProvider.class); 15 sqlProviderAnnotationTypes.add(DeleteProvider.class); 16 }
MapperAnnotationBuilder类的主要属性有:
1 private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();//sql语句上的注解 2 private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();//指定类指定方法上的sql语句注解 3 4 private Configuration configuration;//全局配置对象 5 private MapperBuilderAssistant assistant;//Mapper构建助手类,组装解析出来的配置,生成Cache、ResultMap以及MappedStatement对象 6 private Class<?> type;//解析的目标mapper接口的Class对象
MapperAnnotationBuilder构造方法就是给自己的属性进行了初始化,其中两个Set集合在初始化时表明了支持 @Select、@Insert、@Update、@Delete以及对应的Provider注解,本篇不再介绍,和xml配置的写法只是写法上不一样而已,实际的作用是一样,本文只分析xml这种用法。
由第一段代码可知,MapperAnnotationBuilder有一个parse方法,也是创建MappedStatement的方法,源码如下:
1 public void parse() { 2 String resource = type.toString();//mapper接口名,如:interface com.lucky.test.mapper.UserMapper 3 //Configuration中Set<String> loadedResources 保存着已经加载过的mapper信息 4 if (!configuration.isResourceLoaded(resource)) {//判断是否已经加载过 5 loadXmlResource();//加载XML资源 6 configuration.addLoadedResource(resource);//加载的resource添加到Configuration的loadedResources中 7 assistant.setCurrentNamespace(type.getName()); 8 parseCache(); 9 parseCacheRef(); 10 Method[] methods = type.getMethods();//遍历mapper的所有方法 11 for (Method method : methods) { 12 try { 13 // issue #237 14 if (!method.isBridge()) { 15 parseStatement(method); 16 } 17 } catch (IncompleteElementException e) { 18 configuration.addIncompleteMethod(new MethodResolver(this, method)); 19 } 20 } 21 } 22 parsePendingMethods(); 23 }
可见xml加载是通过调用loadXmlResource方法,源码如下:
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";//通过mapper接口名找到对应的mapper.xml路径 7 InputStream inputStream = null; 8 try { 9 //读取mapper.xml文件流 10 inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); 11 } catch (IOException e) { 12 // ignore, resource is not required 13 } 14 if (inputStream != null) { 15 //根据mapper.xml文件流创建XMLMapperBuilder对象 16 XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); 17 //执行parse方法 18 xmlParser.parse(); 19 } 20 } 21 }
这里又涉及到了XMLMapperBuilder类,很明显是mapper.xml构建者类,构造方法源码如下:
1 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) { 2 this(inputStream, configuration, resource, sqlFragments);//调用下面一个构造方法 3 this.builderAssistant.setCurrentNamespace(namespace); 4 } 5 6 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { 7 this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), 8 configuration, resource, sqlFragments);//调用下面一个构造方法 9 } 10 11 private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { 12 //对自己的属性进行赋值 13 super(configuration); 14 this.builderAssistant = new MapperBuilderAssistant(configuration, resource);//mapper构造者助手 15 this.parser = parser;//XML解析类 16 this.sqlFragments = sqlFragments;//sql片段集合 17 this.resource = resource;//mapper名称 18 }
那么再看下parse方法,源码如下:
1 public void parse() { 2 if (!configuration.isResourceLoaded(resource)) { 3 configurationElement(parser.evalNode("/mapper"));//从XML解析类中获取mapper标签的内容 4 configuration.addLoadedResource(resource);//将加载过的resource添加到Configuration中 5 bindMapperForNamespace();//绑定mapper到对应mapper的命名空间中 6 } 7 8 parsePendingResultMaps(); 9 parsePendingChacheRefs(); 10 parsePendingStatements(); 11 }
而解析xml的主要方法肯定就是configurationElement方法了,源码如下:
1 private void configurationElement(XNode context) { 2 try { 3 String namespace = context.getStringAttribute("namespace");//从xml中获取namespace标签内容 4 if (namespace == null || namespace.equals("")) { 5 throw new BuilderException("Mapper's namespace cannot be empty"); 6 } 7 builderAssistant.setCurrentNamespace(namespace); 8 cacheRefElement(context.evalNode("cache-ref"));//获取cache-ref标签内容 9 cacheElement(context.evalNode("cache"));//获取cache标签内容 10 parameterMapElement(context.evalNodes("/mapper/parameterMap"));//获取parameterMap内容 11 resultMapElements(context.evalNodes("/mapper/resultMap"));//获取resultMap内容 12 sqlElement(context.evalNodes("/mapper/sql"));//获取sql标签内容 13 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析获取各种sql语句标签内容 14 } catch (Exception e) { 15 throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); 16 } 17 }
很显然这个方法就是将xml中所有可能存在的标签都进行了解析处理,本文就不再详细分析,留得青山在不愁没柴烧。既然知道了xml的各个标签是在哪里解析的,就不愁不知道具体是怎么解析的了。分析到这里,似乎还是没有看到MappedStatement的身影,别急,让我们回到MapperAnnotationBuilder
的parse方法,先是通过loadXmlResource方法进行xml文件的解析加载,然后添加已经加载过的resource到Configuration中,然后解析二级缓存的配置,然后就是获取mapper接口的所有方法,遍历解析:
1 loadXmlResource(); 2 configuration.addLoadedResource(resource); 3 assistant.setCurrentNamespace(type.getName()); 4 parseCache(); 5 parseCacheRef(); 6 Method[] methods = type.getMethods(); 7 for (Method method : methods) { 8 try { 9 // issue #237 10 if (!method.isBridge()) { 11 parseStatement(method); 12 } 13 } catch (IncompleteElementException e) { 14 configuration.addIncompleteMethod(new MethodResolver(this, method)); 15 } 16 }
上面红色标注的方法是获取方法的属性,然后将xml中配置的标签进行一一匹配,源码如下:
1 void parseStatement(Method method) { 2 Class<?> parameterTypeClass = getParameterType(method); 3 LanguageDriver languageDriver = getLanguageDriver(method); 4 SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); 5 if (sqlSource != null) { 6 Options options = method.getAnnotation(Options.class); 7 final String mappedStatementId = type.getName() + "." + method.getName(); 8 Integer fetchSize = null; 9 Integer timeout = null; 10 StatementType statementType = StatementType.PREPARED; 11 ResultSetType resultSetType = ResultSetType.FORWARD_ONLY; 12 SqlCommandType sqlCommandType = getSqlCommandType(method); 13 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; 14 boolean flushCache = !isSelect; 15 boolean useCache = isSelect; 16 17 KeyGenerator keyGenerator; 18 String keyProperty = "id"; 19 String keyColumn = null; 20 if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { 21 // first check for SelectKey annotation - that overrides everything else 22 SelectKey selectKey = method.getAnnotation(SelectKey.class); 23 if (selectKey != null) { 24 keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver); 25 keyProperty = selectKey.keyProperty(); 26 } else if (options == null) { 27 keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); 28 } else { 29 keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); 30 keyProperty = options.keyProperty(); 31 keyColumn = options.keyColumn(); 32 } 33 } else { 34 keyGenerator = new NoKeyGenerator(); 35 } 36 37 if (options != null) { 38 if (FlushCachePolicy.TRUE.equals(options.flushCache())) { 39 flushCache = true; 40 } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) { 41 flushCache = false; 42 } 43 useCache = options.useCache(); 44 fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348 45 timeout = options.timeout() > -1 ? options.timeout() : null; 46 statementType = options.statementType(); 47 resultSetType = options.resultSetType(); 48 } 49 50 String resultMapId = null; 51 ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class); 52 if (resultMapAnnotation != null) { 53 String[] resultMaps = resultMapAnnotation.value(); 54 StringBuilder sb = new StringBuilder(); 55 for (String resultMap : resultMaps) { 56 if (sb.length() > 0) { 57 sb.append(","); 58 } 59 sb.append(resultMap); 60 } 61 resultMapId = sb.toString(); 62 } else if (isSelect) { 63 resultMapId = parseResultMap(method); 64 } 65 66 assistant.addMappedStatement( 67 mappedStatementId, 68 sqlSource, 69 statementType, 70 sqlCommandType, 71 fetchSize, 72 timeout, 73 // ParameterMapID 74 null, 75 parameterTypeClass, 76 resultMapId, 77 getReturnType(method), 78 resultSetType, 79 flushCache, 80 useCache, 81 // TODO gcode issue #577 82 false, 83 keyGenerator, 84 keyProperty, 85 keyColumn, 86 // DatabaseID 87 null, 88 languageDriver, 89 // ResultSets 90 options != null ? nullOrEmpty(options.resultSets()) : null); 91 } 92 }