加载配置文件
调用Resources类的静态方法getResourceAsStream,将配置文件的地址传入,完成配置文件的加载。
java
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
Resources类中 调用重载方法 传入配置文件的地址 ClassLoader传了null
使用ClassLoaderWrapper来对配置文件进行了加载
java
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
//classLoaderWrapper 类加载器包装器
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
//如果加载不到指定的资源,抛出异常。
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
ClassLoaderWrapper:类加载器包装器,用来简化类加载器获取、加载资源时的判断、异常处理等操作。
java
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
//参数指定的类加载器
classLoader,
//系统指定的默认类加载器
defaultClassLoader,
//当前线程绑定的类加载器
Thread.currentThread().getContextClassLoader(),
//当前类使用的类加载器
getClass().getClassLoader(),
systemClassLoader};
}
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
//遍历ClassLoader数组,通过指定或者默认的classLoader来读取文件
for (ClassLoader cl : classLoader) {
if (null != cl) {
InputStream returnValue = cl.getResourceAsStream(resource);
//某些类加载器 加载时需要在资源地址之前加一个"/",这里是一个重试机制
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
拓展:包装器模式
基本思想
在上面的源码中,关于类加载器相关的代码 封装了一个包装器 ClassLoaderWrapper,也称为装饰器模式,是一种结构型设计模式。
它允许在不改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装类来实现的,包装类中包含原始对象的引用,以及提供一些额外的功能。
或者规范行为的是一个抽象类,包装器通过继承的方式 重写方法来增强逻辑。
包装器模式的使用方式 并没有一个标准的模板,多种使用方式 不管是接口、抽象类等,都是引用了包装器的思想,实现方式上大同小异。
Mybatis应用
例如 在Mybatis加载配置文件时,类加载器是一个抽象类
java
public abstract class ClassLoader {}
这个抽象类有若干子类,这里是原本的类加载器的实现逻辑。
而类加载器的包装器 就是使用了成员变量 来保存这个抽象类的实例,区别在于这里 包装器(ClassLoaderWrapper)并未去直接继承类加载器(ClassLoader),而是只是封装了一些加载资源时,加载器的获取、加载过程、判断、异常处理等,用来简化使用。个人理解 这也是包装器的一种使用方式,延续了包装器模式的思想,但并不一定实现方式就有标准答案和模板。
java
public class ClassLoaderWrapper {
//默认类加载器
ClassLoader defaultClassLoader;
//系统类加载器
ClassLoader systemClassLoader;
//...
}
解析配置文件
通过调用SqlSessionFactoryBuilder的build方法,来完成配置文件的解析,创建SqlSessionFactory工厂对象。
java
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSessionFactoryBuilder中存在多个build方法
java
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);
}
public SqlSessionFactory build(InputStream inputStream, String environment,
Properties properties) {
//...
}
最后多个重载的方法都会调用到下面这个三个参数的build方法
java
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//1.创建XPathParser解析器对象 根据inputStream解析成了document对象 解析xml配置文件 2.创建了全局配置对象 Configuration
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.
}
}
}
最后的build方法中,创建了DefaultSqlSessionFactory进行返回,同时将全局配置对象Configuration 继续进行了传递。
java
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
构造XMLConfigBuilder
java
public class XMLConfigBuilder extends BaseBuilder {
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//XPathParser 基于Java 的 XPath解析器,用于解析Mybatis中的配置文件
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//Mybaits全局配置对象:Configuration,并通过TypeAliasRegistry注册了一些Mybatis内部类相关的别名
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
}
XMLConfigBuilder的构造器中执行了下面的逻辑
- 创建了XPathParser解析器,用来解析Mybatis中的配置文件
java
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//XPathParser 基于Java 的 XPath解析器,用于解析Mybatis中的配置文件
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
- 在XPathParser的构造器中 将传入的inputStream构建成了一个Document文档对象
java
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
//解析xml文档为Document对象
this.document = createDocument(new InputSource(inputStream));
}
- XMLConfigBuilder调用了父类的构造方法,创建了Configuration对象,也就是Mybatis的全局配置对象。
java
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//Mybaits全局配置对象:Configuration,并通过TypeAliasRegistry注册了一些Mybatis内部类相关的别名
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
- XMLConfigBuilder的父类,这里完成了全局配置对象的赋值。
java
public abstract class BaseBuilder {
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
}
- Configuration的构造器中,声明了一系列的别名,这里是可以直接在xml文件中配置别名的关键
java
public class 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);
//...
}
//...
}
XMLConfigBuilder.parse()
java
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//通过XPATH解析器 解析configuration根节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parser.evalNode("/configuration") 去解析了配置文件,将根标签configuration里面的内容封装为了一个XNode对象
java
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
然后通过调用parseConfiguration方法,去解析XNode里面所有的配置项,也就是根标签configuration里面所有的子标签。
java
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"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
environmentsElement()方法
主要用来解析environments标签 封装数据源信息
通过指定的environment,创建了事务工厂对象、数据源对象,然后通过Environment.Builder 的 build方法,创建了Environment对象,封装到了全局配置对象Configuration中
java
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
//判断 如果调用build方法时,没有指定environment。这里获取到配置的默认值
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
//遍历中 判断 找到指定的Environment 进行解析
if (isSpecifiedEnvironment(id)) {
//创建事务工厂对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//创建数据源工厂对象
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//获取到数据源
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//通过Environment.Builder创建了Environment对象,封装到了全局配置对象Configuration中
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
mapperElement()方法
java
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//如果配置的是package 代表是mapper代理方式
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//反之 是单个mapper配置方式
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);
try(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);
try(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.");
}
}
}
}
}
上面17-34行,是获取到映射文件的配置方式,根据不同的方式 通过不同的类来进行解析。下面就重点说一下resource配置方式,对应的解析类就是XMLMapperBuilder
构建XMLMapperBuilder
这部分跟构建XMLConfigBuilder是基本相似的
java
XMLMapperBuilder mapperParser = new XMLMapperBuilder(
inputStream, configuration, resource, configuration.getSqlFragments());
public class XMLMapperBuilder extends BaseBuilder {
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
}
XMLMapperBuilder.parse()
java
public void parse() {
//首先进行重复加载的判断
if (!configuration.isResourceLoaded(resource)) {
//通过configurationElement方法来解析mapper标签
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
configurationElement方法中,对不同的标签来进行解析
java
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
//会对namespace有一个判断,如果namespace为空 会抛出异常
if (namespace == null || namespace.isEmpty()) {
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"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
buildStatementFromContext()方法
主要看一下buildStatementFromContext方法,也就是用来解析select、insert、update、delete四个增删改查标签的方法。
java
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
buildStatementFromContext方法:通过的XMLStatementBuilder的parseStatementNode方法 来进行进一步的解析。
java
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);
}
}
}
parseStatementNode()方法
该方法 完成对select、insert、update、delete四个增删改查标签的解析。
java
public void parseStatementNode() {
//获取到sql的ID
String id = context.getStringAttribute("id");
//获取到数据源的ID
String databaseId = context.getStringAttribute("databaseId");
//关于数据源ID的判断
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//获取到标签名称:select
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());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
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))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//创建了SqlSource,来完成 解析SQL、封装SQL语句、处理入参信息
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//构建MappedStatement对象,过程中完成了statementId的拼接
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}