1 概述
前面的文章我們針對Mybatis的初始化的整個過程做了一個分析,我們知道Mybatis的初始化其實就是將配置文件的配置信息最終轉換成Configuration對象的一個過程,現在我們就來看一看這個過程到底做了些什麼。
2 Configuration類詳解
我們知道XML配置文件最終都會解析成Configuration配置類,那麼這裏我們就來看看Configuraion這個類中到底包含什麼東西。(針對XML映射配置文件我們可以參考Mybatis官網:Mybatis)
查看mybatis官網,mybatis的xml配置文件主要包含以下內容:
- properties 屬性
- settings 設置
- typeAliases 類型別名
- typeHandlers 類型處理器
- objectFactory 對象工廠
- plugins 插件
- environments 環境
- databaseIdProvider 數據庫廠商標識
- mappers 映射器
2.1 基本屬性
我們來看一下源碼中定義的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<String> lazyLoadTriggerMethods = new HashSet<String>(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 <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300</a> (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<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<String>();
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
/*
* 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<String, String> cacheRefMap = new HashMap<String, String>();
3 BaseBuilder類詳解
上面我們分析了Configuration類的結構及作用,那麼我們繼續來看看將XML文件中的內容讀取出來設置到Configuration的工具BaseBuilder類。UML類圖如下:
3.1 BaseBuilder
直接上源碼,看看裏面包含的東西。
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註冊管理類,可以通過這個類來訪問類型處理器。
3.2 XMLConfigBuilder
XMLConfigBuilder用來解析mybatis配置文件,擁有的屬性如下:
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private XPathParser parser;
private String environment;
private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
4 其餘輔助類
4.1 XPathParser
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;//將元素轉換成爲節點信息
... ...
4.2 XMLMapperEntityResolver
XMLMapperEntityResolver的作用其實就是找到對應的dtd文件。我們mybatis的配置文件,不管是屬性配置文件或者是mapper配置文件都會發現有類似下面的內容:
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
其實每個和mybatis相關的配置文件都有內容,也就是有一個PUBLICID和SYSTEMID。
查看XMLMapperEntityResolver的源碼:
public class XMLMapperEntityResolver implements EntityResolver {
private static final Map<String, String> doctypeMap = new HashMap<String, String>();
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。
4.3 XNode
XNode其實就是對XML中的節點進行的封裝。
public class XNode {
private Node node;
private String name;
private String body;
private Properties attributes;
private Properties variables;
private XPathParser xpathParser;
... ...
5 配置文件解析
請看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文件的解析。
5 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 <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
實際上再addMapper方法內部調用了MapperRegistry的addMapper方法。
public <T> void addMapper(Class<T> 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<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}