系統學習SpringFramework:循環依賴與三級緩存

本篇內容包括:Spring 中的循環依賴問題(包括 Spring 中的循環依賴問題和Spring 中的循環依賴的 5 種場景的介紹)、Spring 三級緩存介紹、4 個 Spring 無法自動解決的循環以來場景以及其對應的手動解決方式。

一、Spring 中的循環依賴問題

1、Spring 中的循環依賴概述

Spring 循環依賴指的是 SpringBean 對象之間的依賴關系形成一個閉環。即在代碼中,把兩個或者多個 Bean 相互之間去持有對方的引用,就會發生循環依賴,循環依賴會導致注入出現死循環,這是 Spring 發生循環依賴的主要原因之一。

Spring 循環依賴主要有三種情況,即:自身依賴自身,兩者互相依賴,多者循環依賴

  1. 自身依賴自身:自己依賴自己的直接依賴
  2. 兩者互相依賴:兩個對象之間的直接依賴
  3. 多者循環依賴:多個對象之間的間接依賴

Spring循環依賴問題

自身依賴自身,兩者互相依賴 兩者互相依賴的情況比較直觀,很好辨識,但是我們工作中最有可能觸發的還是多者循環依賴,多者循環依賴的情況有時候因為業務代碼調用層級很深,不容易識別出來。但無論循環依賴的數量有多少,循環依賴的本質是一樣的。就是你的完整創建依賴于我,而我的完整創建也依賴于你,但我們互相沒法解耦,最終導致依賴創建失敗。

2、Spring 中的循環依賴的 5 種場景

Spring 中出現循環依賴主要有著 5 種場景: ①、單例的 setter 注入(能解決);②、多例的 setter 注入(不能解決);③、構造器注入(不能解決);④、單例的代理對象 setter 注入(有可能解決);⑤、DependsOn 循環依賴(不能解決)。接下來我們逐一來看。

img

二、Spring 三級緩存

1、spring 創建 bean 的流程

在開始理解 Spring 三級緩存如何讓解決循環依賴問題前我們先來溫習一下 spring 創建 bean 的流程:

  1. Spring 啟動時會根據配置文件或啟動類把所有的 bean 注冊成 bean 定義(就是映射 <bean> 標簽屬性的 Java 類)

  2. 遍歷 bean 定義中的 beanName,調用 BeanFactory#getBean(beanName) 方法創建、初始化并返回 bean 實例

    其中 getBean 方法:

    1. 先從緩存(一層到三層依次獲取)拿,沒有就去創建;
    2. 創建 Bean 時,把 beanName 標記為正在創建中,通過其定義里的 class 找到構造器方法反射創建實例,并把其對象工廠放入第三層緩存
    3. 對實例初始化,移除正在創建中的標記,把實例放入第一層緩存,移除第二、三層中的緩存,最后返回實例

Ps1:實例初始化過程:獲取此 bean 中有 @Autowired 等注解的成員變量,從所有 bean 定義中找出此類型的 beanName,又通過 BeanFactory#getBean 方法獲取實例,然后反射設值成員變量。

Ps2:上述流程中 斜體 部分為觸發循環依賴時多出主流程的步驟。

位于 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry 中的三級緩存源碼:

	/** Cache of singleton objects: bean name to bean instance. */private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);/** Cache of singleton factories: bean name to ObjectFactory. */private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);/** Cache of early singleton objects: bean name to bean instance. */private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

2、場景一:單例的 setter 注入

這種注入方式應該是 Spring 中最常見的,Demo 如下:

@Service
public class TestService1 {@Autowiredprivate TestService2 testService2;public void test1() {}
}@Service
public class TestService2 {@Autowiredprivate TestService1 testService1;public void test2() {}
}

在上述代碼中,就是一個經典的循環依賴,其中 TestService1 依賴 TestService2,TestService2 依賴 TestService1 構成了一個簡單了兩者互相依賴關系,但是我們在使用類似代碼時,并沒有感知過該類型的循環依賴存在,因為此種類型已經被 Spring 默默解決了。

3、三級緩存

Spring 內部有三級緩存:

  • 一級緩存(singletonObjects),用于保存實例化、注入、初始化完成的 Bean 實例
  • 二級緩存(earlySingletonObjects),用于保存實例化完成的 Bean 實例
  • 三級緩存(singletonFactories),用于保存 Bean 的創建工廠,以便于后面擴展有機會創建代理對象。

