前面的文章我们针对Mybatis的初始化的整个过程做了一个分析,我们知道Mybatis的初始化其实就是将配置文件的配置信息最终转换成Configuration对象的一个过程,现在我们就来看一看这个过程到底做了些什么。
我们知道XML配置文件最终都会解析成Configuration配置类,那么这里我们就来看看Configuraion这个类中到底包含什么东西。(针对XML映射配置文件我们可以参考Mybatis官网:Mybatis)
查看mybatis官网,mybatis的xml配置文件主要包含以下内容:
我们来看一下源码中定义的Configuration的基本属性。
/**
* @author Clinton Begin
*/
public class Configuration {
//环境变量
protected Environment environment;
//setting设置,可以改变mybatis的运行时行为
protected boolean safeRowBoundsEnabled = false;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase = false;
protected boolean aggressiveLazyLoading = true;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected String logPrefix;
protected Class extends Log> logImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set lazyLoadTriggerMethods = new HashSet(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
//properties属性
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see Issue 300 (google code)
*/
protected Class> configurationFactory;
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//mapper文件解析后存放于此
protected final Map mappedStatements = new StrictMap("Mapped Statements collection");
protected final Map caches = new StrictMap("Caches collection");
protected final Map resultMaps = new StrictMap("Result Maps collection");
protected final Map parameterMaps = new StrictMap("Parameter Maps collection");
protected final Map keyGenerators = new StrictMap("Key Generators collection");
protected final Set loadedResources = new HashSet();
protected final Map sqlFragments = new StrictMap("XML fragments parsed from previous mappers");
protected final Collection incompleteStatements = new LinkedList();
protected final Collection incompleteCacheRefs = new LinkedList();
protected final Collection incompleteResultMaps = new LinkedList();
protected final Collection incompleteMethods = new LinkedList();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map cacheRefMap = new HashMap();
上面我们分析了Configuration类的结构及作用,那么我们继续来看看将XML文件中的内容读取出来设置到Configuration的工具BaseBuilder类。UML类图如下:
直接上源码,看看里面包含的东西。
public abstract class BaseBuilder {
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
... ...
BaseBuilder总共包含了三个属性,这三个属性的作用分别是:
(1)Configuration:XML配置类。
(2)TypeAliasRegistry:类型别名注册器,类型别名的访问入口,可以通过TypeAliasRegistry来获取和添加类型别名。
(3)TypeHandlerRegister:typeHandler注册管理类,可以通过这个类来访问类型处理器。
XMLConfigBuilder用来解析mybatis配置文件,拥有的属性如下:
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private XPathParser parser;
private String environment;
private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
XPathParser是用来解析XML文件的,包括检测XML文件的格式。XPathParser拥有的属性:
public class XPathParser {
private Document document;// 用来解析xml文件
private boolean validation;//验证
private EntityResolver entityResolver;//通过key查找dtd文件
private Properties variables;
private XPath xpath;//将元素转换成为节点信息
... ...
XMLMapperEntityResolver的作用其实就是找到对应的dtd文件。我们mybatis的配置文件,不管是属性配置文件或者是mapper配置文件都会发现有类似下面的内容:
其实每个和mybatis相关的配置文件都有内容,也就是有一个PUBLICID和SYSTEMID。
查看XMLMapperEntityResolver的源码:
public class XMLMapperEntityResolver implements EntityResolver {
private static final Map doctypeMap = new HashMap();
private static final String IBATIS_CONFIG_PUBLIC = "-//ibatis.apache.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String IBATIS_CONFIG_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-config.dtd".toUpperCase(Locale.ENGLISH);
private static final String IBATIS_MAPPER_PUBLIC = "-//ibatis.apache.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String IBATIS_MAPPER_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_CONFIG_PUBLIC = "-//mybatis.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_CONFIG_SYSTEM = "http://mybatis.org/dtd/mybatis-3-config.dtd".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_MAPPER_PUBLIC = "-//mybatis.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_MAPPER_SYSTEM = "http://mybatis.org/dtd/mybatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
可以发现这里定义了大量的PUBLICID和SYSTEMID。
XNode其实就是对XML中的节点进行的封装。
public class XNode {
private Node node;
private String name;
private String body;
private Properties attributes;
private Properties variables;
private XPathParser xpathParser;
... ...
请看SqlSessionFactoryBuilder中的build方法。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//通过传入的配置文件的输入流来创建XMLConfigBuilder
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//通过调用XMLConfigBuilder的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.
}
}
}
//传入Configuration来创建SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
从上面的源码我们可以看出,最终构建Configuration的重担落在了XMLConfigBuilder的parse()方法上面。
public Configuration parse() {
//XMLConfigBuilder创建后仅仅能够被使用一次,如果再次使用则会抛出异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
//设置XMLConfigBuilder已经使用过
parsed = true;
//解析配置文件(将在下面详细分析这一步)
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
这里首先调用了XPathParser的evalNode方法,来将XPathParser的Document属性转换成XNode对象。
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
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);
}
这里针对evalNode我们就不深入研究了。
接着,获得了XNode对象,然后将XNode对象传入parseConfiguration方法。
private void parseConfiguration(XNode root) {
try {
//下面的每个方法其实就是对XNode中包含的不同节点内容进行解析,包含了属性、类别名等节点
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(root.evalNode("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);
}
}
接下来我们来看一看具体是怎样去解析这些属性的。
(1)properties 属性
将从configuration节点转换成的XNode节点中获取到的properties节点的XNode对象传入propertiesElement函数。
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
/**
*获取properties节点的子节点,其实下面就是一些name和value属性节点,
*从这些子节点来初始properties。我们知道properties继承自HashTable。
*/
Properties defaults = context.getChildrenAsProperties();
//获取节点的resource属性
String resource = context.getStringAttribute("resource");
//获取节点的url属性
String url = context.getStringAttribute("url");
//节点的resource属性和url属性不能够同时存在
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
//读取resource属性或者url属性中的properties值,并且存入properties中
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
//将properties属性值存入configuration中
configuration.setVariables(defaults);
}
}
我们来看一下getChildernAsProperties函数做了什么。
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
//循环遍历子节点
for (XNode child : getChildren()) {
//获取name和value属性
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
//存入properties中
properties.setProperty(name, value);
}
}
return properties;
}
其实getChildernAsProperties做的事很简单就是解析下图标识的那一坨数据。
(2)typeAliases 类型别名
这里和properties节点的解析相同,同样是将从configuration节点下面获取到的typeAliases子节点作为参数传入typeAliasesElement函数。
private void typeAliasesElement(XNode parent) {
if (parent != null) {
//遍历节点下面的子节点
for (XNode child : parent.getChildren()) {
//如果子节点的名称为package,则包下面的左右类为类别名
if ("package".equals(child.getName())) {
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) {
//这里需要注意,如果没有执行类别名则用类的smpleName作为别名。
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
至此,我们分析了两个属性的解析,至于其余的都和这两个大同小异,接下来我们重点分析下mapper文件的解析。
针对mapper文件的解析和上面提到的properties和typeAlias的解析开始都是类似的。获取到configuration节点下面的mappers子节点的XNode对象,作为参数传入mapperElement函数。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//遍历mappers节点下面的子节点(即mapper节点)
for (XNode child : parent.getChildren()) {
//如果子节点的名称为package,将包内的映射器接口实现全部注册为映射器
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
//通过包名添加映射器
configuration.addMappers(mapperPackage);
} else {
/**
* 分别获取子节点的resource、url和class属性,这里需要注意的是前两个属性对应mapper文
* 件,而后一个属性对应class接口,并且一个mappers节点仅仅支持一种映射器类型
*/
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//resource提供的映射器
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//将resource对应的xml文件转换成输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//解析mapper映射文件,前面我们也提到,XMLMpperBuilder是用于解析mapper文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
//url提供的映射器
} 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();
//class提供的映射器
} else if (resource == null && url == null && mapperClass != null) {
Class> mapperInterface = Resources.classForName(mapperClass);
//通过接口解析mapper文件
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
针对上面的源码,我们具体分析下以下几点:
(1)通过包名添加mapper映射器
将获取到的包名作为参数传入Configuration对象的addMappers方法。
(2)通过resource解析映射器
(3)class提供的映射器
获取到class对应的class对象后,直接传入Configuration的addMapper()方法。
public void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
实际上再addMapper方法内部调用了MapperRegistry的addMapper方法。
public void addMapper(Class type) {
//此方法仅仅用于接口
if (type.isInterface()) {
//判断接口对应的Mapper是否已经注册过,防止重复注册
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//注册类和相应的代理工厂,关于MapperProxyFactory的作用我们将在后面的文章中分析
knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}