Spring?Lifecycle?和?SmartLifecycle區(qū)別面試精講
引言
當(dāng)我們想在 Spring 容器啟動(dòng)或者關(guān)閉的時(shí)候,做一些初始化操作或者對(duì)象銷毀操作,我們可以怎么做?
注意我這里說的是容器啟動(dòng)或者關(guān)閉的時(shí)候,不是某一個(gè) Bean 初始化或者銷毀的時(shí)候~
1. Lifecycle
對(duì)于上面提到的問題,如果小伙伴們稍微研究過 Spring,應(yīng)該是了解其里邊有一個(gè) Lifecycle 接口,通過這個(gè)接口,我們可以在 Spring 容器啟動(dòng)或者關(guān)閉的時(shí)候,做一些自己需要的事情。
我們先來看下 Lifecycle 接口:
public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
這個(gè)接口一共就三個(gè)方法:
- start:?jiǎn)?dòng)組件,該方法在執(zhí)行之前,先調(diào)用 isRunning 方法判斷組件是否已經(jīng)啟動(dòng)了,如果已經(jīng)啟動(dòng)了,就不重復(fù)啟動(dòng)了。
- stop:停止組件,該方法在執(zhí)行之前,先調(diào)用 isRunning 方法判斷組件是否已經(jīng)停止運(yùn)行了,如果已經(jīng)停止運(yùn)行了,就不再重復(fù)停止了。
- isRunning:這個(gè)是返回組件是否已經(jīng)處于運(yùn)行狀態(tài)了,對(duì)于容器來說,只有當(dāng)容器中的所有適用組件都處于運(yùn)行狀態(tài)時(shí),這個(gè)方法返回 true,否則返回 false。
如果我們想自定義一個(gè) Lifecycle,方式如下:
@Component public class MyLifeCycle implements Lifecycle { private volatile boolean running; @Override public void start() { running = true; System.out.println("start"); } @Override public void stop() { running = false; System.out.println("stop"); } @Override public boolean isRunning() { return running; } }
需要自定義一個(gè) running 變量,該變量用來描述當(dāng)前組件是否處于運(yùn)行/停止?fàn)顟B(tài),因?yàn)橄到y(tǒng)在調(diào)用 start 和 stop 方法的時(shí)候,都會(huì)先調(diào)用 isRunning 方法,用以確認(rèn)是否需要真的調(diào)用 start/stop 方法。
接下來創(chuàng)建配置類,掃描上述組件:
@Configuration @ComponentScan public class JavaConfig { }
最后我們啟動(dòng)容器:
public class Demo { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class); ctx.start(); ctx.stop(); } }
啟動(dòng)之后,我們就可以看到控制臺(tái)打印出來的信息:
[外鏈圖片轉(zhuǎn)存中...(img-jWhjCQzV-1697683960574)]
可以看到,在容器啟動(dòng)和停止的時(shí)候,相應(yīng)的方法會(huì)被觸發(fā)。
不過 Lifecycle 有一個(gè)問題,就是必須顯式的調(diào)用 start 或者 stop 方法才會(huì)觸發(fā) Lifecycle 中的方法。當(dāng)然,如果你沒有調(diào)用 stop 方法,而是調(diào)用了 close 方法,那么在 close 方法內(nèi)部也會(huì)觸發(fā) stop 方法。
如果我們想要 start 方法被自動(dòng)觸發(fā)呢?那就得一個(gè)更加智能的 Lifecycle 了--- SmartLifecycle。
2. SmartLifecycle
相比于 LifeCycle,SmartLifecycle 中多了幾個(gè)方法:
public interface SmartLifecycle extends Lifecycle, Phased { int DEFAULT_PHASE = Integer.MAX_VALUE; default boolean isAutoStartup() { return true; } default void stop(Runnable callback) { stop(); callback.run(); } @Override default int getPhase() { return DEFAULT_PHASE; } }
大家看一下,這里首先多了一個(gè) isAutoStartup 方法,這個(gè)方法就表示是否自動(dòng)執(zhí)行 startup 方法,這個(gè)方法返回 true,則 startup 方法會(huì)被自動(dòng)觸發(fā),這個(gè)方法要是返回 false,則 startup 方法就不會(huì)被自動(dòng)觸發(fā)(那么效果就等同于 LifeCycle 了)。
這里多了一個(gè)重載的 stop 方法,這個(gè)重載的 stop 方法會(huì)傳入一個(gè)線程對(duì)象,然后在 stop 中觸發(fā),這個(gè) callback 回調(diào)是為了告訴容器,我們銷毀組件的工作已經(jīng)完成了。如果使用了 SmartLifecycle,那么 Lifecycle 中的 stop 方法就不會(huì)被直接觸發(fā)了,除非我們?cè)?SmartLifecycle#stop 中手動(dòng)去觸發(fā) Lifecycle#stop 方法。
另外這里還有一個(gè) getPhase 方法,這個(gè)當(dāng)存在多個(gè) SmartLifecycle 實(shí)例的時(shí)候,我們需要為其執(zhí)行順序排序,getPhase 方法就是返回執(zhí)行順序,數(shù)字越小,優(yōu)先級(jí)越高,默認(rèn)優(yōu)先級(jí)最小。
我們來寫一個(gè) SmartLifecycle 的案例來試下:
@Component public class MyLifeCycle implements SmartLifecycle { private volatile boolean running; @Override public void start() { running = true; System.out.println("start"); } @Override public void stop() { running = false; System.out.println("stop"); } @Override public boolean isRunning() { return running; } @Override public boolean isAutoStartup() { return SmartLifecycle.super.isAutoStartup(); } @Override public void stop(Runnable callback) { stop(); callback.run(); } @Override public int getPhase() { return 0; } }
3. 原理分析
那么 Lifecycle 到底是如何被觸發(fā)的呢?我們來分析一下源碼。
由于系統(tǒng)中可能存在多個(gè) Lifecycle,因此這多個(gè) Lifecycle 需要一個(gè)統(tǒng)一的管理,這個(gè)管理者就是 LifecycleProcessor,這也是一個(gè)接口,這個(gè)接口中只有兩個(gè)方法:
public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); }
- onRefresh:這個(gè)是在上下文刷新的時(shí)候被觸發(fā),例如在容器啟動(dòng)的時(shí)候這個(gè)方法被觸發(fā)。
- onClose:這個(gè)是在上下文關(guān)閉的時(shí)候被觸發(fā),例如在容器停止運(yùn)行的時(shí)候這個(gè)方法被觸發(fā)。
LifecycleProcessor 只有一個(gè)實(shí)現(xiàn)類 DefaultLifecycleProcessor,所以很好分析,這個(gè) DefaultLifecycleProcessor 中,重寫了上面的 onRefresh 和 onClose 兩個(gè)方法:
@Override public void onRefresh() { startBeans(true); this.running = true; } @Override public void onClose() { stopBeans(); this.running = false; }
3.1 start
小伙伴們看到,在容器啟動(dòng)的時(shí)候,這里會(huì)去調(diào)用 startBeans 方法,在這個(gè)方法中就會(huì)觸發(fā) Lifecycle#start 方法:
private void startBeans(boolean autoStartupOnly) { Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans(); Map<Integer, LifecycleGroup> phases = new TreeMap<>(); lifecycleBeans.forEach((beanName, bean) -> { if (!autoStartupOnly || (bean instanceof SmartLifecycle smartLifecycle && smartLifecycle.isAutoStartup())) { int phase = getPhase(bean); phases.computeIfAbsent( phase, p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly) ).add(beanName, bean); } }); if (!phases.isEmpty()) { phases.values().forEach(LifecycleGroup::start); } }
在這個(gè)方法中,首先調(diào)用 getLifecycleBeans 方法,這個(gè)方法的作用是去 Spring 容器中查找所有 Lifecycle 類型的 Bean,并把查找結(jié)果封裝到一個(gè) Map 集合中返回。
接下來就去遍歷這個(gè) Map,遍歷的時(shí)候由于 autoStartupOnly 變量傳進(jìn)來的時(shí)候是 true,取反之后就是 false 了,所以就會(huì)去判斷這個(gè) Bean 是否為 SmartLifecycle 類型,如果是該類型并且 isAutoStartup 方法返回 true,就表示要自動(dòng)執(zhí)行 start 方法。
如果確定是 SmartLifecycle 類型的 Bean,那么就調(diào)用 getPhase 方法獲取其 phase,這個(gè)表示執(zhí)行的優(yōu)先級(jí),然后將之存入到 phases 集合中,存儲(chǔ)的時(shí)候,phase 是 key,value 則是一個(gè) LifecycleGroup,phases 是一個(gè) TreeMap,小伙伴們知道,TreeMap 是有序的,也就是存入進(jìn)去的數(shù)據(jù),會(huì)自動(dòng)按照 phase 進(jìn)行排序。LifecycleGroup 是將 phase 相同的 SmartLifecycle 分組之后的對(duì)象。
經(jīng)過上面的分析,相信大家已經(jīng)明白了為什么直接實(shí)現(xiàn) Lifecycle 接口,就一定需要手動(dòng)調(diào)用 start 方法(因?yàn)樯厦?if 中的條件不滿足)。
最后就是遍歷 phases,調(diào)用每一個(gè) LifecycleGroup 中的 start 方法。
public void start() { if (this.members.isEmpty()) { return; } Collections.sort(this.members); for (LifecycleGroupMember member : this.members) { doStart(this.lifecycleBeans, member.name, this.autoStartupOnly); } } private void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) { Lifecycle bean = lifecycleBeans.remove(beanName); if (bean != null && bean != this) { String[] dependenciesForBean = getBeanFactory().getDependenciesForBean(beanName); for (String dependency : dependenciesForBean) { doStart(lifecycleBeans, dependency, autoStartupOnly); } if (!bean.isRunning() && (!autoStartupOnly || !(bean instanceof SmartLifecycle smartLifecycle) || smartLifecycle.isAutoStartup())) { bean.start(); } } }
在 doStart 方法中,從集合中取出來 Lifecycle,然后查找一下該 Lifecycle 是否有依賴的 Bean,如果有,就繼續(xù)遞歸調(diào)用 doStart 方法。否則,在 isRunning 返回 false(即該組件還沒有運(yùn)行),且 bean 不是 SmartLifecycle 類型(那就只能是 Lifecycle 類型)或者 bean 是 SmartLifecycle 類型且 isAutoStartup 方法為 true 的情況下,調(diào)用 bean 的 start 方法。
小伙伴們注意,上面的分析是從 onRefresh 方法開始的,該方法中調(diào)用 startBeans 的時(shí)候,傳入的參數(shù)是 true,也就是上面這個(gè)判斷里邊 autoStartupOnly 為 true,取反之后這個(gè)條件就不滿足了,如果是我們手動(dòng)調(diào)用 start 方法的話,這個(gè)參數(shù)默認(rèn)傳入的是 false,取反之后上面這個(gè)條件就滿足了,也就是無論是手動(dòng)還是自動(dòng),最終都是在這個(gè)地方觸發(fā) start 方法的。
3.2 stop
再來看 stop 方法的邏輯。從 onClose 方法開始,也是先調(diào)用 stopBeans 方法:
private void stopBeans() { Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans(); Map<Integer, LifecycleGroup> phases = new HashMap<>(); lifecycleBeans.forEach((beanName, bean) -> { int shutdownPhase = getPhase(bean); LifecycleGroup group = phases.get(shutdownPhase); if (group == null) { group = new LifecycleGroup(shutdownPhase, this.timeoutPerShutdownPhase, lifecycleBeans, false); phases.put(shutdownPhase, group); } group.add(beanName, bean); }); if (!phases.isEmpty()) { List<Integer> keys = new ArrayList<>(phases.keySet()); keys.sort(Collections.reverseOrder()); for (Integer key : keys) { phases.get(key).stop(); } } }
這塊的邏輯跟 start 差不多,就是排序的方案有一些差別。這里用了 HashMap,沒有用 TreeMap,然后在具體調(diào)用的時(shí)候,再去給 key 排序的。
這里調(diào)用到的也是 LifecycleGroup 的 stop 方法,我們來看下:
public void stop() { if (this.members.isEmpty()) { return; } this.members.sort(Collections.reverseOrder()); CountDownLatch latch = new CountDownLatch(this.smartMemberCount); Set<String> countDownBeanNames = Collections.synchronizedSet(new LinkedHashSet<>()); Set<String> lifecycleBeanNames = new HashSet<>(this.lifecycleBeans.keySet()); for (LifecycleGroupMember member : this.members) { if (lifecycleBeanNames.contains(member.name)) { doStop(this.lifecycleBeans, member.name, latch, countDownBeanNames); } else if (member.bean instanceof SmartLifecycle) { // Already removed: must have been a dependent bean from another phase latch.countDown(); } } try { latch.await(this.timeout, TimeUnit.MILLISECONDS); if (latch.getCount() > 0 && !countDownBeanNames.isEmpty() && logger.isInfoEnabled()) { logger.info("Failed to shut down " + countDownBeanNames.size() + " bean" + (countDownBeanNames.size() > 1 ? "s" : "") + " with phase value " + this.phase + " within timeout of " + this.timeout + "ms: " + countDownBeanNames); } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } }
大家看一下,這里的 doStop 方法,最終就會(huì)觸發(fā)到 Lifecycle 的 stop,這個(gè)里邊的代碼簡(jiǎn)單,我們就不去細(xì)看了。需要提醒大家的時(shí)候,這里使用到了這樣一個(gè)計(jì)數(shù)器,初始值就是 members 的數(shù)量,每當(dāng)調(diào)用一個(gè) member 的 stop 方法之后,這個(gè)計(jì)數(shù)器減一,這樣,到下面調(diào)用 await 的時(shí)候,就剛剛好不用等。
await 方法的等待時(shí)間是 this.timeout,這個(gè)屬性默認(rèn)值是 30s,也就是如果 stop 方法在子線程中執(zhí)行,那么執(zhí)行時(shí)間不能超過 30s,否則就會(huì)拋出異常。
如果我們想要自定義這個(gè)超時(shí)時(shí)間,可以自己在 Spring 容器中提供如下 Bean:
@Configuration @ComponentScan public class JavaConfig { @Bean DefaultLifecycleProcessor lifecycleProcessor() { DefaultLifecycleProcessor processor = new DefaultLifecycleProcessor(); processor.setTimeoutPerShutdownPhase(2000); return processor; } }
上面這個(gè)案例中設(shè)置了超時(shí)時(shí)間為 2s。
好啦,這就是關(guān)于 Lifecycle 的整體觸發(fā)流程。
接下來我們來看下自動(dòng)觸發(fā)和手動(dòng)觸發(fā)分別是在哪里觸發(fā)的。
3.3 自動(dòng)觸發(fā)
先來看自動(dòng)觸發(fā)。
經(jīng)過前面的講解,現(xiàn)在小伙伴們都知道,Spring 容器初始化的時(shí)候,會(huì)調(diào)用到 refresh 方法,這個(gè)刷新要做的事情比較多,其中最后一件事情是調(diào)用 finishRefresh 方法,如下:
protected void finishRefresh() { // Clear context-level resource caches (such as ASM metadata from scanning). clearResourceCaches(); // Initialize lifecycle processor for this context. initLifecycleProcessor(); // Propagate refresh to lifecycle processor first. getLifecycleProcessor().onRefresh(); // Publish the final event. publishEvent(new ContextRefreshedEvent(this)); }
這里有兩個(gè)方法跟本文相關(guān),一個(gè)是 initLifecycleProcessor,這個(gè)是初始化 LifecycleProcessor,就是去 Spring 容器中查找 LifecycleProcessor,找到就用,沒找到就創(chuàng)建新的。
然后就是 getLifecycleProcessor().onRefresh(); 方法,這個(gè)就是觸發(fā)了 DefaultLifecycleProcessor#onRefresh 方法,而關(guān)于該方法的邏輯,松哥在前面已經(jīng)介紹過了。
來看下 initLifecycleProcessor 方法是如何做初始化操作的:
protected void initLifecycleProcessor() { ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) { this.lifecycleProcessor = beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class); } else { DefaultLifecycleProcessor defaultProcessor = new DefaultLifecycleProcessor(); defaultProcessor.setBeanFactory(beanFactory); this.lifecycleProcessor = defaultProcessor; beanFactory.registerSingleton(LIFECYCLE_PROCESSOR_BEAN_NAME, this.lifecycleProcessor); } }
大家注意,LIFECYCLE_PROCESSOR_BEAN_NAME 常量的值是 lifecycleProcessor,為什么要強(qiáng)調(diào)這個(gè),如果我們是自定義 DefaultLifecycleProcessor,那么 beanName 必須是 lifecycleProcessor,否則系統(tǒng)會(huì)以為我們沒有自定義 DefaultLifecycleProcessor。
那么這里的邏輯就是如果用戶自定義了 DefaultLifecycleProcessor,那么就使用用戶自定義的 DefaultLifecycleProcessor,否則就創(chuàng)建一個(gè)新的 DefaultLifecycleProcessor,并注冊(cè)到 Spring 容器中。
這就是自動(dòng)觸發(fā)的邏輯。
3.4 手動(dòng)觸發(fā)
手動(dòng)觸發(fā)需要我們自己調(diào)用 start 方法,start 方法如下:
@Override public void start() { getLifecycleProcessor().start(); publishEvent(new ContextStartedEvent(this)); }
相當(dāng)于直接調(diào)用了 DefaultLifecycleProcessor 的 start 方法:
@Override public void start() { startBeans(false); this.running = true; }
這個(gè)跟 DefaultLifecycleProcessor 的 onRefresh 方法內(nèi)容基本一致,唯一的區(qū)別在于調(diào)用 startBeans 的時(shí)候,傳入的參數(shù)為 false,這個(gè)參數(shù)帶來的變化,這個(gè)松哥在前面的內(nèi)容中已經(jīng)分析過了,這里就不再啰嗦啦。
4. 小結(jié)
好啦,這就是松哥和大家分享的 Spring Lifecycle 和 SmartLifecycle 的區(qū)別。老實(shí)說,我們自己開發(fā)需要自定義這兩個(gè)的場(chǎng)景其實(shí)并不多,但是在 Spring Boot 中,SmartLifecycle 的應(yīng)用還是比較多的,有了今天這個(gè)內(nèi)容作基礎(chǔ),將來小伙伴們分析 Spring Boot 的時(shí)候就會(huì)容易很多了。
以上就是Spring Lifecycle 和 SmartLifecycle區(qū)別面試精講的詳細(xì)內(nèi)容,更多關(guān)于Spring Lifecycle區(qū)別SmartLifecycle的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring?Validation參數(shù)效驗(yàn)的各種使用姿勢(shì)總結(jié)
在實(shí)際項(xiàng)目中經(jīng)常需要對(duì)前段傳來的數(shù)據(jù)進(jìn)行校驗(yàn),下面這篇文章主要給大家介紹了關(guān)于Spring?Validation參數(shù)效驗(yàn)的各種使用姿勢(shì),文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04詳解java調(diào)用存儲(chǔ)過程并封裝成map
這篇文章主要介紹了詳解java調(diào)用存儲(chǔ)過程并封裝成map的相關(guān)資料,希望通過本文能幫助到大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-09-09Spring開發(fā)核心之AOP的實(shí)現(xiàn)與切入點(diǎn)持久化
面向?qū)ο缶幊淌且环N編程方式,此編程方式的落地需要使用“類”和 “對(duì)象”來實(shí)現(xiàn),所以,面向?qū)ο缶幊唐鋵?shí)就是對(duì) “類”和“對(duì)象” 的使用,面向切面編程,簡(jiǎn)單的說,就是動(dòng)態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程2022-10-10Java簡(jiǎn)單實(shí)現(xiàn)農(nóng)夫過河問題示例
這篇文章主要介紹了Java簡(jiǎn)單實(shí)現(xiàn)農(nóng)夫過河問題,簡(jiǎn)單描述了農(nóng)夫過河問題的概念、原理并結(jié)合簡(jiǎn)單實(shí)例形式分析了java解決農(nóng)夫過河問題的相關(guān)操作技巧,需要的朋友可以參考下2017-12-12Maven中plugins與pluginManagement的區(qū)別說明
這篇文章主要介紹了Maven中plugins與pluginManagement的區(qū)別說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09