以上面 Demo 為例,現在項目啟動,spring 開始創建 bean,比如先創建 TestService1:

  1. 標記 TestService1 為正在創建中,反射創建其實例,其對象工廠放入第三層緩存
  2. 初始化 TestService1 實例化時發現需要依賴注入 TestService2,則獲取 TestService2 的實例
  3. 標記 TestService2 為正在創建中,反射創建其實例,其對象工廠放入第三層緩存
  4. 初始化 TestService2 實例化時發現需要依賴注入 TestService1,則獲取 TestService1 的實例
  5. 這時候從緩存中獲取時,TestService1 為正在創建中且第三層緩存有 TestService1 的值了,所以調用緩存的對象工廠的 getObject 方法,把返回的 TestService1 實例放入第二層緩存,刪除第三層緩存
  6. TestService2 實例初始化完成,放入第一層緩存,移除第二、三層中的緩存
  7. 回到第 2 步,TestService1 實例初始化完成,放入第一層緩存,移除第二、三層中的緩存

img

下面是 getBean(beanName) 方法最先調用的從這三層緩存中獲取 bean 實例的邏輯(即上面第5步)

	/*** Return the (raw) singleton object registered under the given name.* <p>Checks already instantiated singletons and also allows for an early* reference to a currently created singleton (resolving a circular reference).* @param beanName the name of the bean to look for* @param allowEarlyReference whether early references should be created or not* @return the registered singleton object, or {@code null} if none found*/@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}

以及一直提到的對象工廠,及其 getObject 方法的實現:

	/*** Obtain a reference for early access to the specified bean,* typically for the purpose of resolving a circular reference.* @param beanName the name of the bean (for error handling purposes)* @param mbd the merged bean definition for the bean* @param bean the raw bean instance* @return the object to expose as bean reference*/protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}

4、關于二級緩存

細心的朋友可能會發現在這種場景中第二級緩存作用不大。那么問題來了,為什么要用第二級緩存呢?

試想一下,如果出現以下這種情況,我們要如何處理?

@Service
public class TestService1 {@Autowiredprivate TestService2 testService2;@Autowiredprivate TestService3 testService3;public void test1() {}
}
@Service
public class TestService2 {@Autowiredprivate TestService1 testService1;public void test2() {}
}
@Service
public class TestService3 {@Autowiredprivate TestService1 testService1;public void test3() {}
}

TestService1 依賴于 TestService2 和 TestService3,而 TestService2 依賴于 TestService1,同時 TestService3 也依賴于 TestService1。按照上圖的流程可以把 TestService1 注入到 TestService2,并且 TestService1 的實例是從第三級緩存中獲取的。

假設不用第二級緩存,TestService1 注入到 TestService3 的流程如圖:

img

TestService1 注入到 TestService3 又需要從第三級緩存中獲取實例,而第三級緩存里保存的并非真正的實例對象,而是 ObjectFactory對象。說白了,兩次從三級緩存中獲取都是 ObjectFactory 對象,而通過它創建的實例對象每次可能都不一樣的。這樣不是有問題?

為了解決這個問題,Spring 引入的第二級緩存。上面其實 TestService1 對象的實例已經被添加到第二級緩存中了,而在 TestService1 注入到 TestService3 時,只用從第二級緩存中獲取該對象即可。

img

還有個問題,第三級緩存中為什么要添加 ObjectFactory 對象,直接保存實例對象不行嗎?答:不行,因為假如你想對添加到三級緩存中的實例對象進行增強,直接用實例對象是行不通的。


三、循環依賴的其他 4 種場景

1、多例的 setter 注入

