Mybatis之Configuration初始化(配置文件.xml的解析)

时间:2022-08-13 14:52:29

源码解读第一步我觉着应该从Mybatis如何解析配置文件开始。

1.先不看跟Spring集成如何解析,先看从SqlSessionFactoryBuilder如果解析的。

 String resouce = "conf.xml";
InputStream is = Resources.getResourceAsStream(resouce); // 构建sqlSession工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

SqlSessionFactoryBuilder

   public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
} public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
} public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
//上面那么多同名不同参的方法最后都会进入这个方法
   //支持传入Enviromment.properties实际上是支持动态传入覆盖全局配置xml的内容
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //因为从下面的Build方法可以看出 parser.parse()后Configuration就初始化好了
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
   public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
   }

真正初始化Configuration的类是XMLConfigBuilder

   public Configuration parse() {
    //这一块可以看出来 全局Configuration只会初始化一次,实例化时候是false
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//获取配置文件整个/configuration节点内容
private void parseConfiguration(XNode root) {
try {
Properties settings = settingsAsPropertiess(root.evalNode("settings")); //初始化配置文件中全局变量
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));//初始化xml中的配置文件,同时讲其中的变量保存起来,因为可能其他地方引用
loadCustomVfs(settings); //加载自定义的VFS实现类? 这个什么用?
typeAliasesElement(root.evalNode("typeAliases")); //加载typeAliases别名初始化
pluginElement(root.evalNode("plugins")); //加载插件,实际上就是拦截器
objectFactoryElement(root.evalNode("objectFactory"));// //加载自定义的对象工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //加载自定义的处理驼峰方式的key的处理器
reflectionFactoryElement(root.evalNode("reflectionFactory")); //加载自定义的反射器 没用过?
settingsElement(settings); //将setting的属性,设置到Configuration对象属性中
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers")); //初始化类型处理器
mapperElement(root.evalNode("mappers")); //处理mappers节点内容,实际上就是初始化MaperStatement
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

因为大部分方法都比较简单,我这里只介绍几个我认为比较重要的。

① typeAliasesElement(root.evalNode("typeAliases")); //加载typeAliases别名初始化

   private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) { //配置的是包名 扫描包里所有的类。放入的key默认是注解的key
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
         //实际上就是存在了TypeAliasRegistry类的一个私有map里面。
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
 public class TypeAliasRegistry {

   private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
  。。。。。。
}

② pluginElement(root.evalNode("plugins")); 加载插件,便于后期理解拦截器原理

   private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
       //获取interceptor 这里resolveClass实际上就是到TypeAliasResitstry里面找一下,找到了获取class,没有直接用class去反射回去对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
       //获取对象后调用configuration方法。
configuration.addInterceptor(interceptorInstance);
}
}
}

Configuration

  public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}

InterceptorChain

   public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}

这一块就是放Configuration的拦截器链里面添加拦截器。这一块现在知道是在这时候添加的就好了。后面介绍Mybatis的拦截器的时候深入了解。

③ mapperElement(root.evalNode("mappers")); //处理mappers节点内容,实际上就是初始化MaperStatement

   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");
        //xml文件中mapper节点,配置resource,url是执行的mapper.xml文件的位置,所以现在只分析这一块。
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(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) {
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.");
}
}
}
}
}

可以看出来resource和url都是通过XMLMapperBuilder来解析的。下面来看下parse方法。

   private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
//前面还有个构造器,是根据传入的inputstream构造XPathParser,parser可以拿到resouce里面的内容
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
} public void parse() {
if (!configuration.isResourceLoaded(resource)) { 是否加载过改文件,
configurationElement(parser.evalNode("/mapper")); 来解析mapper文件内容
configuration.addLoadedResource(resource); 添加加载记录
bindMapperForNamespace();往configration添加命名空间的代理对象
} parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
   private void configurationElement(XNode context) { //解析mapper.xml子节点的内容
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap")); //解析resultMap 很复杂,后面单独解读
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete")); //初始化这几个类型节点的内容
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
   private void buildStatementFromContext(List<XNode> list) {
因为会有多个节点,所以是一个List<XNode>
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
} private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

从上面可以看出来最终是由XMLStatementBuilder来解析我们的写的sql部分。

   public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
} Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
    //获取语言驱动, 我们自定义的语言驱动也是从这里读取的, 同时参数处理器也是从驱动定义的,自定义参数处理器也可以在自定义语言驱动里面去实现
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
//根据前面的语言驱动去获取对应的SqlSource。SqlSource有两种,一种是处理${}一种是处理#{}
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
53   获取节点所有属性内容,调用builderAssistant,实现是MapperBuilderAssistant 在XmlMapperBuilder构造器初始化时候就制定了
54 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
55 fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
56 resultSetTypeEnum, flushCache, useCache, resultOrdered,
57 keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
   public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) { if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
} id = applyCurrentNamespace(id, false); //把id加上namespace+"."
boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resulSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
} MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
MappedStatement.Builder 是MappedStatement的内部类。里面有MappedStatement的引用,所有方法都设置内部引用mappedStatement的属性,并返回自身,所以这一块就是类似于setparam()
最终通过build方法 返回对象。 然后调用Congifuration.addMappedStatement保存到Congifuration对象里面。
   public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}

