Spring?Lifecycle?和?SmartLifecycle區(qū)別面試精講
引言
當我們想在 Spring 容器啟動或者關(guān)閉的時候,做一些初始化操作或者對象銷毀操作,我們可以怎么做?
注意我這里說的是容器啟動或者關(guān)閉的時候,不是某一個 Bean 初始化或者銷毀的時候~
1. Lifecycle
對于上面提到的問題,如果小伙伴們稍微研究過 Spring,應(yīng)該是了解其里邊有一個 Lifecycle 接口,通過這個接口,我們可以在 Spring 容器啟動或者關(guān)閉的時候,做一些自己需要的事情。
我們先來看下 Lifecycle 接口:
public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
這個接口一共就三個方法:
- start:啟動組件,該方法在執(zhí)行之前,先調(diào)用 isRunning 方法判斷組件是否已經(jīng)啟動了,如果已經(jīng)啟動了,就不重復啟動了。
- stop:停止組件,該方法在執(zhí)行之前,先調(diào)用 isRunning 方法判斷組件是否已經(jīng)停止運行了,如果已經(jīng)停止運行了,就不再重復停止了。
- isRunning:這個是返回組件是否已經(jīng)處于運行狀態(tài)了,對于容器來說,只有當容器中的所有適用組件都處于運行狀態(tài)時,這個方法返回 true,否則返回 false。
如果我們想自定義一個 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; } }
需要自定義一個 running 變量,該變量用來描述當前組件是否處于運行/停止狀態(tài),因為系統(tǒng)在調(diào)用 start 和 stop 方法的時候,都會先調(diào)用 isRunning 方法,用以確認是否需要真的調(diào)用 start/stop 方法。
接下來創(chuàng)建配置類,掃描上述組件:
@Configuration @ComponentScan public class JavaConfig { }
最后我們啟動容器:
public class Demo { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class); ctx.start(); ctx.stop(); } }
啟動之后,我們就可以看到控制臺打印出來的信息:
[外鏈圖片轉(zhuǎn)存中...(img-jWhjCQzV-1697683960574)]
可以看到,在容器啟動和停止的時候,相應(yīng)的方法會被觸發(fā)。
不過 Lifecycle 有一個問題,就是必須顯式的調(diào)用 start 或者 stop 方法才會觸發(fā) Lifecycle 中的方法。當然,如果你沒有調(diào)用 stop 方法,而是調(diào)用了 close 方法,那么在 close 方法內(nèi)部也會觸發(fā) stop 方法。
如果我們想要 start 方法被自動觸發(fā)呢?那就得一個更加智能的 Lifecycle 了--- SmartLifecycle。
2. SmartLifecycle
相比于 LifeCycle,SmartLifecycle 中多了幾個方法:
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; } }
大家看一下,這里首先多了一個 isAutoStartup 方法,這個方法就表示是否自動執(zhí)行 startup 方法,這個方法返回 true,則 startup 方法會被自動觸發(fā),這個方法要是返回 false,則 startup 方法就不會被自動觸發(fā)(那么效果就等同于 LifeCycle 了)。
這里多了一個重載的 stop 方法,這個重載的 stop 方法會傳入一個線程對象,然后在 stop 中觸發(fā),這個 callback 回調(diào)是為了告訴容器,我們銷毀組件的工作已經(jīng)完成了。如果使用了 SmartLifecycle,那么 Lifecycle 中的 stop 方法就不會被直接觸發(fā)了,除非我們在 SmartLifecycle#stop 中手動去觸發(fā) Lifecycle#stop 方法。
另外這里還有一個 getPhase 方法,這個當存在多個 SmartLifecycle 實例的時候,我們需要為其執(zhí)行順序排序,getPhase 方法就是返回執(zhí)行順序,數(shù)字越小,優(yōu)先級越高,默認優(yōu)先級最小。
我們來寫一個 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)中可能存在多個 Lifecycle,因此這多個 Lifecycle 需要一個統(tǒng)一的管理,這個管理者就是 LifecycleProcessor,這也是一個接口,這個接口中只有兩個方法:
public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); }
- onRefresh:這個是在上下文刷新的時候被觸發(fā),例如在容器啟動的時候這個方法被觸發(fā)。
- onClose:這個是在上下文關(guān)閉的時候被觸發(fā),例如在容器停止運行的時候這個方法被觸發(fā)。
LifecycleProcessor 只有一個實現(xiàn)類 DefaultLifecycleProcessor,所以很好分析,這個 DefaultLifecycleProcessor 中,重寫了上面的 onRefresh 和 onClose 兩個方法:
@Override public void onRefresh() { startBeans(true); this.running = true; } @Override public void onClose() { stopBeans(); this.running = false; }
3.1 start
小伙伴們看到,在容器啟動的時候,這里會去調(diào)用 startBeans 方法,在這個方法中就會觸發(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); } }
在這個方法中,首先調(diào)用 getLifecycleBeans 方法,這個方法的作用是去 Spring 容器中查找所有 Lifecycle 類型的 Bean,并把查找結(jié)果封裝到一個 Map 集合中返回。
接下來就去遍歷這個 Map,遍歷的時候由于 autoStartupOnly 變量傳進來的時候是 true,取反之后就是 false 了,所以就會去判斷這個 Bean 是否為 SmartLifecycle 類型,如果是該類型并且 isAutoStartup 方法返回 true,就表示要自動執(zhí)行 start 方法。
如果確定是 SmartLifecycle 類型的 Bean,那么就調(diào)用 getPhase 方法獲取其 phase,這個表示執(zhí)行的優(yōu)先級,然后將之存入到 phases 集合中,存儲的時候,phase 是 key,value 則是一個 LifecycleGroup,phases 是一個 TreeMap,小伙伴們知道,TreeMap 是有序的,也就是存入進去的數(shù)據(jù),會自動按照 phase 進行排序。LifecycleGroup 是將 phase 相同的 SmartLifecycle 分組之后的對象。
經(jīng)過上面的分析,相信大家已經(jīng)明白了為什么直接實現(xiàn) Lifecycle 接口,就一定需要手動調(diào)用 start 方法(因為上面 if 中的條件不滿足)。
最后就是遍歷 phases,調(diào)用每一個 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(即該組件還沒有運行),且 bean 不是 SmartLifecycle 類型(那就只能是 Lifecycle 類型)或者 bean 是 SmartLifecycle 類型且 isAutoStartup 方法為 true 的情況下,調(diào)用 bean 的 start 方法。
小伙伴們注意,上面的分析是從 onRefresh 方法開始的,該方法中調(diào)用 startBeans 的時候,傳入的參數(shù)是 true,也就是上面這個判斷里邊 autoStartupOnly 為 true,取反之后這個條件就不滿足了,如果是我們手動調(diào)用 start 方法的話,這個參數(shù)默認傳入的是 false,取反之后上面這個條件就滿足了,也就是無論是手動還是自動,最終都是在這個地方觸發(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)用的時候,再去給 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 方法,最終就會觸發(fā)到 Lifecycle 的 stop,這個里邊的代碼簡單,我們就不去細看了。需要提醒大家的時候,這里使用到了這樣一個計數(shù)器,初始值就是 members 的數(shù)量,每當調(diào)用一個 member 的 stop 方法之后,這個計數(shù)器減一,這樣,到下面調(diào)用 await 的時候,就剛剛好不用等。
await 方法的等待時間是 this.timeout,這個屬性默認值是 30s,也就是如果 stop 方法在子線程中執(zhí)行,那么執(zhí)行時間不能超過 30s,否則就會拋出異常。
如果我們想要自定義這個超時時間,可以自己在 Spring 容器中提供如下 Bean:
@Configuration @ComponentScan public class JavaConfig { @Bean DefaultLifecycleProcessor lifecycleProcessor() { DefaultLifecycleProcessor processor = new DefaultLifecycleProcessor(); processor.setTimeoutPerShutdownPhase(2000); return processor; } }
上面這個案例中設(shè)置了超時時間為 2s。
好啦,這就是關(guān)于 Lifecycle 的整體觸發(fā)流程。
接下來我們來看下自動觸發(fā)和手動觸發(fā)分別是在哪里觸發(fā)的。
3.3 自動觸發(fā)
先來看自動觸發(fā)。
經(jīng)過前面的講解,現(xiàn)在小伙伴們都知道,Spring 容器初始化的時候,會調(diào)用到 refresh 方法,這個刷新要做的事情比較多,其中最后一件事情是調(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)); }
這里有兩個方法跟本文相關(guān),一個是 initLifecycleProcessor,這個是初始化 LifecycleProcessor,就是去 Spring 容器中查找 LifecycleProcessor,找到就用,沒找到就創(chuàng)建新的。
然后就是 getLifecycleProcessor().onRefresh(); 方法,這個就是觸發(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,為什么要強調(diào)這個,如果我們是自定義 DefaultLifecycleProcessor,那么 beanName 必須是 lifecycleProcessor,否則系統(tǒng)會以為我們沒有自定義 DefaultLifecycleProcessor。
那么這里的邏輯就是如果用戶自定義了 DefaultLifecycleProcessor,那么就使用用戶自定義的 DefaultLifecycleProcessor,否則就創(chuàng)建一個新的 DefaultLifecycleProcessor,并注冊到 Spring 容器中。
這就是自動觸發(fā)的邏輯。
3.4 手動觸發(fā)
手動觸發(fā)需要我們自己調(diào)用 start 方法,start 方法如下:
@Override public void start() { getLifecycleProcessor().start(); publishEvent(new ContextStartedEvent(this)); }
相當于直接調(diào)用了 DefaultLifecycleProcessor 的 start 方法:
@Override public void start() { startBeans(false); this.running = true; }
這個跟 DefaultLifecycleProcessor 的 onRefresh 方法內(nèi)容基本一致,唯一的區(qū)別在于調(diào)用 startBeans 的時候,傳入的參數(shù)為 false,這個參數(shù)帶來的變化,這個松哥在前面的內(nèi)容中已經(jīng)分析過了,這里就不再啰嗦啦。
4. 小結(jié)
好啦,這就是松哥和大家分享的 Spring Lifecycle 和 SmartLifecycle 的區(qū)別。老實說,我們自己開發(fā)需要自定義這兩個的場景其實并不多,但是在 Spring Boot 中,SmartLifecycle 的應(yīng)用還是比較多的,有了今天這個內(nèi)容作基礎(chǔ),將來小伙伴們分析 Spring Boot 的時候就會容易很多了。
以上就是Spring Lifecycle 和 SmartLifecycle區(qū)別面試精講的詳細內(nèi)容,更多關(guān)于Spring Lifecycle區(qū)別SmartLifecycle的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring?Validation參數(shù)效驗的各種使用姿勢總結(jié)
在實際項目中經(jīng)常需要對前段傳來的數(shù)據(jù)進行校驗,下面這篇文章主要給大家介紹了關(guān)于Spring?Validation參數(shù)效驗的各種使用姿勢,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-04-04Spring開發(fā)核心之AOP的實現(xiàn)與切入點持久化
面向?qū)ο缶幊淌且环N編程方式,此編程方式的落地需要使用“類”和 “對象”來實現(xiàn),所以,面向?qū)ο缶幊唐鋵嵕褪菍?nbsp;“類”和“對象” 的使用,面向切面編程,簡單的說,就是動態(tài)地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程2022-10-10Maven中plugins與pluginManagement的區(qū)別說明
這篇文章主要介紹了Maven中plugins與pluginManagement的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09