quarkus依赖注入之七:生命周期回调

2023年 7月 14日 47.6k 0

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):github.com/zq2599/blog…

本篇概览

  • 本篇的知识点是bean的生命周期回调:在bean生命周期的不同阶段,都可以触发自定义代码的执行
  • 触发自定义代码执行的具体方式,是用对应的注解去修饰要执行的方法,如下图所示:

流程图 - 2022-04-05T094019.781

  • 有两种模式可以实现生命周期回调:拦截器模式和自定义模式,接下来通过编码依次学习

拦截器模式

  • 《拦截器(Interceptor)》已详细介绍了quarkus拦截器的自定义和使用,包括以下三个步骤

流程图 (19)

  • 如果要自定义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 zq2599@gmail.com
     * @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方法,符合预期

    image-20220410173202641

    • 至此,生命周期回调相关的实战就完成了,希望能给您一些参考,接下来的文章会继续深入学习依赖注入相关的知识点

    欢迎关注掘金:程序员欣宸

    学习路上,你不孤单,欣宸原创一路相伴...

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论