前面我们已经知道了Configuration 是在实例化XMLConfigBuilder的时候在构造函数里面一起实例化的,如下;
//外层调用的public的构造函数
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//内部private的构造函数,被上述调用
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//此处实例化Configuration
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
于是,我们又有了一个疑问,刚刚加载的stream流并没有传入这个new Configuration()里面,而是在外层构造函数里面,实例化XPathParser的时候传入了,此刻我猜想,这个new Configuration()里面肯定没有加载配置文件的内容,我们来一起看一下下面代码
public class Configuration {
......
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
......
//省略很多属性和方法
......
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);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
......
}
public class TypeAliasRegistry {
private final Map<String, Class<?>> typeAliases = new HashMap<>();
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
......
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
typeAliases.put(key, value);
}
......
}
在上述代码中,我们看到Configuration 构造函数里面全部都在一件事情,就是注册别名而且Configuration里面typeAliasRegistry属性在实例化的时候也注册了很多默认的别名。正如我们所猜想一样,实例化Configuration并没有加载配置文件,而仅仅只做了注册一系列默认的别名。但是我现在想找到我们加载的配置文件在何处解析成了Configuration里面的属性呢。我们回头看看刚刚所带疑问的XPathParser,因为实例化这个对象的时候是传入了流的。
public class XPathParser {
private final Document document;
private boolean validation;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath;
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
//代码1
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
//代码2
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
// NOP
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
//省略很多方法
......
}
在调用的XPathParser构造函数里面,调用两个方法,一个通用的方法(代码1)分别填充了validation、entityResolver、variables和xpath(此处xpath是用来解析xml文件的)。而在另一个方法(代码2)处则是把传入的stream流转成了Document对象。这里转成Document和实例化xpath,让我们联想到了,后续肯定是需要通过xpath来解析这个Document了。因为XPathParser里面存储了xpath和Document,并且XMLConfigBuilder里面存储了XPathParser和Configuration。就在此处Configuration正式和加载的文件关联起来了,此刻XMLConfigBuilder已经实例化完成了,但是我们还是没看到解析的方法,我们只能回到SqlSessionFactoryBuilder.build 方法了
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
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 class XMLConfigBuilder extends BaseBuilder {
......
public Configuration parse() {
//判断是否已经解析过了,避免多次解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//解析之前设置解析标识为已经解析过
parsed = true;
//parser.evalNode("/configuration") 通过xpath的方式,解析从根节点开始的configuration节点
//解析文档成为configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
......
}
在build方法中,我们发现实例化XMLConfigBuilder 后,直接调用了一个parse方法,在方法里面又通过XPathParser调用evalNode,而实际上这个方法里面就是调用了xpath.evaluate,而这个方法正是通过xpath的方式解析最开始加载并且储存在XPathParser里面的document,返回一个XNode,在XNode 里面包含了XPathParser和通过xpath解析的根结点Node,实际上后面很多地方都会通过返回的XNode里面包含的XPathParser调用evalNode的方式来解析根结点Node,后面我们会详细看到
//外层调用
//代码a
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
//代码b
//被代码a调用
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
//代码c
//被代码b调用
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
后面我们来具体看看parseConfiguration方法是如何解析的
总结:
1:在build方法调用的时候,实例化了XMLConfigBuilder
2:在XMLConfigBuilder里面实例化XPathParser和Configuration
3:XPathParser实际上是包含了一个xpath和document的实例
4:使用XPathParser解析文档的时候是通过xpath表达式的方式,使用内部的xpath加上传入的表达式来解析内部的document
5:在解析configuration之前会先判断是否已经做过解析,避免多次解析文档
6:解析完根结点返回XNode后,里面包含了XPathParser和已经解析过的根结点Node,后续解析都是基于在根结点Node上进行的
【个人阅读源码,能力有限,望各方大佬有幸瞧见后能指点一二,如果有错误之处,我看到后会立马改正,谢谢🙏🙏🙏】