2023-08-06 20:48:54
本篇的知识点是bean的生命周期回调:在bean生命周期的不同阶段,都可以触发自定义代码的执行
触发自定义代码执行的具体方式,是用对应的注解去修饰要执行的方法,如下图所示:
有两种模式可以实现生命周期回调:拦截器模式和自定义模式,接下来通过编码依次学习
已详细介绍了quarkus拦截器的自定义和使用,包括以下三个步骤
如果要自定义bean的生命周期回调,也是遵照上述步骤执行,接下来编码实现
首先定义拦截器,名为TrackLifeCycle,就是个普通拦截器,需要用注解InterceptorBinding修饰
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">package</span> com.bolingcavalry.interceptor.define; <span style="color:#0000ff">import</span> javax.interceptor.InterceptorBinding; <span style="color:#0000ff">import</span> java.lang.annotation.ElementType; <span style="color:#0000ff">import</span> java.lang.annotation.Retention; <span style="color:#0000ff">import</span> java.lang.annotation.RetentionPolicy; <span style="color:#0000ff">import</span> java.lang.annotation.Target; <span style="color:#0000ff">import</span> <span style="color:#0000ff">static</span> java.lang.annotation.ElementType.TYPE; <span style="color:#2b91af">@InterceptorBinding</span> <span style="color:#2b91af">@Target({TYPE, ElementType.METHOD})</span> <span style="color:#2b91af">@Retention(RetentionPolicy.RUNTIME)</span> <span style="color:#0000ff">public</span> <span style="color:#2b91af">@interface</span> TrackLifeCycle { } </code></span></span>
然后是实现拦截器的功能,有几处要注意的地方稍后会提到
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">package</span> com.bolingcavalry.interceptor.impl; <span style="color:#0000ff">import</span> com.bolingcavalry.interceptor.define.TrackLifeCycle; <span style="color:#0000ff">import</span> io.quarkus.arc.Priority; <span style="color:#0000ff">import</span> io.quarkus.logging.Log; <span style="color:#0000ff">import</span> javax.annotation.PostConstruct; <span style="color:#0000ff">import</span> javax.annotation.PreDestroy; <span style="color:#0000ff">import</span> javax.interceptor.AroundConstruct; <span style="color:#0000ff">import</span> javax.interceptor.Interceptor; <span style="color:#0000ff">import</span> javax.interceptor.InvocationContext; <span style="color:#2b91af">@TrackLifeCycle</span> <span style="color:#2b91af">@Interceptor</span> <span style="color:#2b91af">@Priority(Interceptor.Priority.APPLICATION + 1)</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">LifeCycleInterceptor</span> { <span style="color:#2b91af">@AroundConstruct</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">execute</span>(InvocationContext context) <span style="color:#0000ff">throws</span> Exception { Log.info(<span style="color:#a31515">"start AroundConstruct"</span>); <span style="color:#0000ff">try</span> { context.proceed(); } <span style="color:#0000ff">catch</span> (Exception e) { e.printStackTrace(); } Log.info(<span style="color:#a31515">"end AroundConstruct"</span>); } <span style="color:#2b91af">@PostConstruct</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">doPostConstruct</span>(InvocationContext ctx) { Log.info(<span style="color:#a31515">"life cycle PostConstruct"</span>); } <span style="color:#2b91af">@PreDestroy</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">doPreDestroy</span>(InvocationContext ctx) { Log.info(<span style="color:#a31515">"life cycle PreDestroy"</span>); } } </code></span></span>
上述代码有以下几点需要注意
用注解Interceptor和TrackLifeCycle修饰,说明这是拦截器TrackLifeCycle的实现
被拦截bean实例化的时候,AroundConstruct修饰的方法execute就会被执行,这和 一文中的AroundInvoke的用法很相似
被拦截bean创建成功后,PostConstruct修饰的方法doPostConstruct就会被执行
被拦截bean在销毁之前,PreDestroy修饰的方法doPreDestroy就会被执行
接下来是使用拦截器TrackLifeCycle了,用于演示的bean如下,用TrackLifeCycle修饰,有构造方法和简单的helloWorld方法
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#2b91af">@ApplicationScoped</span> <span style="color:#2b91af">@TrackLifeCycle</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">Hello</span> { <span style="color:#0000ff">public</span> <span style="color:#a31515">Hello</span>() { Log.info(<span style="color:#0000ff">this</span>.getClass().getSimpleName() + <span style="color:#a31515">" at instance"</span>); } <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">helloWorld</span>() { Log.info(<span style="color:#a31515">"Hello world!"</span>); } } </code></span></span>
最后再写个单元测试类验证
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#2b91af">@QuarkusTest</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">LifeCycleTest</span> { <span style="color:#2b91af">@Inject</span> Hello hello; <span style="color:#2b91af">@Test</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">testLifyCycle</span>() { hello.helloWorld(); } } </code></span></span>
执行单元测试,控制台输出如下,可见拦截器的日志输出都符合预期
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-shell">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 </code></span></span>
以上就是通过拦截器制作的bean生命周期回调的全过程,接下来再看另一种方式:不用拦截器的方式
刚才的拦截器模式有个明显问题:如果不同bean的生命周期回调有不同业务需求,该如何是好?为每个bean做一个拦截器吗?随着bean的增加会有大量拦截器,似乎不是个好的方案
如果您熟悉spring,对下面的代码要改不陌生,这是来自
的内容,直接在bean的方法上用PostConstruct和PreDestroy修饰,即可在bean的创建完成和销毁前被调用<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">CachingMovieLister</span> { <span style="color:#2b91af">@PostConstruct</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">populateMovieCache</span>() { <span style="color:#008000">// populates the movie cache upon initialization...</span> } <span style="color:#2b91af">@PreDestroy</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">clearMovieCache</span>() { <span style="color:#008000">// clears the movie cache upon destruction...</span> } } </code></span></span>
实际上,quarkus也支持上述方式,不过和拦截器相比有两个差异:
在bean的内部,只能用PostConstruct和TrackLifeCycle,不能用AroundConstruct,只有拦截器才能用AroundConstruct
在拦截器中,PostConstruct和TrackLifeCycle修饰的方法必须要有InvocationContext类型的入参,但是在bean内部则没有此要求
咱们来改造Hello.java的源码,修改后如下,增加了两个方法,分别被PostConstruct和PreDestroy修饰
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#2b91af">@ApplicationScoped</span> <span style="color:#2b91af">@TrackLifeCycle</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">Hello</span> { <span style="color:#0000ff">public</span> <span style="color:#a31515">Hello</span>() { Log.info(<span style="color:#0000ff">this</span>.getClass().getSimpleName() + <span style="color:#a31515">" at instance"</span>); } <span style="color:#2b91af">@PostConstruct</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">doPostConstruct</span>() { Log.info(<span style="color:#a31515">"at doPostConstruct"</span>); } <span style="color:#2b91af">@PreDestroy</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">doPreDestroy</span>() { Log.info(<span style="color:#a31515">"at PreDestroy"</span>); } <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">helloWorld</span>() { Log.info(<span style="color:#a31515">"Hello world!"</span>); } } </code></span></span>
再次运行单元测试,控制台输出如下,可见Hello自定义的两个生命周期回调都执行了,同时原拦截器的三个回调也都正常执行
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">134</span> INFO [io.quarkus] (main) Quarkus <span style="color:#880000">2.7</span><span style="color:#880000">.3</span>.Final on JVM started in <span style="color:#880000">2.</span>529s. Listening on: http:<span style="color:#008000">//localhost:8081</span> <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">135</span> INFO [io.quarkus] (main) Profile test activated. <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">135</span> INFO [io.quarkus] (main) Installed features: [agroal, cdi, narayana-jta, resteasy, smallrye-context-propagation, vertx] <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">147</span> INFO [com.bol.lif.Hello] (main) Hello_ClientProxy at instance <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">710</span> INFO [com.bol.<span style="color:#a31515">int</span>.imp.LifeCycleInterceptor] (main) start AroundConstruct <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">711</span> INFO [com.bol.lif.Hello] (main) Hello_Subclass at instance <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">711</span> INFO [com.bol.<span style="color:#a31515">int</span>.imp.LifeCycleInterceptor] (main) end AroundConstruct <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">711</span> INFO [com.bol.<span style="color:#a31515">int</span>.imp.LifeCycleInterceptor] (main) life cycle PostConstruct <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">712</span> INFO [com.bol.lif.Hello] (main) at doPostConstruct <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">712</span> INFO [com.bol.lif.Hello] (main) Hello world! <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">747</span> INFO [com.bol.<span style="color:#a31515">int</span>.imp.LifeCycleInterceptor] (main) life cycle PreDestroy <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">747</span> INFO [com.bol.lif.Hello] (main) at PreDestroy <span style="color:#880000">16</span>:<span style="color:#880000">27</span>:<span style="color:#880000">54</span>,<span style="color:#880000">765</span> INFO [io.quarkus] (main) Quarkus stopped in <span style="color:#880000">0.</span>044s </code></span></span>
试想这样的场景:我的bean在销毁前要做自定义操作,但是如果用之前的两种方案,可能面临以下问题:
不适合修改bean的代码,bean的类可能是第三方库
也不适合修改生命周期拦截器代码,拦截器可能也是第三方库,也可能是多个bean共用,若修改会影响其他bean
好在quarkus为我们提供了另一个方案,不用修改bean和拦截器的代码,用注解dispose修饰指定方法即可,接下来编码验证
增加一个普通类ResourceManager.java,假设这是业务中的资源管理服务,可以打开和关闭业务资源,稍后会在配置类中将其指定为bean
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">package</span> com.bolingcavalry.service.impl; <span style="color:#0000ff">import</span> io.quarkus.logging.Log; <span style="color:#008000">/** * <span style="color:#808080">@author</span> zq2599@gmail.com * <span style="color:#808080">@Title</span>: 资源管理类 * <span style="color:#808080">@Package</span> * <span style="color:#808080">@Description</span>: * <span style="color:#808080">@date</span> 4/10/22 10:20 AM */</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ResourceManager</span> { <span style="color:#0000ff">public</span> <span style="color:#a31515">ResourceManager</span> () { Log.info(<span style="color:#a31515">"create instance, "</span> + <span style="color:#0000ff">this</span>.getClass().getSimpleName()); } <span style="color:#008000">/** * 假设再次方法中打开资源,如网络、文件、数据库等 */</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">open</span>() { Log.info(<span style="color:#a31515">"open resource here"</span>); } <span style="color:#008000">/** * 假设在此方法中关闭所有已打开的资源 */</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">closeAll</span>() { Log.info(<span style="color:#a31515">"close all resource here"</span>); } } </code></span></span>
配置类SelectBeanConfiguration.java,指定了ResourceManager的生命周期是每次http请求
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">package</span> com.bolingcavalry.config; <span style="color:#0000ff">import</span> com.bolingcavalry.service.impl.ResourceManager; <span style="color:#0000ff">import</span> javax.enterprise.context.RequestScoped; <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">SelectBeanConfiguration</span> { <span style="color:#2b91af">@RequestScoped</span> <span style="color:#0000ff">public</span> ResourceManager <span style="color:#a31515">getResourceManager</span>() { <span style="color:#0000ff">return</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">ResourceManager</span>(); } } </code></span></span>
再写一个web服务类ResourceManagerController.java,这里面使用了ResourceManager
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#0000ff">package</span> com.bolingcavalry; <span style="color:#0000ff">import</span> com.bolingcavalry.service.impl.ResourceManager; <span style="color:#0000ff">import</span> javax.inject.Inject; <span style="color:#0000ff">import</span> javax.ws.rs.GET; <span style="color:#0000ff">import</span> javax.ws.rs.Path; <span style="color:#0000ff">import</span> javax.ws.rs.Produces; <span style="color:#0000ff">import</span> javax.ws.rs.core.MediaType; <span style="color:#2b91af">@Path("/resourcemanager")</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ResourceManagerController</span> { <span style="color:#2b91af">@Inject</span> ResourceManager resourceManager; <span style="color:#2b91af">@GET</span> <span style="color:#2b91af">@Produces(MediaType.TEXT_PLAIN)</span> <span style="color:#0000ff">public</span> String <span style="color:#a31515">get</span>() { resourceManager.open(); <span style="color:#0000ff">return</span> <span style="color:#a31515">"success"</span>; } } </code></span></span>
由于ResourceManager的生命周期是RequestScoped,因此每次请求/resourcemanager都会实例化一个ResourceManager,请求结束后再将其销毁
现在,业务需求是每个ResourceManager的bean在销毁前,都要求其closeAll方法被执行
重点来了,在SelectBeanConfiguration.java中新增一个方法,入参是bean,而且要用Disposes注解修饰,如此,ResourceManager类型的bean在销毁前此方法都会被执行
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#008000">/** * 使用了Disposes注解后,ResourceManager类型的bean在销毁前,此方法都会执行 * <span style="color:#808080">@param</span> resourceManager */</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">closeResource</span>(<span style="color:#2b91af">@Disposes</span> ResourceManager resourceManager) { <span style="color:#008000">// 在这里可以做一些额外的操作,不需要bean参与</span> Log.info(<span style="color:#a31515">"do other things that bean do not care"</span>); <span style="color:#008000">// 也可以执行bean的方法</span> resourceManager.closeAll(); } </code></span></span>
最后是单元测试类DisposeTest.java,这里用了注解RepeatedTest表示重复执行,属性值为3,表示重复执行3次
<span style="color:#393939"><span style="background-color:#faf7ef"><code class="language-java"><span style="color:#2b91af">@QuarkusTest</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">DisposeTest</span> { <span style="color:#2b91af">@RepeatedTest(3)</span> <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">test</span>() { given() .when().get(<span style="color:#a31515">"/resourcemanager"</span>) .then() .statusCode(<span style="color:#880000">200</span>) <span style="color:#008000">// 检查body内容</span> .body(is(<span style="color:#a31515">"success"</span>)); } } </code></span></span>
执行单元测试,控制台输出如下图,可见每次请求都有bean创建,也伴随着bean销毁,每次销毁都会执行closeResource方法,符合预期
至此,生命周期回调相关的实战就完成了,希望能给您一些参考,接下来的文章会继续深入学习依赖注入相关的知识点