到此Congifuration对象初始话完事了...  全局只有一个。

回过头来刚才还有怎么获取的语言驱动,和MappedStatement的SqlSource怎么初始化的没说。

①获取语言驱动:

16     String lang = context.getStringAttribute("lang");
17 LanguageDriver langDriver = getLanguageDriver(lang);
   private LanguageDriver getLanguageDriver(String lang) {
Class<?> langClass = null;
if (lang != null) {
     // 也是到别名注册器里面去找
langClass = resolveClass(lang);
}
构造助手类去获取的。下面看MapperBuilderAssistant类怎么实现的
return builderAssistant.getLanguageDriver(langClass);
}
   // 从configuration里面找,也有个语言注册器,然后还有个默认的,我们平时不指定的时候都是默认的。 
  public LanguageDriver getLanguageDriver(Class<?> langClass) {
if (langClass != null) {
configuration.getLanguageRegistry().register(langClass);
} else {
      
langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
}
return configuration.getLanguageRegistry().getDriver(langClass);
}

那语言注册器是什么时候注册的呢。 来看Configuration的构造器

   public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class); typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
//在最下面 看到了吗。默认的是XMLLanguageDruiver,然后把Raw也注册进去了
31 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
32 languageRegistry.register(RawLanguageDriver.class);
}

②MappedStatement的SqlSource怎么初始化

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
因为我们一般都不配置lang属性,所以看默认的XMLLanguageDriver怎么实现的。
   @Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
XMLScriptBuilder 
   public SqlSource parseScriptNode() {
//主要在这里面判断是否有Dynamic标签 是就是就是${}
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
   List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
//获取每一个node,然后判断是否是isDynamic();
TextSqlNode textSqlNode = new TextSqlNode(data);
9 if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
   public boolean isDynamic() {
//这一块就是典型的面向接口编程, checker接口的实现类有很多, 在parser.parse一定会调用checker实现类的方法。
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
} private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
 public String parse(String text) {
final StringBuilder builder = new StringBuilder();
final StringBuilder expression = new StringBuilder();
if (text != null && text.length() > 0) {
char[] src = text.toCharArray();
int offset = 0;
// search open token
int start = text.indexOf(openToken, offset);
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
expression.setLength(0);
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//这一块实际上就是遍历node文本找到openToken和cloaseToken中间的变量名字传给handler处理
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
}
return builder.toString();
}

下面看下DynamicCheckerTokenParser怎么处理?

   private static class DynamicCheckerTokenParser implements TokenHandler {

     private boolean isDynamic;

     public DynamicCheckerTokenParser() {
// Prevent Synthetic Access
} public boolean isDynamic() {
return isDynamic;
}
  //实现上什么也没做,因为内部类就把父类的变量设置为true,告诉父类这个Node含有DynamicTag就好了。
@Override
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
}

这样就好了,如果有标签SqlSource就是DynamicSqlSourcr() 没有就是RawSqlSource():

这里在额外讲一下,TextSqlNode还有一个内部类BindingTokenParser也实现了Tokenhandler接口看下他是怎么实现的,就提前知道了参数值是怎么具体获取的。

   private static class BindingTokenParser implements TokenHandler {

     private DynamicContext context;
private Pattern injectionFilter; public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
this.context = context;
this.injectionFilter = injectionFilter;
} @Override
public String handleToken(String content) {
Object parameter = context.getBindings().get("_parameter");
if (parameter == null) {
context.getBindings().put("value", null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
context.getBindings().put("value", parameter);
}
//根据传入的key到parameter里面找 这样就把变量替换掉了。 后面讲sql执行过程再详细讲解
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
checkInjection(srtValue);
return srtValue;
} private void checkInjection(String value) {
if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
}
}
}