摘要:前言我們知道在使用時(shí),我們需要通過去創(chuàng)建實(shí)例,譬如為的配置文件那么我們看下方法的具體實(shí)現(xiàn)創(chuàng)建實(shí)例并執(zhí)行解析主要通過執(zhí)行對(duì)配置文件的解析,具體實(shí)現(xiàn)如下文配置文件解析解析標(biāo)簽解析標(biāo)簽解析別名標(biāo)簽解析插件標(biāo)簽解析標(biāo)簽解析標(biāo)簽解析標(biāo)簽從的方法實(shí)現(xiàn)我
前言
我們知道在使用 Mybatis 時(shí),我們需要通過 SqlSessionFactoryBuild 去創(chuàng)建 SqlSessionFactory 實(shí)例,譬如:
// resource 為 mybatis 的配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
那么我們看下 build 方法的具體實(shí)現(xiàn)
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { // 創(chuàng)建 XMLConfigBuilder 實(shí)例并執(zhí)行解析 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { } } } public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
Mybatis 主要通過 XMLConfigBuilder 執(zhí)行對(duì)配置文件的解析,具體實(shí)現(xiàn)如下文:
配置文件解析private void parseConfiguration(XNode root) { try { //issue #117 read properties first // 解析 properties 標(biāo)簽 propertiesElement(root.evalNode("properties")); // 解析 settings 標(biāo)簽 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); // 解析 typeAliases 別名標(biāo)簽 typeAliasesElement(root.evalNode("typeAliases")); // 解析 plugins 插件標(biāo)簽 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 // 解析 environments 標(biāo)簽 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析 typeHandlers 標(biāo)簽 typeHandlerElement(root.evalNode("typeHandlers")); // 解析 mappers 標(biāo)簽 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
從 XMLConfigBuilder 的方法 parseConfiguration 實(shí)現(xiàn)我們知道,MyBatis 會(huì)依次解析配置文件中的相應(yīng)標(biāo)簽,本文將針對(duì)開發(fā)中常用的配置進(jìn)行分析;主要包括 properties, typeAliases, enviroments, typeHandlers, mappers 。
properties 解析 配置示例從配置示例可以看出 properties 屬性變量的來源可以是外部的配置文件,也可以是配置文件中自定義的,也可以是 SqlSessionFactoryBuilder 的 build 方法傳參譬如:
public SqlSessionFactory build(InputStream inputStream, Properties properties) { return build(inputStream, null, properties); }
那么當(dāng)存在同名的屬性時(shí),將采用哪種方式的屬性值呢?解析
private void propertiesElement(XNode context) throws Exception { if (context != null) { // 獲取 properties 標(biāo)簽下的所有 property 子標(biāo)簽 Properties defaults = context.getChildrenAsProperties(); // 獲取 resource,url 屬性 String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); // resource url 兩個(gè)屬性不能同時(shí)存在 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) { // 加載 resource 指定的配置文件 defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { // 加載 url 指定的配置文件 defaults.putAll(Resources.getUrlAsProperties(url)); } /** * 獲取傳參的 properties * 構(gòu)建 sqlSessionFactory 時(shí)可以傳參 properties * * @see SqlSessionFactoryBuilder.build(InputStream inputStream, Properties properties) */ Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } parser.setVariables(defaults); // 將 properties 賦值 configuration 中的 variables 變量 configuration.setVariables(defaults); } }
public Properties getChildrenAsProperties() { Properties properties = new Properties(); // 遍歷 properties 標(biāo)簽下的 propertry 子標(biāo)簽 for (XNode child : getChildren()) { // 獲取 propertry 的 name value 屬性 String name = child.getStringAttribute("name"); String value = child.getStringAttribute("value"); if (name != null && value != null) { properties.setProperty(name, value); } } return properties; }
從 properties 標(biāo)簽解析的實(shí)現(xiàn)來看,MyBatis 加載 properties 屬性的過程如下:
首先加載 properties 標(biāo)簽內(nèi)所有子標(biāo)簽的 property
其次加載 properties 標(biāo)簽屬性 resource 或 url 指定的外部屬性配置
最后加載 SqlSessionFactoryBuilder 的方法 build 傳參的屬性配置
因此,通過方法參數(shù)傳遞的 properties 具有最高優(yōu)先級(jí),resource/url 屬性中指定的配置文件次之,最低優(yōu)先級(jí)的是 properties 標(biāo)簽內(nèi)的子標(biāo)簽 property 指定的屬性。typeAliases 解析
類型別名是為 Java 類型設(shè)置一個(gè)短的名字。它只和 XML 配置有關(guān),存在的意義僅在于用來減少類完全限定名的冗余配置示例
也可以指定一個(gè)包名,MyBatis 會(huì)在包名下面搜索需要的 Java Bean,比如:
解析
private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { // 如果是 package 標(biāo)簽,對(duì)整個(gè)包下的 java bean 進(jìn)行別名處理 // 若 java bean 沒有配置注解的話,使用 bean 的首字母小寫類名作為別名 // 若 java bean 配置了注解,使用注解值作為別名 if ("package".equals(child.getName())) { // 獲取指定的包名 String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { // 別名 String alias = child.getStringAttribute("alias"); // 別名對(duì)應(yīng)的類 String type = child.getStringAttribute("type"); try { Class> clazz = Resources.classForName(type); if (alias == null) { // 默認(rèn)別名為類名,若配置了別名注解則取注解值映射類 typeAliasRegistry.registerAlias(clazz); } else { // 通過指定的別名映射類 typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for "" + alias + "". Cause: " + e, e); } } } } }
typeAliasesElement 在對(duì) typeAliases 標(biāo)簽解析時(shí),針對(duì)采用 package 和 typeAlias 兩種配置方式進(jìn)行了不同的解析。 下面我們先看下通過包名的配置方式
public void registerAliases(String packageName) { registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class> superType) { // 獲取包下所有的類 ResolverUtil> resolverUtil = new ResolverUtil<>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set >> typeSet = resolverUtil.getClasses(); for (Class> type : typeSet) { // Ignore inner classes and interfaces (including package-info.java) // Skip also inner classes. See issue #6 // 忽略內(nèi)部類 接口 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } } public void registerAlias(Class> type) { // 別名為類名 String alias = type.getSimpleName(); // 是否配置了別名注解,若配置了則別名取注解值 Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } registerAlias(alias, type); }
當(dāng)通過 package 指定包名時(shí),MyBatis 會(huì)掃描包下所有的類(忽略內(nèi)部類,接口),若類沒有采用 @Alias 注解的情況下,會(huì)使用 Bean 的首字母小寫的非限定類名來作為它的別名, 比如 domain.blog.Author 的別名為 author;若有注解,則別名為其注解值。
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); }
在完成別名的解析之后會(huì)將其注冊(cè)到 typeAliasRegistry 的變量 typeAliases Map 集合中。
配置環(huán)境 environments 解析environments 用于事務(wù)管理器及數(shù)據(jù)源相關(guān)配置配置示例
從 environments 的配置來看 MyBatis 是支持多數(shù)據(jù)源的,但每個(gè) SqlSessionFactory 實(shí)例只能選擇其中一個(gè); 若需要連接多個(gè)數(shù)據(jù)庫,就得需要?jiǎng)?chuàng)建多個(gè) SqlSessinFactory 實(shí)例。解析
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { /** * @see org.apache.ibatis.session.SqlSessionFactoryBuilder.build 時(shí)未指定 enviorment, 則取默認(rèn)的 */ environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); // 查找與 environment 匹配的配置環(huán)境 if (isSpecifiedEnvironment(id)) { // 解析事務(wù)管理 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // 解析數(shù)據(jù)源 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); // 獲取數(shù)據(jù)源實(shí)例 DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); // 設(shè)置配置環(huán)境 configuration.setEnvironment(environmentBuilder.build()); } } } }
private boolean isSpecifiedEnvironment(String id) { if (environment == null) { // 若 environment 為空說明未指定當(dāng)前 SqlSessionFactory 實(shí)例所需的配置環(huán)境;同時(shí) environments 標(biāo)簽未配置 default 屬性 throw new BuilderException("No environment specified."); } else if (id == null) { // environment 標(biāo)簽需要配置 id 屬性 throw new BuilderException("Environment requires an id attribute."); } else if (environment.equals(id)) { // environment == id 說明當(dāng)前匹配配置環(huán)境 return true; } return false; }
因 environments 支持多數(shù)據(jù)源的配置,所以在解析時(shí)會(huì)先查找匹配當(dāng)前 SqlSessionFactory 的 environment; 然后在解析當(dāng)前配置環(huán)境所需的事務(wù)管理器和數(shù)據(jù)源。
private TransactionFactory transactionManagerElement(XNode context) throws Exception { if (context != null) { // 獲取配置事務(wù)管理器的類別,也就是別名 String type = context.getStringAttribute("type"); // 獲取事務(wù)屬性配置 Properties props = context.getChildrenAsProperties(); // 通過別名查找對(duì)應(yīng)的事務(wù)管理器類并實(shí)例化 TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a TransactionFactory."); }
事務(wù)管理器解析時(shí)會(huì)通過配置中指定的 type 別名去查找對(duì)應(yīng)的 TransactionFactory 并實(shí)例化。
那么 MyBatis 內(nèi)部?jī)?nèi)置了哪些事務(wù)管理器呢?
public Configuration() { typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); // 省略 }
從 Configuration 的構(gòu)造可以看出,其構(gòu)造時(shí)會(huì)通過 typeAliasRegistry 注冊(cè)了別名為 JDBC,MANAGED 的兩種事務(wù)管理器。
private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { // 獲取配置數(shù)據(jù)源的類別,也就是別名 String type = context.getStringAttribute("type"); // 獲取數(shù)據(jù)源屬性配置 Properties props = context.getChildrenAsProperties(); // 通過別名查找數(shù)據(jù)源并實(shí)例化 DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }
同事務(wù)管理器一樣,數(shù)據(jù)源解析時(shí)也會(huì)通過指定的別名查找對(duì)應(yīng)的數(shù)據(jù)源實(shí)現(xiàn)類同樣其在 Configuration 構(gòu)造時(shí)向 typeAliasRegistry 注冊(cè)了三種數(shù)據(jù)源
public Configuration() { // 省略 typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); // 省略 }類型轉(zhuǎn)換器 typeHandlers 解析 配置示例
解析
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 { // 映射 java 對(duì)象類型 String javaTypeName = child.getStringAttribute("javaType"); // 映射 jdbc 類型 String jdbcTypeName = child.getStringAttribute("jdbcType"); // 類型轉(zhuǎn)換器類名 String handlerTypeName = child.getStringAttribute("handler"); Class> javaTypeClass = resolveClass(javaTypeName); JdbcType jdbcType = resolveJdbcType(jdbcTypeName); Class> typeHandlerClass = resolveClass(handlerTypeName); if (javaTypeClass != null) { if (jdbcType == null) { // 指定了 java type,未指定 jdbc type typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } else { // 指定了 java type,指定了 jdbc type typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); } } else { // 未指定 java type 按 typeHandlerClass 注冊(cè) typeHandlerRegistry.register(typeHandlerClass); } } } } }
public void register(Class> javaTypeClass, JdbcType jdbcType, Class> typeHandlerClass) { register(javaTypeClass, jdbcType, getInstance(javaTypeClass, typeHandlerClass)); }
private void register(Type javaType, JdbcType jdbcType, TypeHandler> handler) { if (javaType != null) { // 一個(gè) java type 可能會(huì)映射多個(gè) jdbc type Map> map = typeHandlerMap.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { map = new HashMap<>(); typeHandlerMap.put(javaType, map); } map.put(jdbcType, handler); } // 存儲(chǔ) typeHandler allTypeHandlersMap.put(handler.getClass(), handler); }
當(dāng)指定了 javaType 和 jdbcType 最終會(huì)將二者及 typeHandler 映射并注冊(cè)到 typeHandlerMap 中,從 typeHandlerMap 的數(shù)據(jù)結(jié)構(gòu)來看,javaType 可能會(huì)與多個(gè) jdbcType 映射。 譬如 String -> CHAR,VARCHAR 。
public void register(Class> javaTypeClass, Class> typeHandlerClass) { // 將 type handler 實(shí)例化 register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass)); }
privatevoid register(Type javaType, TypeHandler extends T> typeHandler) { // 獲取 MappedJdbcTypes 注解 // 該注解用于設(shè)置類型轉(zhuǎn)換器匹配的 jdbcType MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); if (mappedJdbcTypes != null) { // 遍歷匹配的 jdbcType 并注冊(cè) for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { register(javaType, handledJdbcType, typeHandler); } if (mappedJdbcTypes.includeNullJdbcType()) { register(javaType, null, typeHandler); } } else { // 未指定 jdbcType 時(shí)按 null 處理 register(javaType, null, typeHandler); } }
當(dāng)類型轉(zhuǎn)換器配置了 javaType 未配置 jdbcType 時(shí),會(huì)判斷類型轉(zhuǎn)換器是否配置了 @MappedJdbcTypes 注解; 若配置了則使用注解值作為 jdbcType 并注冊(cè),若未配置則按 null 注冊(cè)。
public void register(Class> typeHandlerClass) { boolean mappedTypeFound = false; // 獲取 MappedTypes 注解 // 該注解用于設(shè)置類型轉(zhuǎn)換器匹配的 javaType MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class> javaTypeClass : mappedTypes.value()) { // 執(zhí)行注冊(cè) register(javaTypeClass, typeHandlerClass); mappedTypeFound = true; } } if (!mappedTypeFound) { register(getInstance(null, typeHandlerClass)); } }
當(dāng) javaType,jdbcType 均為指定時(shí),會(huì)判斷類型轉(zhuǎn)換器是否配置了 @MappedTypes 注解; 若配置了則使用注解值作為 javaType 并注冊(cè)。
public void register(String packageName) { // 掃描指定包下的所有類 ResolverUtil> resolverUtil = new ResolverUtil<>(); resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName); Set >> handlerSet = resolverUtil.getClasses(); for (Class> type : handlerSet) { //Ignore inner classes and interfaces (including package-info.java) and abstract classes // 忽略內(nèi)部類 接口 抽象類 if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { // 執(zhí)行注冊(cè) register(type); } } }
當(dāng)按指定包名解析時(shí),會(huì)掃描包下的所有類(忽略內(nèi)部類,接口,抽象類)并執(zhí)行注冊(cè)小結(jié)
本文我們主要分析了 Mybatis 配置文件中標(biāo)簽 properties,typeAliases,enviroments,typeHandlers 的解析過程,由于 mappers 的解析比較復(fù)雜后續(xù)在進(jìn)行分析;通過本文的分析我們了解到 Configuration 實(shí)例中包括以下內(nèi)容:
variables : Properties 類型,存儲(chǔ)屬性變量
typeAliasRegistry : 別名注冊(cè)中心,通過一個(gè) Map 集合變量 typeAliases 存儲(chǔ)別名與類的映射關(guān)系
environment : 配置環(huán)境,綁定事務(wù)管理器和當(dāng)前數(shù)據(jù)源
typeHandlerRegistry : 類型轉(zhuǎn)換器注冊(cè)中心,存儲(chǔ) javaType 與 jdbcType,typeHandler 的映射關(guān)系,內(nèi)置 jdbcType 與 typeHandler 的映射關(guān)系
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/73708.html
摘要:探究系統(tǒng)登錄驗(yàn)證碼的實(shí)現(xiàn)后端掘金驗(yàn)證碼生成類手把手教程后端博客系統(tǒng)第一章掘金轉(zhuǎn)眼間時(shí)間就從月份到現(xiàn)在的十一月份了。提供了與標(biāo)準(zhǔn)不同的工作方式我的后端書架后端掘金我的后端書架月前本書架主要針對(duì)后端開發(fā)與架構(gòu)。 Spring Boot干貨系列總綱 | 掘金技術(shù)征文 - 掘金原本地址:Spring Boot干貨系列總綱博客地址:http://tengj.top/ 前言 博主16年認(rèn)識(shí)Spin...
摘要:當(dāng)存在掛起的事務(wù)時(shí),執(zhí)行恢復(fù)掛起的事務(wù)將掛起的事務(wù)綁定的重新綁定到當(dāng)前上下文事務(wù)的就是將掛起的事務(wù)重新綁定到當(dāng)前上下文中。 問題 面試中是不是有時(shí)經(jīng)常會(huì)被問到 Spring 事務(wù)如何管理的了解嗎? ,Spring 事務(wù)的傳播性有哪些,能聊聊它們的使用場(chǎng)景嗎?, 事務(wù)回滾的時(shí)候是所有異常下都會(huì)回滾嗎?; 下面我們就帶著這些問題來看看 Spring 事務(wù)是如何實(shí)現(xiàn)的吧。 實(shí)現(xiàn)分析 首先我們...
摘要:為何重拾使用了多年,但是對(duì)其底層的一些實(shí)現(xiàn)還是一知半解,一些概念比較模糊故決定重新拾起,加深對(duì)的認(rèn)識(shí)。小結(jié)是在完成創(chuàng)建后對(duì)其進(jìn)行后置處理的接口是在完成實(shí)例化對(duì)其進(jìn)行的后置處理接口是框架底層的核心接口,其提供了創(chuàng)建,獲取等核心功能。 為何重拾 使用了 Spring 多年,但是對(duì)其底層的一些實(shí)現(xiàn)還是一知半解,一些概念比較模糊;故決定重新拾起,加深對(duì) Spring 的認(rèn)識(shí)。 重拾計(jì)劃 spr...
摘要:框架具有輕便,開源的優(yōu)點(diǎn),所以本譯見構(gòu)建用戶管理微服務(wù)五使用令牌和來實(shí)現(xiàn)身份驗(yàn)證往期譯見系列文章在賬號(hào)分享中持續(xù)連載,敬請(qǐng)查看在往期譯見系列的文章中,我們已經(jīng)建立了業(yè)務(wù)邏輯數(shù)據(jù)訪問層和前端控制器但是忽略了對(duì)身份進(jìn)行驗(yàn)證。 重拾后端之Spring Boot(四):使用JWT和Spring Security保護(hù)REST API 重拾后端之Spring Boot(一):REST API的搭建...
摘要:前言今天,我將梳理在網(wǎng)絡(luò)編程中很重要的一個(gè)類以及其相關(guān)的類。這類主機(jī)通常不需要外部互聯(lián)網(wǎng)服務(wù),僅有主機(jī)間相互通訊的需求??梢酝ㄟ^該接口獲取所有本地地址,并根據(jù)這些地址創(chuàng)建。在這里我們使用阻塞隊(duì)列實(shí)現(xiàn)主線程和打印線程之間的通信。 前言 今天,我將梳理在Java網(wǎng)絡(luò)編程中很重要的一個(gè)類InetAddress以及其相關(guān)的類NetworkInterface。在這篇文章中將會(huì)涉及: InetA...
閱讀 2030·2021-09-29 09:35
閱讀 1957·2019-08-30 14:15
閱讀 2981·2019-08-30 10:56
閱讀 967·2019-08-29 16:59
閱讀 581·2019-08-29 14:04
閱讀 1315·2019-08-29 12:30
閱讀 1033·2019-08-28 18:19
閱讀 517·2019-08-26 11:51