### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本篇的知識點是bean的生命周期回調:在 ...
歡迎訪問我的GitHub
這裡分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos
本篇概覽
- 本篇的知識點是bean的生命周期回調:在bean生命周期的不同階段,都可以觸發自定義代碼的執行
- 觸發自定義代碼執行的具體方式,是用對應的註解去修飾要執行的方法,如下圖所示:

- 有兩種模式可以實現生命周期回調:攔截器模式和自定義模式,接下來通過編碼依次學習
攔截器模式
- 《攔截器(Interceptor)》已詳細介紹了quarkus攔截器的自定義和使用,包括以下三個步驟

- 如果要自定義bean的生命周期回調,也是遵照上述步驟執行,接下來編碼實現
- 首先定義攔截器,名為TrackLifeCycle,就是個普通攔截器,需要用註解InterceptorBinding修飾
package com.bolingcavalry.interceptor.define;
import javax.interceptor.InterceptorBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
@InterceptorBinding
@Target({TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackLifeCycle {
}
- 然後是實現攔截器的功能,有幾處要註意的地方稍後會提到
package com.bolingcavalry.interceptor.impl;
import com.bolingcavalry.interceptor.define.TrackLifeCycle;
import io.quarkus.arc.Priority;
import io.quarkus.logging.Log;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.interceptor.AroundConstruct;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
@TrackLifeCycle
@Interceptor
@Priority(Interceptor.Priority.APPLICATION + 1)
public class LifeCycleInterceptor {
@AroundConstruct
void execute(InvocationContext context) throws Exception {
Log.info("start AroundConstruct");
try {
context.proceed();
} catch (Exception e) {
e.printStackTrace();
}
Log.info("end AroundConstruct");
}
@PostConstruct
public void doPostConstruct(InvocationContext ctx) {
Log.info("life cycle PostConstruct");
}
@PreDestroy
public void doPreDestroy(InvocationContext ctx) {
Log.info("life cycle PreDestroy");
}
}
- 上述代碼有以下幾點需要註意
- 用註解Interceptor和TrackLifeCycle修飾,說明這是攔截器TrackLifeCycle的實現
- 被攔截bean實例化的時候,AroundConstruct修飾的方法execute就會被執行,這和《攔截器》一文中的AroundInvoke的用法很相似
- 被攔截bean創建成功後,PostConstruct修飾的方法doPostConstruct就會被執行
- 被攔截bean在銷毀之前,PreDestroy修飾的方法doPreDestroy就會被執行
- 接下來是使用攔截器TrackLifeCycle了,用於演示的bean如下,用TrackLifeCycle修飾,有構造方法和簡單的helloWorld方法
@ApplicationScoped
@TrackLifeCycle
public class Hello {
public Hello() {
Log.info(this.getClass().getSimpleName() + " at instance");
}
public void helloWorld() {
Log.info("Hello world!");
}
}
- 最後再寫個單元測試類驗證
@QuarkusTest
public class LifeCycleTest {
@Inject
Hello hello;
@Test
public void testLifyCycle() {
hello.helloWorld();
}
}
- 執行單元測試,控制台輸出如下,可見攔截器的日誌輸出都符合預期
15:26:32,447 INFO [io.quarkus] (main) Quarkus 2.7.3.Final on JVM started in 2.899s. Listening on: http://localhost:8081
15:26:32,448 INFO [io.quarkus] (main) Profile test activated.
15:26:32,448 INFO [io.quarkus] (main) Installed features: [agroal, cdi, narayana-jta, resteasy, smallrye-context-propagation, vertx]
15:26:32,483 INFO [com.bol.lif.Hello] (main) Hello_ClientProxy at instance
15:26:33,040 INFO [com.bol.int.imp.LifeCycleInterceptor] (main) start AroundConstruct
15:26:33,040 INFO [com.bol.lif.Hello] (main) Hello_Subclass at instance
15:26:33,040 INFO [com.bol.int.imp.LifeCycleInterceptor] (main) end AroundConstruct
15:26:33,041 INFO [com.bol.int.imp.LifeCycleInterceptor] (main) life cycle PostConstruct
15:26:33,041 INFO [com.bol.lif.Hello] (main) Hello world!
15:26:33,097 INFO [com.bol.int.imp.LifeCycleInterceptor] (main) life cycle PreDestroy
15:26:33,128 INFO [io.quarkus] (main) Quarkus stopped in 0.075s
- 以上就是通過攔截器製作的bean生命周期回調的全過程,接下來再看另一種方式:不用攔截器的方式
自定義模式
- 剛纔的攔截器模式有個明顯問題:如果不同bean的生命周期回調有不同業務需求,該如何是好?為每個bean做一個攔截器嗎?隨著bean的增加會有大量攔截器,似乎不是個好的方案
- 如果您熟悉spring,對下麵的代碼要改不陌生,這是來自spring官網的內容,直接在bean的方法上用PostConstruct和PreDestroy修飾,即可在bean的創建完成和銷毀前被調用
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
- 實際上,quarkus也支持上述方式,不過和攔截器相比有兩個差異:
- 在bean的內部,只能用PostConstruct和TrackLifeCycle,不能用AroundConstruct,只有攔截器才能用AroundConstruct
- 在攔截器中,PostConstruct和TrackLifeCycle修飾的方法必須要有InvocationContext類型的入參,但是在bean內部則沒有此要求
- 咱們來改造Hello.java的源碼,修改後如下,增加了兩個方法,分別被PostConstruct和PreDestroy修飾
@ApplicationScoped
@TrackLifeCycle
public class Hello {
public Hello() {
Log.info(this.getClass().getSimpleName() + " at instance");
}
@PostConstruct
public void doPostConstruct() {
Log.info("at doPostConstruct");
}
@PreDestroy
public void doPreDestroy() {
Log.info("at PreDestroy");
}
public void helloWorld() {
Log.info("Hello world!");
}
}
- 再次運行單元測試,控制台輸出如下,可見Hello自定義的兩個生命周期回調都執行了,同時原攔截器的三個回調也都正常執行
16:27:54,134 INFO [io.quarkus] (main) Quarkus 2.7.3.Final on JVM started in 2.529s. Listening on: http://localhost:8081
16:27:54,135 INFO [io.quarkus] (main) Profile test activated.
16:27:54,135 INFO [io.quarkus] (main) Installed features: [agroal, cdi, narayana-jta, resteasy, smallrye-context-propagation, vertx]
16:27:54,147 INFO [com.bol.lif.Hello] (main) Hello_ClientProxy at instance
16:27:54,710 INFO [com.bol.int.imp.LifeCycleInterceptor] (main) start AroundConstruct
16:27:54,711 INFO [com.bol.lif.Hello] (main) Hello_Subclass at instance
16:27:54,711 INFO [com.bol.int.imp.LifeCycleInterceptor] (main) end AroundConstruct
16:27:54,711 INFO [com.bol.int.imp.LifeCycleInterceptor] (main) life cycle PostConstruct
16:27:54,712 INFO [com.bol.lif.Hello] (main) at doPostConstruct
16:27:54,712 INFO [com.bol.lif.Hello] (main) Hello world!
16:27:54,747 INFO [com.bol.int.imp.LifeCycleInterceptor] (main) life cycle PreDestroy
16:27:54,747 INFO [com.bol.lif.Hello] (main) at PreDestroy
16:27:54,765 INFO [io.quarkus] (main) Quarkus stopped in 0.044s
dispose註解:實現銷毀前自定義操作,dispose是另一種可選方案
- 試想這樣的場景:我的bean在銷毀前要做自定義操作,但是如果用之前的兩種方案,可能面臨以下問題:
- 不適合修改bean的代碼,bean的類可能是第三方庫
- 也不適合修改生命周期攔截器代碼,攔截器可能也是第三方庫,也可能是多個bean共用,若修改會影響其他bean
- 好在quarkus為我們提供了另一個方案,不用修改bean和攔截器的代碼,用註解dispose修飾指定方法即可,接下來編碼驗證
- 增加一個普通類ResourceManager.java,假設這是業務中的資源管理服務,可以打開和關閉業務資源,稍後會在配置類中將其指定為bean
package com.bolingcavalry.service.impl;
import io.quarkus.logging.Log;
/**
* @author [email protected]
* @Title: 資源管理類
* @Package
* @Description:
* @date 4/10/22 10:20 AM
*/
public class ResourceManager {
public ResourceManager () {
Log.info("create instance, " + this.getClass().getSimpleName());
}
/**
* 假設再次方法中打開資源,如網路、文件、資料庫等
*/
public void open() {
Log.info("open resource here");
}
/**
* 假設在此方法中關閉所有已打開的資源
*/
public void closeAll() {
Log.info("close all resource here");
}
}
- 配置類SelectBeanConfiguration.java,指定了ResourceManager的生命周期是每次http請求
package com.bolingcavalry.config;
import com.bolingcavalry.service.impl.ResourceManager;
import javax.enterprise.context.RequestScoped;
public class SelectBeanConfiguration {
@RequestScoped
public ResourceManager getResourceManager() {
return new ResourceManager();
}
}
- 再寫一個web服務類ResourceManagerController.java,這裡面使用了ResourceManager
package com.bolingcavalry;
import com.bolingcavalry.service.impl.ResourceManager;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/resourcemanager")
public class ResourceManagerController {
@Inject
ResourceManager resourceManager;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String get() {
resourceManager.open();
return "success";
}
}
- 由於ResourceManager的生命周期是RequestScoped,因此每次請求/resourcemanager都會實例化一個ResourceManager,請求結束後再將其銷毀
- 現在,業務需求是每個ResourceManager的bean在銷毀前,都要求其closeAll方法被執行
- 重點來了,在SelectBeanConfiguration.java中新增一個方法,入參是bean,而且要用Disposes註解修飾,如此,ResourceManager類型的bean在銷毀前此方法都會被執行
/**
* 使用了Disposes註解後,ResourceManager類型的bean在銷毀前,此方法都會執行
* @param resourceManager
*/
public void closeResource(@Disposes ResourceManager resourceManager) {
// 在這裡可以做一些額外的操作,不需要bean參與
Log.info("do other things that bean do not care");
// 也可以執行bean的方法
resourceManager.closeAll();
}
- 最後是單元測試類DisposeTest.java,這裡用了註解RepeatedTest表示重覆執行,屬性值為3,表示重覆執行3次
@QuarkusTest
public class DisposeTest {
@RepeatedTest(3)
public void test() {
given()
.when().get("/resourcemanager")
.then()
.statusCode(200)
// 檢查body內容
.body(is("success"));
}
}
- 執行單元測試,控制台輸出如下圖,可見每次請求都有bean創建,也伴隨著bean銷毀,每次銷毀都會執行closeResource方法,符合預期
- 至此,生命周期回調相關的實戰就完成了,希望能給您一些參考,接下來的文章會繼續深入學習依賴註入相關的知識點