這種注入方法偶然會有,特別是在多線程的場景下,具體代碼如下:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService1 {@Autowiredprivate TestService2 testService2;public void test1() {}
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService2 {@Autowiredprivate TestService1 testService1;public void test2() {}
}

在上述多例的 setter 注入情況下,Spring 程序也是能夠正常啟動啟動的,其實在 AbstractApplicationContext 類的 refresh方法中告訴了我們答案,它會調用 finishBeanFactoryInitialization 方法,該方法的作用是為了 Spring 容器啟動的時候提前初始化一些 Bean。該方法的內部又調用了 preInstantiateSingletons 方法

@Overridepublic void preInstantiateSingletons() throws BeansException {if (logger.isTraceEnabled()) {logger.trace("Pre-instantiating singletons in " + this);}// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);if (bean instanceof FactoryBean) {FactoryBean<?> factory = (FactoryBean<?>) bean;boolean isEagerInit;if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,getAccessControlContext());}else {isEagerInit = (factory instanceof SmartFactoryBean &&((SmartFactoryBean<?>) factory).isEagerInit());}if (isEagerInit) {getBean(beanName);}}}else {getBean(beanName);}}}

其中 非抽象、單例 并且非懶加載的類才能被提前初始 Bean。,而多例即 SCOPE_PROTOTYPE 類型的類,非單例,不會被提前初始化 Bean,所以程序能夠正常啟動。如何讓他提前初始化bean呢?

只需要再在 DEMO 中定義一個單例的類,在它里面注入 TestService1

@Service
public class TestService3 {@Autowiredprivate TestService1 testService1;
}

重新啟動程序,執行結果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

果然出現了循環依賴。

Ps:這種循環依賴問題是無法解決的,因為它沒有用緩存,每次都會生成一個新對象。

2、構造器注入

這種注入方式現在其實用的已經非常少了,但是我們還是有必要了解一下,如下代碼:

@Service
public class TestService1 {public TestService1(TestService2 testService2) {}
}
@Service
public class TestService2 {public TestService2(TestService1 testService1) {}
}

運行結果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

出現了循環依賴,為什么呢?

img

從圖中的流程看出構造器注入沒能添加到三級緩存,也沒有使用緩存,所以也無法解決循環依賴問題。

3、單例的代理對象 setter 注入

這種注入方式其實也比較常用,比如平時使用:@Async 注解的場景,會通過 AOP 自動生成代理對象。

@Service
public class TestService1 {@Autowiredprivate TestService2 testService2;@Asyncpublic void test1() {}
}
@Service
public class TestService2 {@Autowiredprivate TestService1 testService1;public void test2() {}
}

從前面得知程序啟動會報錯,出現了循環依賴,為什么會循環依賴呢?答案就在下面這張圖中:

img

說白了,Bean 初始化完成之后,后面還有一步去檢查:第二級緩存和原始對象是否相等。由于它對前面流程來說無關緊要,所以前面的流程圖中省略了,但是在這里是關鍵點,我們重點說說:

if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {String[] dependentBeans = getDependentBeans(beanName);Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);for (String dependentBean : dependentBeans) {if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,"Bean with name '" + beanName + "' has been injected into other beans [" +StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +"] in its raw version as part of a circular reference, but has eventually been " +"wrapped. This means that said other beans do not use the final version of the " +"bean. This is often the result of over-eager type matching - consider using " +"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");}}}}

正好是走到這段代碼,發現第二級緩存和原始對象不相等,所以拋出了循環依賴的異常。如果這時候把 TestService1 改個名字,改成:TestService6,其他的都不變。

@Service
public class TestService6 {@Autowiredprivate TestService2 testService2;@Asyncpublic void test1() {}
}

再重新啟動一下程序,神奇般的好了。這是為什么?這就要從 Spring Bean 加載順序說起了,默認情況下,Spring 是按照文件完整路徑遞歸查找的,按路徑+文件名排序,排在前面的先加載。所以 TestService1 比T estService2 先加載,而改了文件名稱之后,TestService2 比 TestService6 先加載。

為什么 TestService2 比 TestService6 先加載就沒問題呢?答案在下面這張圖中:

img

這種情況 testService6 中其實第二級緩存是空的,不需要跟原始對象判斷,所以不會拋出循環依賴。

4、DependsOn 循環依賴

還有一種有些特殊的場景,比如我們需要在實例化 Bean A 之前,先實例化 Bean B,這個時候就可以使用 @DependsOn 注解。

@DependsOn(value = "testService2")
@Service
public class TestService1 {@Autowiredprivate TestService2 testService2;public void test1() {}
}
@DependsOn(value = "testService1")
@Service
public class TestService2 {@Autowiredprivate TestService1 testService1;public void test2() {}
}

程序啟動之后,執行結果:

Circular depends-on relationship between 'testService2' and 'testService1'

這個例子中本來如果 TestService1 和 TestService2 都沒有加 @DependsOn 注解是沒問題的,反而加了這個注解會出現循環依賴問題。

這又是為什么?答案在 AbstractBeanFactory 類的 doGetBean 方法的這段代碼中:

// Guarantee initialization of beans that the current bean depends on.String[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {for (String dep : dependsOn) {if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");}registerDependentBean(dep, beanName);try {getBean(dep);}catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"'" + beanName + "' depends on missing bean '" + dep + "'", ex);}}}

