1. 程式人生 > >拋開 Spring ,你知道 MyBatis 載入 Mapper 的底層原理嗎?

拋開 Spring ,你知道 MyBatis 載入 Mapper 的底層原理嗎?

# 原文連結:[拋開 Spring ,你知道 MyBatis 載入 Mapper 的底層原理嗎?](https://blog.csdn.net/Howinfun/article/details/106383895) 大家都知道,利用 Spring 整合 MyBatis,我們可以直接利用 @MapperScan 註解或者 @Mapper 註解,讓 Spring 可以掃描全部的 Mapper 介面,解析然後載入。那麼如果拋開 Spring,你們可知道 MyBatis 是如何解析和載入 Mapper 介面的? 如果不知道的話,可以跟著我這篇文章,一步一步地深入和解讀原始碼,帶你從底層來看通 MyBatis 解析載入 Mapper 的實現原理。 > 文章可是很長的,你能全部看完麼?啊哈哈哈~ > 當然了,如果大家本來就對 MyBatis 挺熟悉的,可以根據自己的情況挑選著目錄來看! ### 一、MyBatis 核心元件: 在解讀原始碼之前,我們很有必要先了解 MyBatis 幾大核心元件,知道他們都是做什麼用的。 核心元件有:Configuration、SqlSession、Executor、StatementHandler、ParameterHandler、ResultSethandler。 下面簡單介紹一下他們: - **Configuration**:用於描述 MyBatis 主配置檔案資訊,MyBatis 框架在啟動時會載入主配置檔案,將配置資訊轉換為 Configuration 物件。 - **SqlSession**:面向使用者的 API,是 MyBatis 與資料庫互動的介面。 - **Executor**:SQL 執行器,用於和資料庫互動。SqlSession 可以理解為 Executor 元件的外觀(外觀模式),真正執行 SQL 的是 Executor 元件。 - **MappedStatement**:用於描述 SQL 配置資訊,MyBatis 框架啟動時,XML 檔案或者註解配置的 SQL 資訊會被轉換為 MappedStatement 物件註冊到 Configuration 元件中。 - **StatementHandler**:封裝了對 JDBC 中 Statement 物件的操作,包括為 Statement 引數佔位符設定值,通過 Statement 物件執行 SQL 語句。 - **TypeHandler**:型別處理器,用於 Java 型別與 JDBC 型別之間的轉換。 - **ParameterHandler**:用於處理 SQL 中的引數佔位符,為引數佔位符設定值。 - **ResultSetHandler**:封裝了對 ResultSet 物件的處理邏輯,將結果集轉換為 Java 實體物件。 ### 二、簡述 Mapper 執行流程: SqlSession元件,它是使用者層面的API。使用者可利用 SqlSession 獲取想要的 Mapper 物件(MapperProxy 代理物件);當執行 Mapper 的方法,MapperProxy 會建立對應的 MapperMetohd,然後 MapperMethod 底層其實是利用 SqlSession 來執行 SQL。 但是真正執行 SQL 操作的應該是 Executor組 件,Executor 可以理解為 SQL 執行器,它會使用 StatementHandler 元件對 JDBC 的 Statement 物件進行操作。當 Statement 型別為 CallableStatement 和 PreparedStatement 時,會通過 ParameterHandler 元件為引數佔位符賦值。 ParameterHandler 元件中會根據 Java 型別找到對應的 TypeHandler 物件,TypeHandler 中會通過 Statement 物件提供的 setXXX() 方法(例如setString()方法)為 Statement 物件中的引數佔位符設定值。 StatementHandler 元件使用 JDBC 中的 Statement 物件與資料庫完成互動後,當 SQL 語句型別為 SELECT 時,MyBatis 通過 ResultSetHandler 元件從 Statement 物件中獲取 ResultSet 物件,然後將 ResultSet 物件轉換為 Java 物件。 我們可以用一幅圖來描述上面各個核心元件之間的關係: ![MyBatis 各大元件關係](https://img-blog.csdnimg.cn/20200527161433937.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0hvd2luZnVu,size_16,color_FFFFFF,t_70) ### 三、簡單例子深入講解底層原理 下面我將帶著一個非常簡單的 Mapper 使用例子來講解底層的流程和原理。 例子很簡單,首先是獲取 MyBatis 的主配置檔案的檔案輸入流,然後建立 SqlSessinoFactory,接著利用 SqlSessionFactory 建立 SqlSessin;然後利用 SqlSession 獲取要使用的 Mapper 代理物件,最後執行 Mapper 的方法獲取結果。 #### 1、程式碼例子: ``` @Test public void testMybatis () throws IOException { // 獲取配置檔案輸入流 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); // 通過SqlSessionFactoryBuilder的build()方法建立SqlSessionFactory例項 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 呼叫openSession()方法建立SqlSession例項 SqlSession sqlSession = sqlSessionFactory.openSession(); // 獲取UserMapper代理物件 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 執行Mapper方法,獲取執行結果 List userList = userMapper.listAllUser(); System.out.println(JSON.toJSONString(userList)); } ``` 第一行程式碼,非常的明顯,就是讀取 MyBatis 的主配置檔案。正常來說,這個主配置檔案應該用來建立 Configuration ,但是這裡卻是傳給 SqlSessionFactoryBuilder 來建立 SqlSessionFactory,然後就利用工廠模式來建立 SqlSession 了;上面我們也提及到, SqlSession 是提供給使用者友好的資料庫操作介面,那麼豈不是說不需要 Configuratin 也可以直接獲取 Mapper 然後操作資料庫了? 那當然不是了,Configuration 是 MyBatis 的主配置類,它裡面會包含 MyBatis 的所有資訊(不管是主配置資訊,還是所有 Mapper 配置資訊),所以肯定是需要建立的。 所以其實在建立 SqlSessionFactory 時就已經初始化 Configuration 了,因為 SqlSession 需要利用 Executor、ParameterHandler 和 ResultSetHandler 等等各大元件互相配合來執行 Mapper,而 Configuration 就是這些元件的工廠類。 我們可以在 SqlSessionFactoryBuilder#build() 方法中看到 Configuration 是如何被初始化的: ``` public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); //...... } ``` 從上面的程式碼能看到,就是根據主配置檔案的檔案輸入流建立 XMLConfigBuilder 物件,然後利用 XMLConfigBuilder#parse() 方法來建立 Configuration 物件。 當然了,雖然只是簡單的呼叫了 XMLConfigBuilder#parse() 方法,可是裡面包含的東西是非常的多的。例如: MyBatis 主配置檔案的解析;如何根據 \ 標籤給每個 Mapper 介面生產 MapperProxy 代理類和將 SQL 配置轉換為 MappedStatement;以及 \、\、\、\ 等等標籤是如何解析的。 當然了,這篇文章我們只會著重於關於 Mapper 配置的解析和載入,根據底層原始碼一步一步的去分析弄明白,至於其他的知識點就不過多講解了。 #### 2、XMLConfigBuilder 中關於 Configuration 的解析過程 ##### Ⅰ. XMLConfigBuilder#parseConfiguration() 上面講到 XMLConfigBuilder 會呼叫 parse() 方法去解析 MyBatis 的主配置檔案,底層主要是利用 XPATH 來解析 XML 檔案。程式碼如下: ``` public class XMLConfigBuilder extends BaseBuilder { // ..... private final XPathParser parser; // ..... public Configuration parse() { // 防止parse()方法被同一個例項多次呼叫 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 呼叫XPathParser.evalNode()方法,建立表示configuration節點的XNode物件。 // 呼叫parseConfiguration()方法對XNode進行處理 parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); 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); } } // .... } ``` 在 XMLConfigBuilder#parseConfiguration() 方法裡面,會對主配置檔案裡的所有標籤進行解析;當然了,由於我們這篇文章的主題是分析 Mapper 的解析和載入過程,所以接下來將直接關注 parseConfiguration() 方法裡面的 mapperElement() 方法,其他部分大家可以直接去閱讀 MyBatis 的原始碼。 > 備註:MyBatis 裡面的所有 xxxBuilder 類都是繼承與 BaseBuilder,而 BaseBuilder 要注意的點就是它持有著 Configuration 例項的引用。 ##### Ⅱ . XMLConfigBuilder#mapperElement() 在 XMLConfigBuilder#mapperElement() 方法裡面,主要是解析 \ 標籤裡面的 \ 標籤和 \ 標籤,這兩個標籤主要是描述 Mapper 介面的全路徑、Mapper 介面所在的包的全路徑以及 Mapper 介面對應的 XML 檔案的全路徑。 程式碼如下: ``` private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // 通過標籤指定包名 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 通過resource屬性指定XML檔案路徑 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { // 通過url屬性指定XML檔案路徑 ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { // 通過class屬性指定介面的完全限定名 Class mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } } ``` 從上面的程式碼來看,主要是將標籤分為 \ 和 \ 來解析,而再細一點可以分為兩種解析情況:一種是指定了Mapper介面的 XML 檔案,而另外一種是指定了 Mapper 介面。 **那麼我們可以先看看指定 XML 檔案是如何解析與載入 Mapper 的。** #### 3、XMLMapperBuilder 中關於 Mapper 的解析過程 Mapper 介面的 XML 檔案的解析當然也是利用 XPath,但此時不再是 XMLConfigBuilder 來負責了,而是需要建立一個 XMLMapperBuilder 物件,而 XMLMapperBuilder 需要傳入 XML 檔案的檔案輸入流。 ##### Ⅰ . XMLMapperBuilder#parse() 我們可以看看 XMLMapperBuilder#parse() 方法,XML 檔案的解析流程就是在這裡面: ``` public void parse() { if (!configuration.isResourceLoaded(resource)) { // 呼叫XPathParser的evalNode()方法獲取根節點對應的XNode物件 configurationElement(parser.evalNode("/mapper")); // 將資源路徑新增到Configuration物件中 configuration.addLoadedResource(resource); bindMapperForNamespace(); } // 繼續解析之前解析出現異常的ResultMap物件 parsePendingResultMaps(); // 繼續解析之前解析出現異常的CacheRef物件 parsePendingCacheRefs(); // 繼續解析之前解析出現異常標籤配置 parsePendingStatements(); } ``` 解析前,會先判斷 Configuratin 是否已經載入這個 XML 資源,如果不存在,則呼叫 configurationElement() 方法;在方法裡面會解析所有的 \、\、\、\、\ 和 \ 標籤。 ##### Ⅱ . XMLMapperBuilder#configuratinElement() 下面我們先看一下 XMLMapperBuilder#configuratinElement() 方法的程式碼: ``` private void configurationElement(XNode context) { try { // 獲取名稱空間 String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } // 設定當前正在解析的Mapper配置的名稱空間 builderAssistant.setCurrentNamespace(namespace); // 解析標籤 cacheRefElement(context.evalNode("cache-ref")); // 解析標籤 cacheElement(context.evalNode("cache")); // 解析所有的標籤 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析所有的標籤 resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析所有的標籤 sqlElement(context.evalNodes("/mapper/sql")); // 解析所有的標籤 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } } ``` 當然了,我們此時將所有注意力集中在 buildStatementFromContext 方法即可。 ##### Ⅲ . XMLMapperBuilder#buildStatementFromContext() 在這個方法裡面,會呼叫過載的 buildStatementFromContext 方法;但是這裡還不是真正解析的地方,而是遍歷所有標籤,然後建立一個 XMLStatementBuilder 物件,對標籤進行解析。程式碼如下: ``` private void buildStatementFromContext(List list, String requiredDatabaseId) { for (XNode context : list) { // 通過XMLStatementBuilder物件,對標籤進行解析 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { // 呼叫parseStatementNode()方法解析 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } } ``` #### 4、XMLStatementBuilder 中關於 的解析過程 ##### Ⅰ. XMLStatementBuilder#parseStatementNode() 那麼我們接著看看 XMLStatementBuilder#parseStatementNode 方法: ``` public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } // 解析標籤屬性 Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); // 獲取LanguageDriver物件 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // 獲取Mapper返回結果型別Class物件 Class resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); // 預設Statement型別為PREPARED StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // 將標籤內容,替換為標籤定義的SQL片段 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // 解析標籤 processSelectKeyNodes(id, parameterTypeClass, langDriver); // 通過LanguageDriver解析SQL內容,生成SqlSource物件 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); // 獲取主鍵生成策略 if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } ``` 從上面的程式碼可得,首先會解析標籤裡的所有屬性;然後建立 LanguageDriver 來解析標籤裡面的 SQL 配置,並生成對應的 SqlSource 物件;最後,利用工具類 MapperBuilderAssistant 來將上面解析的內容組裝成 MappedStatement 物件,並且註冊到 Configuration 中。 #### 5、詳細介紹 SqlSource 與 LanguageDriver 介面 上面我們說到,解析的 SQL 內容會生成對應的 SqlSource 物件,那麼我們先看看 SqlSource 介面,程式碼如下: ``` public interface SqlSource { BoundSql getBoundSql(Object parameterObject); } ``` SqlSource 介面的定義非常簡單,只有一個 getBoundSql() 方法,該方法返回一個 BoundSql 例項。 所以說 BoundSql 才是對 SQL 語句及引數資訊的封裝,它是 SqlSource 解析後的結果,BoundSql 的程式碼如下: ``` public class BoundSql { // Mapper配置解析後的sql語句 private final String sql; // Mapper引數對映資訊 private final List parameterMappings; // Mapper引數物件 private final Object parameterObject; // 額外引數資訊,包括標籤繫結的引數,內建引數 private final Map additionalParameters; // 引數物件對應的MetaObject物件 private final MetaObject metaParameters; // ... 省略 get/set 和 建構函式 } ``` 因為 SQL 的解析是利用 LanguageDriver 元件完成的,所以我們再接著看看 LanguageDriver 介面,程式碼如下: ``` public interface LanguageDriver { ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql); SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType); SqlSource createSqlSource(Configuration configuration, String script, Class parameterType); } ``` 如上面的程式碼所示,LanguageDriver 介面中一共有3個方法,其中 createParameterHandler() 方法用於建立 ParameterHandler 物件,另外還有兩個過載的 createSqlSource() 方法,這兩個過載的方法用於建立 SqlSource 物件。 MyBatis 中為 LanguageDriver 介面提供了兩個實現類,分別為 XMLLanguageDriver 和 RawLanguageDriver。 - XMLLanguageDriver 為 XML 語言驅動,實現了動態 SQL 的功能,也就是說可以利用 MyBatis 提供的 XML 標籤(常用的等標籤)結合OGNL表示式語法來實現動態的條件判斷。 - RawLanguageDriver 表示僅支援靜態 SQL 配置,不支援動態 SQL 功能。 接下來我們重點了解一下 XMLLanguageDriver 實現類的內容,程式碼如下: ``` public class XMLLanguageDriver implements LanguageDriver { @Override public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); } @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) { // 該方法用於解析XML檔案中配置的SQL資訊 // 建立XMLScriptBuilder物件 XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); // 呼叫 XMLScriptBuilder物件parseScriptNode()方法解析SQL資源 return builder.parseScriptNode(); } @Override public SqlSource createSqlSource(Configuration configuration, String script, Class parameterType) { // 該方法用於解析Java註解中配置的SQL資訊 // 字串以

