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