它會檢查 dependsOn 的實例有沒有循環依賴,如果有循環依賴則拋異常。


三、出現循環依賴如何解決?

項目中如果出現循環依賴問題,說明是 Spring 默認無法解決的循環依賴,要看項目的打印日志,屬于哪種循環依賴。目前包含下面幾種情況:

img

解決方式:

問題解決方式
生成代理對象產生的循環依賴①、 使用 @Lazy 注解,延遲加載 ②、使用 @DependsOn 注解,指定加載先后關系 ③、修改文件名稱,改變循環依賴類的加載順序
多例循環依賴可以通過把 Bean 改成單例的解決
構造器循環依賴可以通過使用 @Lazy 注解解決
使用 @DependsOn 產生的循環依賴要找到@DependsOn注解循環依賴的地方,迫使它不循環依賴就可以解決問題

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/535488.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/535488.shtml
英文地址,請注明出處:http://en.pswp.cn/news/535488.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

oracle安裝需要的包列表

redhat7.5安裝圖形界面&#xff1a; [rootwwyt ~]# rpm -ivh --nodeps --force xorg-x11-font* [rootwwyt ~]# mount -o loop -t iso9660 rhel-server-7.5-x86_64-dvd.iso /media/iso/ [rootwwyt ~]# cat /etc/yum.repos.d/my.repo [base] nameredhat7.5 baseurlfile:///m…

深入理解Java虛擬機:Java類的加載機制

本篇內容包括&#xff1a;Java 類的加載機制&#xff08;Jvm 結構組成、Java 類的加載&#xff09;、類的生命周期&#xff08;加載-驗證-準備-解析-初始化-使用-卸載&#xff09;、類加載器 以及 雙親委派模型。 一、Java 類的加載機制 1、 Jvm 結構組成 Jvm 整體組成可分為…

新版谷歌瀏覽器開啟Flash支持

瀏覽器地址欄中輸入chrome://version查看Chrome瀏覽器、Flash插件的版本信息。 Chrome 69.0-70.0版本Chrome 71.0-74.0及以后版本谷歌瀏覽器地址欄中輸入【chrome://flags/#enable-ephemeral-flash-permission】&#xff0c;將【Enable Ephemeral Flash Permissions】從【Defau…

深入理解Java虛擬機:Java垃圾回收機制

本篇內容包括&#xff1a;JAVA 垃圾回收機制概述、有哪些內存需要回收、如何回收&#xff08;標記-清除、標記-整理&#xff08;標記-清除-壓縮&#xff09;、復制&#xff08;標記-復制-清除&#xff09;、分代收集等算法&#xff09; 以及 何時進行垃圾回收等內容&#xff01…

深入理解Java虛擬機:Java垃圾回收器

本篇內容包括&#xff1a;7 種 Jvm 垃圾回收器的介紹、對比 以及 對應的 Jvm 參數設置&#xff0c;這 7 種包括了&#xff1a;Serial、ParNew 以及 Parallel Scavenge 三種新生代回收器 和 &#xff1a;Serial Old、Parallel Old 以及 CMS 三種老年代回收器&#xff0c;此外還有…

oracle跨越千年處理

如果指定的兩位年份0-4950-99 如果當前 的兩位年 份是 0-49返回的日期是當前世紀返回的日期是上個世紀50-99返回的日期是下個世紀返回的日期是當前世紀 current yearSpecified DateRR FormatYY Format199527-OCT-9519951995199527-OCT-171951917200127-OCT-1720012017200127-OC…

網絡協議:什么是網絡分層的七四五

本篇內容包括&#xff1a;網絡分層七層、五層、四層網絡協議概念的介紹&#xff0c;IOS 體系結構的介紹與構成、TCP/IP體系結構的簡介及與IOS體系的關系 以及五層體系結構的介紹。 一、七層、五層、四層網絡協議概念 1、關于網絡協議 網絡協議&#xff0c;即是指計算機網絡中…

查看表空間相關命令

默認表空間數據文件大小根據DATA BLOCKS的大小有關&#xff0c;默認最大為32GB表空間達到32G&#xff0c;只能增加數據文件alter tablespace 表空間名 add datafile 數據文件路徑‘ size 500m autoextend on next 100m maxsize 10000M;未達到32G&#xff0c;修改數據文件的擴展…