相關推薦

拋開 Spring 知道 MyBatis 載入 Mapper底層原理

# 原文連結:[拋開 Spring ,你知道 MyBatis 載入 Mapper 的底層原理嗎?](https://blog.csdn.net/Howinfun/article/details/106383895) 大家都知道,利用 Spring 整合 MyBatis,我們可以直接利用 @MapperSc

二哥知道騰訊的技術職級

先看再點贊,給自己一點思考的時間;歡迎微信搜尋【沉默王二】關注這個有顏值卻假裝靠才華苟且的程式設計師。本文 GitHub github.com/itwanger 已收錄,裡面還有我精心準備的一線大廠面試題。 題目是一個讀者問我的,但摸著良心講,我沒在騰訊待過,具體技術職級真的不清楚,但我在網上搜到了下

MybatisMapper底層原理

總的來說是通過動態代理。動態代理的功能就是通過攔截器方法回撥(invokeHandler),達到增強目標物件的目的。看下面程式碼,很關鍵一點就是InvocationHandler包含target物件。在invoke方法中會呼叫target的方法。public class He

知道 react-color 的實現原理

## 一、前言 [`ReactColor`](https://github.com/casesandberg/react-color) 是一個優秀的 React 顏色選擇器元件,官方給了多種佈局供開發者選擇。 筆者常用的主題為 Sketch,這種主題涵蓋了**顏色面板**、**推薦色塊**、**RG

