首先分析源碼,我們需要知道其使用在入手分析其源碼。
MyBatis的使用:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlogById(1);
session.close();
從原生的MyBatis使用上看,其使用分為了六部分,分別是:
- 文件讀取成流
- 獲取一個SqlSessionFactory工廠類,在這一步驟里,完成了解析配置文件,包括mybatis-config.xml和Mapper映射器文件。
- 通過SqlSessionFactory獲得一個SqlSession
- 根據(jù)SqlSeesion的實例獲取一個Mapper對象
- 使用mapper對象調(diào)用接口
- 關(guān)閉session
下面就按這些步驟去分析源碼,分析這些步驟中隱藏的操作。
讀取文件成流就不說了,直接進入第二步獲取工廠類時解析配置文件分析。
首先,在分析其源碼之前我們應該關(guān)心的內(nèi)容:其解析過程做了什么,產(chǎn)生了哪些對象,這些對象時放到哪里的。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
首先
我們需要了解SqlSessionFactory 是由SqlSessionFactoryBuilder()的build()方法得到,所以直接進入buil()方法中
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 用于解析 mybatis-config.xml,同時創(chuàng)建了 Configuration 對象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析XML,最終返回一個 DefaultSqlSessionFactory
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.
}
}
}
從方法里可以看出,這里創(chuàng)建了一個XMLConfigBuilder對象,這個對象時BaseBuilder的一個子類,是用來專門解析全局配置文件的,可以看到BaseBuilder的類圖
- XMLMapperBuilder:解析Mapper映射器文件的
- XMLStatementBuilder:解析增刪改查標簽的
- XMLScriptBuilder:解析動態(tài)SQL
- SqlSourceBuilder:將SQL語句中的#{xxx}替換成占位符 ?和將占位符 ?對應的屬性信息一起構(gòu)建成到ParameterMapping中,以便在后續(xù)ParameterHandler真正地參數(shù)賦值
- ParameterMappingTokenHandler 是SqlSourceBuilder的靜態(tài)內(nèi)部類,是為參數(shù)構(gòu)建 ParameterMapping的。
這些后面分析源碼的時候都會看到
好了,言歸正傳
我們先來看看創(chuàng)建XMLConfigBuilder對象時做了些什么。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
XPathParser 類中持有一個Document對象,使我們的XML文件,還有一個XPath對象,是定位信息的方法,是java提供的,XPathParser解析出的元素用一個XNode對象存儲。
XMLConfigBuilder的構(gòu)造方法里可以看出new Configuration()創(chuàng)建了我們重要的對象Configuration,我們都知道它是全局唯一用來存放MyBatis行為信息的。
我們來看看創(chuàng)建Configuration對象時都做了些什么。
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
...
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<>();
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
...
其中一部分源碼就不全部貼上了,自己可以去仔細看看,在這個configuration類里可以看到它有很多的屬性,并且有些屬性是直接賦值了,所以我們就明白了在配置文件中的一些屬性為什么不需要我們?nèi)ピO(shè)置了,因為在其內(nèi)部已經(jīng)幫我們設(shè)置了默認值,如果我們自己設(shè)置了值得話,在后面的配置文件中將會解析出來再進行替換。
還有里面的各種容器跟一些注冊中心可以網(wǎng)上查找更多的資料去了解,因為太多了就不一一解讀了。
我們再看其構(gòu)造函數(shù),里面都是為我們常用的類注冊別名的。
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);
···
XMLConfigBuilder的創(chuàng)建會新建一個Configuration對象,我們接著往下看
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析XML,最終返回一個 DefaultSqlSessionFactory >>
return build(parser.parse());
在得到XMLConfigBuilder 對象后,可以看到調(diào)用了其內(nèi)部的parse()方法
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// XPathParser,dom 和 SAX 都有用到 >>
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 對于全局配置文件各種標簽的解析
propertiesElement(root.evalNode("properties"));
// 解析 settings 標簽
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 類型別名
typeAliasesElement(root.evalNode("typeAliases"));
// 插件
pluginElement(root.evalNode("plugins"));
// 用于創(chuàng)建對象
objectFactoryElement(root.evalNode("objectFactory"));
// 用于對對象進行加工
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工具箱
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 子標簽賦值,默認值就是在這里提供的 >>
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 創(chuàng)建了數(shù)據(jù)源 >>
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析引用的Mapper映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
parse()方法中首先會parser.evalNode("/configuration")獲取到XNode對象,XNode是MyBatis對Node類的封裝,它會自動解析出元素的元素名,元素的屬性,反之起就是解析XML文件的,重點不在此。
parseConfiguration()方法中是對中心配置文件的解析
見名知意,可以看出它是一步一步的解析器配置中心的標簽,如果設(shè)置有就將其解析并放入configuration中。
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 類型別名
typeAliasesElement(root.evalNode("typeAliases"));
// 插件
pluginElement(root.evalNode("plugins"));
// 用于創(chuàng)建對象
objectFactoryElement(root.evalNode("objectFactory"));
// 用于對對象進行加工
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 反射工具箱
reflectorFactoryElement(root.evalNode("reflectorFactory"));
- 第一是解析<properties>標簽,讀取我們引入的外部配置文件,例如db.properties,這里有兩種類型,一種是放在resorce目錄下的,是相對路徑,一種是寫絕對路徑(url),解析的最終結(jié)果就是我們配置信息放到 XPathparser的variables中和Configuration的variables中
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 創(chuàng)建了一個 Properties 對象,后面可以用到
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("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.");
}
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里面
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
- 第二是解析<setting>標簽解析成一個Properties對象,將放到后面的setting方法中處理。
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
- 第三步loadCustomVfs(settings) 是獲取Vitual File System的自定義實現(xiàn)類,比如要讀取本地文件,或者FTP遠程文件的時候,就可以用到自定義的VFS類,是根據(jù)在<settings>標簽里的<vfsImpl>標簽設(shè)置生成一個抽象類VFS的子類,最后賦值到Configuration中
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
- 第四步 loadCustomLogImpl(settings)根據(jù)<logImpl>標簽獲取日志的實現(xiàn)類,包括可以有LOG4J,LOG4J2,SLF4J等待,在這里生成一個Log接口的實現(xiàn)類并賦值到Configuration中
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
- 第五步typeAliasesElement(root.evalNode("typeAliases"))這一步是解析類型別名,類的別名和類的關(guān)系,放在TypeAliasRegistry對象里面。
private void typeAliasesElement(XNode parent) {
// 放入 TypeAliasRegistry
if (parent != null) {
for (XNode child : parent.getChildren()) {
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) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
- 第六步pluginElement(root.evalNode("plugins"))插件的解析,這一步就是把插件解析換成Interceptor類,設(shè)置屬性,首先它會先判斷是否為空,就是判斷配置中心是否配置有這一標簽。有則動態(tài)代理實現(xiàn)的插件,放入configuration對象的interceptorChain里。
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
//動態(tài)代理 我們實現(xiàn)的插件
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
- 對于objectFactoryElement、objectWrapperFactoryElement、 reflectorFactoryElement的解析跟插件類似;
- ObjectFactory 是用來創(chuàng)建返回的對象
- ObjectWrapperFactory 是用來對對象做特殊處理的,
- ReflectorFactory 是反射工具箱,對反射進行了封裝
這些對象都是用resolveClass創(chuàng)建的
settingsElement(settings);
看看這個方法做了些什么
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
···
可以看到里面就是將中心配置文件的<setting>標簽里我們設(shè)置的屬性進行重新賦值,settings二級標簽中一共有26個配置,比如二級緩存、延遲加載、默認執(zhí)行器類型等。一些默認值也是在這里賦值的。
我們再來看看中心配置文件我們設(shè)置的數(shù)據(jù)庫相關(guān)的配置它是怎么解析的
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 事務工廠
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 數(shù)據(jù)源工廠(例如 DruidDataSourceFactory )
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 數(shù)據(jù)源
DataSource dataSource = dsFactory.getDataSource();
// 包含了 事務工廠和數(shù)據(jù)源的 Environment
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 放入 Configuration
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
- typeHandlerElement 跟TypeAlias一樣,TypeHandler有兩種配置方式,一種是單獨配置一個類,一種是指定一個package,最后我們得到的是javaType和JdbcType,以及用來做相互映射的TypeHandler之間的映射關(guān)系,存放在TypeHandlerRegistry對象里。
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
映射關(guān)系
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
第二大步驟,就是解析我們在配置文件中配置的Mapper映射器文件了
// 解析引用的Mapper映射器
mapperElement(root.evalNode("mappers"));
這一步是解析Mapper映射器文件了,到了這里對于配置中心文件的解析就剩下映射器了,下一篇會詳細的介紹。