quarkus依赖注入之十:学习和改变bean懒加载规则

打印 上一主题 下一主题

主题 919|帖子 919|积分 2757

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览


  • 本篇是《quarkus依赖注入》系列的第十篇,来看一个容易被忽略的知识点:bean的懒加载,咱们先去了解quarkus框架下的懒加载规则,然后更重要的是掌握如何改变规则,以达到提前实例化的目标
  • 总的来说本篇由以下内容构成

  • 关于懒加载
  • 编码体验懒加载
  • 改变懒加载规则的第一种手段
  • 改变懒加载规则的第二种手段(居然和官方资料有出入)
  • 小结
关于懒加载(Lazy Instantiation


  • CDI规范下的懒加载规则:

  • 常规作用域的bean(例如ApplicationScoped、RequestScoped),在注入时,实例化的是其代理类,而真实类的实例化发生在bean方法被首次调用的时候
  • 伪作用域的bean(Dependent和Singleton),在注入时就会实例化


  • quarkus也遵循此规则,接下来编码验证
编码验证懒加载


  • 为了验证bean的懒加载,接下来会写这样一些代码

  • NormalApplicationScoped.java:作用域是ApplicationScoped的bean,其构造方法中打印日志,带有自己的类名
  • NormalSingleton.java:作用域是Singleton的bean,其构造方法中打印日志,带有自己的类名
  • ChangeLazyLogicTest.java:这是个单元测试类,里面注入了NormalApplicationScoped和NormalSingleton的bean,在其ping方法中依次调用上面两个bean的方法


  • 以上就是稍后要写的代码,咱们根据刚刚提到的懒加载规则预测一下要输出的内容和顺序:

  • 首先,在ChangeLazyLogicTest的注入点,NormalSingleton会实例化,NormalApplicationScoped的代理类会实例化
  • 然后,在ChangeLazyLogicTest#ping方法中,由于调用了NormalApplicationScoped的方法,会导致NormalApplicationScoped的实例化


  • 接下来开始写代码,第一个bean,NormalApplicationScoped.java
  1. package com.bolingcavalry;
  2. import com.bolingcavalry.service.impl.NormalApplicationScoped;
  3. import com.bolingcavalry.service.impl.NormalSingleton;
  4. import io.quarkus.logging.Log;
  5. import io.quarkus.test.junit.QuarkusTest;
  6. import org.junit.jupiter.api.Test;
  7. import javax.inject.Inject;
  8. @QuarkusTest
  9. class ChangeLazyLogicTest {
  10.     @Inject
  11.     NormalSingleton normalSingleton;
  12.     @Inject
  13.     NormalApplicationScoped normalApplicationScoped;
  14.     @Test
  15.     void ping() {
  16.         Log.info("start invoke normalSingleton.ping");
  17.         normalSingleton.ping();
  18.         Log.info("start invoke normalApplicationScoped.ping");
  19.         normalApplicationScoped.ping();
  20.     }
  21. }
复制代码

  • 第二个bean,NormalSingleton.java
  1. package com.bolingcavalry.service.impl;
  2. import io.quarkus.logging.Log;
  3. import javax.inject.Singleton;
  4. @Singleton
  5. public class NormalSingleton {
  6.     public NormalSingleton() {
  7.         Log.info("Construction from " + this.getClass().getSimpleName());
  8.     }
  9.     public String ping() {
  10.         return "ping from NormalSingleton";
  11.     }
  12. }
复制代码

  • 然后是单元测试类ChangeLazyLogicTest,可见NormalApplicationScoped构造方法的日志应该在start invoke normalApplicationScoped.ping这一段之后
  1. package com.bolingcavalry;
  2. import com.bolingcavalry.service.impl.NormalApplicationScoped;
  3. import com.bolingcavalry.service.impl.NormalSingleton;
  4. import io.quarkus.logging.Log;
  5. import io.quarkus.test.junit.QuarkusTest;
  6. import org.junit.jupiter.api.Test;
  7. import javax.inject.Inject;
  8. @QuarkusTest
  9. class ChangeLazyLogicTest {
  10.     @Inject
  11.     NormalSingleton normalSingleton;
  12.     @Inject
  13.     NormalApplicationScoped normalApplicationScoped;
  14.     @Test
  15.     void ping() {
  16.         Log.info("start invoke normalSingleton.ping");
  17.         normalSingleton.ping();
  18.         Log.info("start invoke normalApplicationScoped.ping");
  19.         normalApplicationScoped.ping();
  20.     }
  21. }
复制代码

  • 编码完成,运行单元测试类,验证我们之前的预测,控制台输出结果如下图所示,符合预期


  • 至此,懒加载基本规则咱们已经清楚了,聪明的您应该想到了此规则的弊端:如果在构造方法中有一些耗时操作,必须等到第一次调用bean的方法时才会执行,这可能不符合我们的预期,有时候我们希望应用初始化的时候把耗时的事情做完,这样执行bean方法的时候就没有影响了
  • 显然,quarkus也意识到了这个问题,于是,给出了两中改变懒加载规则的方法,使得bean的实例化可以更早完成,接下来咱们逐个尝试
改变懒加载规则的第一种手段


  • 让bean尽早实例化的第一种手段,是让bean消费StartupEvent事件,这是quarkus框架启动成功后发出的事件,从时间上来看,此事件的时间比注入bean的时间还要早,这样消费事件的bean就会实例化
  • 咱们给NormalApplicationScoped增加下图红框中的代码,让它消费StartupEvent事件


  • 运行代码前,先预测一下修改后的结果

  • 首先应该是NormalApplicationScoped的实例化
  • NormalApplicationScoped实例收到StarttupEvent事件,打印日志
  • 开始注入bean到ChangeLazyLogicTest,引发NormalApplicationScoped代理类和NormalSingleton的实例化
  • 简单地说:原本最晚实例化的NormalApplicationScoped,由于消费StarttupEvent事件,现在变成了最早实例化的


  • 现在运行代码验证,如下图,符合预期
改变懒加载规则的第二种手段(居然和官方资料有出入)


  • 第二种方法更简单了:用StartupEvent修饰类,下图是完整NormalApplicationScoped代码,可见改动仅有红框位置


  • 在运行代码前,先预测一下运行结果,理论上应该和第一种手段的结果差不多:NormalApplicationScoped、NormalApplicationScoped代理、NormalSingleton,
  • 上述推测的依据来自Startup源码中的注释,如下图,官方表示StartupEvent和Startup效果一致


  • 官方都这么说了,我岂敢不信,不过流程还是要完成的,把修改后的代码再运行一遍,截个图贴到文中,走走过场...
  • 然而,这次运行的结果,却让人精神一振,StartupEvent和Startup效果是不一样的!!!
  • 运行结果如下图,最先实例化的居然不是被Startup注解修饰的NormalApplicationScoped,而是它的代理类!


  • 由此可见,Startup可以将bean的实例化提前,而且是连带bean的代理类的实例化也提前了
  • 回想一下,虽然结果与预期不符合,而预期来自官方注释,但这并不代表官方注释有错,人家只说了句functionally equivalent,从字面上看并不涉及代理类的实例化
  • 另外Startup也有自己的独特之处,一共有以下两点

  • Startup注解的value属性值,是bean的优先级,这样,多个bean都使用Startup的时候,可以通过value值设置优先级,以此控制实例化顺序(实际上控制的是事件observer的创建顺序)
  • 如果一个类只有Startup注解修饰,而没有设置作用域的时候,quarkus自动将其作用域设置为ApplicationScoped,也就是说,下面这段代码中,ApplicationScoped注解写不写都一样
  1. @ApplicationScoped
  2. @Startup
  3. public class NormalApplicationScoped {
复制代码
小结


  • 懒加载、StartupEvent、Startup这三种情况下的实例化顺序各不相同,最好是有个对比让大家一目了然,方便选择使用
  • 接下来就画个对比图,图中有懒加载、StartupEvent、Startup三个场景,每个场景都是三个阶段:quarkus框架初始化、注入bean、bean的方法被调用,每个阶段都有哪些对象被实例化就是它们最大的区别,如下所示


  • 至此,懒加载相关的知识点学习完毕,个人认为这是个很重要的技能,用好了它对业务有不小的助力,希望能给您一些参考吧
欢迎关注博客园:程序员欣宸

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

民工心事

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表