女朋友問:知道藍芽耳機的原理

![](https://james-1258744956.cos.ap-shanghai.myqcloud.com/bluetooth/cycling.jpeg?imageMogr2/thumbnail/!50p) 又到了春暖花開的季節,每天最幸福的時光就是戴著我的藍芽耳機,聽著自己喜歡的歌,騎著我心愛的小

網頁載入知道幾種原因?

記得以前有個培訓班的老師過來宣傳,他當時問了我們一個問題,“開啟一個網頁慢,你能說出10個原因麼?”,我腦海裡立刻就出現了網速慢、電腦卡等原因,但是發現自己能說出的不超過五個,自己還是學web的,GG。今天突然想到了這個問題,就總結下 頻寬不足,首先想到的就是自己網速的

這些優秀的 Spring Cloud 開源軟體知道的有幾個?

點選上方“程式設計師大咖”,選擇“置頂公眾號”關鍵時刻,第一時間送達!來自:開源最前線(ID:O

這些優秀的 Spring Cloud 開源軟體知道幾個?

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的開發便利性巧妙地簡化了分散式系統基礎設施的開發,如服務發現註冊、配置中心、訊息匯流排、負載均衡、斷路器、資料監控等,都可以用Spring Boot的開發風格做到一鍵啟動和部署。 為整理了一些非常優秀的 Spring Cloud 開

Spring還可以這樣用快取知道

大家在專案開發過程中,或多或少都用過快取,為了減少資料庫的壓力,把資料放在快取當中,當訪問的請求過來時,直接從快取讀取。快取一般都是基於記憶體的,讀取速度比較快,市面上比較常見的快取有:memcache、redis、mongodb、guava cache等。 快取的常規用法 大家使用快取時,常用的邏輯時這

談談Spring中的物件跟Bean知道Spring怎麼建立物件的

> **本系列文章:** > > [讀原始碼,我們可以從第一行讀起](https://blog.csdn.net/qq_41907991/article/details/105667900) > > [你知道Spring是怎麼解析配置類的嗎?](https://blog.csdn.net/qq_4190799

設計模式 | Spring中用到的設計模式知道幾個?

>設計模式無處不在,因為它就來自於我們的日常生活,提煉於生活經驗。 > >正握在你手中的手機,不能用220V的電壓直接充電,需要一個專門的電源介面卡(充電器)才行。擺在你桌上的電腦也是一樣的,都需要“適配”。而 介面卡模式 (Adapter Pattern)正是由此總結而來。 從一個問題出

知道的大腦在想什麽

記憶的方法 腦的使用 腦功能   近幾個月的學習知識與能力,利用一些新學的名詞,配合自己對記憶的體會以及參考了一些書的內容,總結了以下用腦記憶的心得體會。  你的大腦在想什麽嗎?它又會對什麽特別感興趣嗎?  我們的大腦總是渴求新奇的東西,它喜歡搜尋、審視、期待著不尋常的事情發生。正是因為這個特點,

羅斯蒙特變送器這些使用技巧知道多少?

羅斯蒙特 羅斯蒙特3051 毫無置疑,羅斯蒙特的誕生給壓力檢測方面帶來了巨大的變革。使用者在對羅斯蒙特變送器工作原理不是很清楚地情況下就能使用,並且輕輕松解決工作上的很多問題。如果了解羅斯蒙特這些使用小技巧,將在維持其工作性能上有很大幫助。羅斯蒙特使用五條禁忌①切勿用硬物碰觸膜片,導致隔離膜片損壞;②

老男孩:做運維比做開發崗位有哪些特殊好處知道麽?

老男孩思想 運維屌絲 逆襲之路 現實中很多網友,包括大學生對編程開發了解很多,但對運維了解較少,有經驗的部分人員(包括一些從事運維的)也會覺得開發更牛逼,運維就是背黑鍋(如何不背黑鍋,看老男孩的以後文章)的,運維==黑鍋俠。那麽,老男孩就給大家講講老男孩眼中運維的好處,讓大家重新認識下運維崗

Oracle 的密碼策略知道多少?

oracle 密碼策略 你知道 今天突然有客戶問我一個問題,數據庫要添加一個監控用戶,想做一個會話數的限制,這裏做了一個小測試,平日維護的時候也需要關註一下數據庫的資源限制。<roidb1:orcl1:/home/oracle>$sqlplus / as sysdbaSQL*Plus:

湯世聲: 普通人也可以變成記憶力超人知道

中國已經有幾十萬的孩子,他們可以 10分鐘記住100個隨機詞組! 10分鐘就能把100位隨機數字倒背如流! 30分鐘學會文科類記憶方法,從此天文地理,輕松拿下。 30分鐘學會思維導圖,從此不再思維受限 這不是天方夜譚!這更不是死記硬背!這只是一個科學的方法這只是一個開啟右腦潛能的鑰匙

Docker技術這些應用場景知道

docker docker應用場景 場景一:節省項目環境部署時間1.單項目打包每次部署項目到測試、生產等環境,都要部署一大堆依賴的軟件、工具,而且部署期間出現問題幾率很大,不經意就花費了很長時間。Docker主要理念就是環境打包部署,可在任意Docker Engine運行。前期我們只需要將每個項目環境

關於 Mesos知道多少?13 個問題帶深入了解 Mesos

增加 manage 人的 國內 工作 mas cloudera 目前 獲得 聽過不少人在討論 Mesos,然而並不是很明白 Mesos 到底能夠解決什麽問題,使用場景是怎樣的,周偉濤(國內較早一批接觸使用 Docker,Mesos 等技術的開發者)用一句話形容它, Meso

宏宇電商:當下流行的網絡推廣方式知道哪種最有效?

網絡營銷隨著市場競爭的日益激烈,網絡推廣已成為各品牌或企業的一個重要營銷宣傳方式。雖然我們也經常會接觸到一些網絡推廣方式,但是究竟哪一種網絡推廣方式對企業來說才是最有效的呢?下面小編結合幾種當下流行的網絡推廣方式,根據他們的特點,為大家在網絡推廣過程中提供一些參考性的建議。 ??NO.1 SEO??SEO是指

Python常見十六個錯誤集合知道那些?

學習 錯誤 程序員 使用python會出現各種各樣的錯誤,以下是Python常見的錯誤以及解決方法。 1.ValueError: ‘Conv2d_1a_3×3’ is not a valid scope name 這個是剛遇到的問題,在LZ自己手打Inception net的時候,想賦一個名字的時