網絡協議:一文搞懂Socket套接字

本篇內容包括&#xff1a;Socket 套接字的簡介、Socket 套接字的分類、Java 中的 Socket 即 java.net.ServerSocket、java.net.Socket 的使用&#xff0c;以及Java 使用套接字 Scoket 編程的Demo。 一、Socket 簡介 TCP&#xff08;傳輸控制協議&#xff09;是一種面向連接的、…

RESETLOGS

使用resetlogs選項&#xff0c;會把當前的日志序號&#xff08;log sequence number&#xff09;重設為1&#xff0c;并拋棄所有日志信息。在以下條件時需要使用resetlogs選項&#xff1a; 在不完全恢復&#xff08;介質恢復&#xff09;&#xff1b; 使用備份控制文件。 使…

網絡協議:透徹解析HTTP協議

本篇內容包括&#xff1a;HTTP 協議定義及其特點概述、關于 URL 定義及分類概述、Request 請求、Response 響應 以及 瀏覽器訪問一個網站的全過程 等內容… 一、HTTP 協議概述 HTTP&#xff08;HyperText Transfer Protocol&#xff09; 即 超文本傳輸協議&#xff0c;它是一種…

oracle參數文件和口令文件

外部 審核 口令&#xff1a;記錄超級用戶的用戶名和口令&#xff0c;做sys用戶的安全審核 oracle9以后全部使用sys登錄&#xff0c;但需要使用as sysdba &#xff0c;之前版本需要使用internal o7字典打開 只要用戶和密碼存在于口令文件&#xff0c;就可以以sysdba登錄&#…

innobackup備份恢復實操步驟--gtid復制(1)(1)

首先在主庫進行備份&#xff1a; 備份命令&#xff1a; Innobackupex --defaults-file/app/dbcluster/sgrdb/mysql/my19103.cnf --no-timestamp --userdbscale --passwordS6000dbscale --host10.157.43.224 --port19103 /data/backup 如果使用setsid&#xff1a; setsid …

Redis系列:Redis的概述與安裝

Redis(Remote Dictionary Server) 是一個使用 C 語言編寫的&#xff0c;開源的&#xff08;BSD許可&#xff09;高性能非關系型&#xff08;NoSQL&#xff09;的鍵值對數據庫。 本篇內容包括&#xff1a;Redis 簡介&#xff08;為什么快&#xff1f;為什么單線程&#xff1f;優…

安裝LibreOffice和字體

#/bin/bash # Check if user is root if [ $(id -u) ! "0" ]; thenecho "Error: You must be root to run this script, please use root"exit 1 fi echo 安裝LibreOffice cd /home/ tar -zxvf LibreOffice_6.3.3_Linux_x86-64_rpm.tar.gz cd /home/LibreO…

xtrabackup備份腳本

#!/bin/sh #備份主機 remote_ip100.0.132.160 Master_ip100.20.132.158 VIP100.20.132.166 #備份用戶 userroot #密碼 password00000 # 返回年月日 backup_datedate %F # 返回時分秒 backup_timedate %H-%M-%S # 返回今天是這周的第幾天 backup_week_daydate %u backup_ok0 #備…

Redis系列:使用Redis實現分布式鎖及相關問題

分布式鎖其實就是&#xff0c;控制分布式系統不同進程共同訪問共享資源的一種鎖的實現。如果不同的系統或同一個系統的不同主機之間共享了某個臨界資源&#xff0c;往往需要互斥來防止彼此干擾&#xff0c;以保證一致性。 本篇內容包括&#xff1a;關于 Redis 與 分布式鎖&…

Redis系列:Redis持久化機制與Redis事務

Redis 是個基于內存的數據庫。那服務一旦宕機&#xff0c;內存中數據必將全部丟失。所以丟失數據的恢復對于 Redis 是十分重要的&#xff0c;我們首先想到是可以從數據庫中恢復&#xff0c;但是在由 Redis 宕機時&#xff08;說明相關工作正在運行&#xff09;且數據量很大情況…

Java基礎:Java程序設計環境

按應用范圍&#xff0c;Java 可分為 3 個體系&#xff0c;即 Java SE、Java EE 和 Java ME。Java 語言的開發運行&#xff0c;也離不開 Java 語言的運行環境 JRE。沒有 JRE 的支持&#xff0c;Java 語言便無法運行。當然&#xff0c;如果還想編譯 Java 程序&#xff0c;搞搞小開…