quarkus依赖注入之十三:其他重要知识点大串讲(终篇)
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- 本篇是《quarkus依赖注入》系列的终篇,前面十二篇已覆盖quarkus依赖注入的大部分核心内容,但依然漏掉了一些知识点,今天就将剩下的内容汇总,来个一锅端,轻松愉快的结束这个系列
- 总的来说,本篇由以下内容构成,每个段落都是个独立的知识点
- 几处可以简化编码的地方,如bean注入、构造方法等
- WithCaching:特定场景下,减少bean实例化次数
- 静态方法是否可以被拦截器拦截?
- All注解,让多个bean的注入更加直观
- 统一处理异步事件的异常
- 咱们从最简单的看起:表达方式的简化,一共有三个位置可以简化:bean的注入、bean构造方法、bean生产方法
简化之一:bean注入
quarkus在CDI规范的基础上做了简化,可以让我们少写几行代码
将配置文件中名为greeting.message的配置项注入到bean的成员变量greetingMsg中,按照CDI规范的写法如下
@Inject
@ConfigProperty(name = "greeting.message")
String greetingMsg;
- 在quarkus框架下可以略去@Inject,写成下面这样的效果和上面的代码一模一样
@ConfigProperty(name = "greeting.message")
String greetingMsg;
简化之二:bean构造方法
- 关于bean的构造方法,CDI有两个规定:首先,必须要有无参构造方法,其次,有参数的构造方法需要@Inject注解修饰,实例代码如下所示
@ApplicationScoped
public class MyCoolService {
private SimpleProcessor processor;
MyCoolService() { // dummy constructor needed
}
@Inject // constructor injection
MyCoolService(SimpleProcessor processor) {
this.processor = processor;
}
}
- 但是,在quarkus框架下,无参构造方法可不写,有参数的构造方法也可以略去@Inject,写成下面这样的效果和上面的代码一模一样
@ApplicationScoped
public class MyCoolService {
private SimpleProcessor processor;
MyCoolService(SimpleProcessor processor) {
this.processor = processor;
}
}
简化之三:bean生产方法
- 在CDI规范中,通过方法生产bean的语法如下,可见要同时使用Produces和ApplicationScoped注解修饰返回bean的方法
class Producers {
@Produces
@ApplicationScoped
MyService produceServ
ice() {
return new MyService(coolProperty);
}
}
- 在quarkus框架下可以略去@Produces,写成下面这样的效果和上面的代码一模一样
class Producers {
@ApplicationScoped
MyService produceService() {
return new MyService(coolProperty);
}
}
- 好了,热身结束,接下来看几个略有深度的技能
WithCaching注解:避免不必要的多次实例化
- 在介绍WithCaching注解之前,先来看一个普通场景
- 下面是一段单元测试代码,HelloDependent类型的bean通过Instance的方式被注入,再用Instance#get来获取此bean
@QuarkusTest
public class WithCachingTest {
@Inject
Instance<HelloDependent> instance;
@Test
public void test() {
// 第一次调用Instance#get方法
HelloDependent helloDependent = instance.get();
helloDependent.hello();
// 第二次调用Instance#get方法
helloDependent = instance.get();
helloDependent.hello();
}
}
上述代码是种常见的bean注入和使用方式,我们的本意是在WithCachingTest实例中多次使用HelloDependent类型的bean,可能是在test方法中使用,也可能在WithCachingTest的其他方法中使用
如果HelloDependent的作用域是ApplicationScoped,上述代码一切正常,但是,如果作用域是Dependent呢?代码中执行了两次Instance#get,得到的HelloDependent实例是同一个吗?Dependent的特性是每次注入都实例化一次,这里的Instance#get又算几次注入呢?
最简单的方法就是运行上述代码看实际效果,这里先回顾HelloDependent.java的源码,如下所示,构造方法中会打印日志,这下好办了,只要看日志出现几次,就知道实例化几次了
@Dependent
public class HelloDependent {
public HelloDependent(InjectionPoint injectionPoint) {
Log.info("injecting from bean "+ injectionPoint.getMember().getDeclaringClass());
}
public String hello() {
return this.getClass().getSimpleName();
}
}
- 运行单元测试类WithCachingTest,如下图红框所示,构造方法中的日志打印了两次,所以:每次Instance#get都相当于一次注入,如果bean的作用域是Dependent,就会创建一个新的实例并返回
- 现在问题来了:如果bean的作用域必须是Dependent,又希望多次Instance#get返回的是同一个bean实例,这样的要求可以做到吗?
- 答案是可以,用WithCaching注解修饰Instance即可,改动如下图红框1,改好后再次运行,红框2显示HelloDependent只实例化了一次
拦截静态方法
- 先回顾一下拦截器的基本知识,定义一个拦截器并用来拦截bean中的方法,总共需要完成以下三步
- 实现拦截器的具体功能时,还要用注解指明拦截器类型,一共有四种类型
- AroundInvoke:拦截bean方法
- PostConstruct:生命周期拦截器,bean创建后执行
- PreDestroy:生命周期拦截器,bean销毁前执行
- AroundConstruct:生命周期拦截器,拦截bean构造方法
- 现在问题来了:拦截器能拦截静态方法吗?
- 答案是可以,但是有限制,具体的限制如下
- 仅支持方法级别的拦截(即拦截器修饰的是方法)
- private型的静态方法不会被拦截
- 下图是拦截器实现的常见代码,通过入参InvocationContext的getTarget方法,可以得到被拦截的对象,然而,在拦截静态方法时,getTarget方法的返回值是null,这一点尤其要注意,例如下图红框中的代码,在拦截静态方法是就会抛出空指针异常
All更加直观的注入
- 假设有个名为SayHello的接口,源码如下
public interface SayHello {
void hello();
}
现在有三个bean都实现了SayHello接口,如果想要调用这三个bean的hello方法,应该怎么做呢?
按照CDI的规范,应该用Instance注入,然后使用Instance中的迭代器即可获取所有bean,代码如下
public class InjectAllTest {
/**
* 用Instance接收注入,得到所有SayHello类型的bean
*/
@Inject
Instance<SayHello> instance;
@Test
public void testInstance() {
// instance中有迭代器,可以用遍历的方式得到所有bean
for (SayHello sayHello : instance) {
sayHello.hello();
}
}
}
- quarkus提供了另一种方式,借助注解io.quarkus.arc.All,可以将所有SayHello类型的bean注入到List中,如下所示
@QuarkusTest
public class InjectAllTest {
/**
* 用All注解可以将SayHello类型的bean全部注入到list中,
* 这样更加直观
*/
@All
List<SayHello> list;
@Test
public void testAll() {
for (SayHello sayHello : list) {
sayHello.hello();
}
}
}
- 和CDI规范相比,使用All注解可以让代码显得更为直观,另外还有以下三个特点
此list是immutable的(内容不可变)
list中的bean是按照priority排序的
如果您需要的不仅仅是注入bean,还需要bean的元数据信息(例如bean的scope),可以将List中的类型从SayHello改为InstanceHandle<SayHello>,这样即可以得到注入bean,也能得到注入bean的元数据(在InjectableBean中),参考代码如下
@QuarkusTest
public class InjectAllTest {
@All
List<InstanceHandle<SayHello>> list;
@Test
public void testQuarkusAllAnnonation() {
for (InstanceHandle<SayHello> instanceHandle : list) {
// InstanceHandle#get可以得到注入bean
SayHello sayHello = instanceHandle.get();
// InjectableBean封装了注入bean的元数据信息
InjectableBean<SayHello> injectableBean = instanceHandle.getBean();
// 例如bean的作用域就能从InjectableBean中取得
Class clazz = injectableBean.getScope();
// 打印出来验证
Log.infov("bean [{0}], scope [{1}]", sayHello.getClass().getSimpleName(), clazz.getSimpleName() );
}
}
}
- 代码的执行结果如下图红框所示,可见注入bean及其作用域都能成功取得(要注意的是注入bean是代理bean)
统一处理异步事件的异常
需要提前说一下,本段落涉及的知识点和AsyncObserverExceptionHandler类有关,而《quarkus依赖注入》系列所用的quarkus-2.7.3.Final版本中并没有AsyncObserverExceptionHandler类,后来将quarkus版本更新为2.8.2.Final,就可以正常使用AsyncObserverExceptionHandler类了
本段落的知识点和异步事件有关:如果消费异步事件的过程中发生异常,而开发者有没有专门写代码处理异步消费结果,那么此异常就默默无闻的被忽略了,我们也可能因此错失了及时发现和处理问题的时机
来写一段代码复现上述问题,首先是事件定义TestEvent.java,就是个普通类,啥都没有
public class TestEvent {
}
- 然后是事件的生产者TestEventProducer.java,注意其调用fireAsync方法发送了一个异步事件
@ApplicationScoped
public class TestEventProducer {
@Inject
Event<TestEvent> event;
/**
* 发送异步事件
*/
public void asyncProduce() {
event.fireAsync(new TestEvent());
}
}
- 事件的消费者TestEventConsumer.java,这里在消费TestEvent事件的时候,故意抛出了异常
@ApplicationScoped
public class TestEventConsumer {
/**
* 消费异步事件,这里故意抛出异常
*/
public void aSyncConsume(@ObservesAsync TestEvent testEvent) throws Exception {
throw new Exception("exception from aSyncConsume");
}
}
- 最后是单元测试类将事件的生产和消费运行起来
@QuarkusTest
public class EventExceptionHandlerTest {
@Inject
TestEventProducer testEventProducer;
@Test
public void testAsync() throws InterruptedException {
testEventProducer.asyncProduce();
}
}
- 运行EventExceptionHandlerTest,结果如下图,DefaultAsyncObserverExceptionHandler处理了这个异常,这是quarkus框架的默认处理逻辑
- DefaultAsyncObserverExceptionHandler只是输出了日志,这样的处理对于真实业务是不够的(可能需要记录到特定地方,调用其他告警服务等),所以,我们需要自定义默认的异步事件异常处理器
- 自定义的全局异步事件异常处理器如下
package com.bolingcavalry.service.impl;
import io.quarkus.arc.AsyncObserverExceptionHandler;
import io.quarkus.logging.Log;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.spi.EventContext;
import javax.enterprise.inject.spi.ObserverMethod;
@ApplicationScoped
public class NoopAsyncObserverExceptionHandler implements AsyncObserverExceptionHandler {
@Override
public void handle(Throwable throwable, ObserverMethod<?> observerMethod, EventContext<?> eventContext) {
// 异常信息
Log.info("exception is - " + throwable);
// 事件信息
Log.info("observer type is - " + observerMethod.getObservedType().getTypeName());
}
}
- 此刻,咱们再执行一次单元测试,如下图所示,异常已经被NoopAsyncObserverExceptionHandler#handler处理,异常和事件相关的信息都能拿到,您可以按照实际的业务需求来进行定制了
- 另外还要说明一下,自定义的全局异步事件异常处理器,其作用域只能是ApplicationScoped或者Singleton
- 至此,《quarkus依赖注入》系列全部完成,与bean相关的故事也就此结束了,十三篇文章凝聚了欣宸对quarkus框架bean容器的思考和实践,希望能帮助您更快的掌握和理解quarkus最核心的领域
- 虽然《quarkus依赖注入》已经终结,但是《quarkus实战》系列依然还在持续更新中,有了依赖注入的知识作为基础,接下来的quarkus之旅会更加轻松和高效
欢迎关注博客园:程序员欣宸
quarkus依赖注入之十三:其他重要知识点大串讲(终篇)的更多相关文章
- PHP关于依赖注入(控制反转)的解释和例子说明
PHP关于依赖注入(控制反转)的解释和例子说明 发表于2年前(2014-03-20 10:12) 阅读(726) | 评论(1) 8人收藏此文章, 我要收藏 赞2 阿里云双11绽放在即 1111 ...
- Nikola的5项依赖注入法则
本篇文章来自对 Nikola Malovic 博客文章 <Inversion Of Control, Single Responsibility Principle and Nikola’s l ...
- Spring学习(三)——Spring中的依赖注入的方式
[前面的话] Spring对我太重要了,做个关于web相关的项目都要使用Spring,每次去看Spring相关的知识,总是感觉一知半解,没有很好的系统去学习一下,现在抽点时间学习一下Spring.不知 ...
- 控制反转(IOC)/依赖注入(DI)理解
个人学习笔记,来自Acode. 1.术语 控制反转/反向控制,英文全称“Inversion of Control”,简称IoC. 依赖注入,英文全称“Dependency Injection”,简称D ...
- .Net Core中依赖注入服务使用总结
一.依赖注入 引入依赖注入的目的是为了解耦和.说白了就是面向接口编程,通过调用接口的方法,而不直接实例化对象去调用.这样做的好处就是如果添加了另一个种实现类,不需要修改之前代码,只需要修改注入的地方将 ...
- spring(3)------控制反转(IOC)/依赖注入(DI)
一.spring核心概念理解 控制反转: 控制反转即IoC (Inversion of Control).它把传统上由程序代码直接操控的对象的调用权交给容器.通过容器来实现对象组件的装配和管理. 所谓 ...
- .Net Core3.0依赖注入DI
构建ASP.NET Core应用程序的时候,依赖注入已成为了.NET Core的核心,这篇文章,我们理一理依赖注入的使用方法. 不使用依赖注入 首先,我们创建一个ASP.NET Core Mvc项目, ...
- 依赖注入之unity(winform方式)
依赖注入之unity(winform方式) 要讲unity就必须先了解DI和IOC及DIP,如下链接提供DI和IOC的基础:https://www.cnblogs.com/zlp520/p/12015 ...
- 看过这些我明白了依赖注入及IoC
背景 最近一段时间在学习laravel框架,了解到这个框架一个比较核心的概念就是服务容器,而服务容器似乎又和依赖注入有关系.但是碍于官方关于这方面的讲解篇幅过少,所以自学了一下. 自学的途径也跟大家一 ...
- Spring ——依赖注入配置一些知识点
依赖注入 依赖注入的原理与实现 依赖注入(DI)和依赖查找(Dependency Lookup)共同组成 控制反转(IoC).从原理的角度来说,依赖注入和控制反转是没 有不同的,可以看作是从两个角度来 ...
随机推荐
- 2022-11-26:给定一个字符串s,只含有0~9这些字符 你可以使用来自s中的数字,目的是拼出一个最大的回文数 使用数字的个数,不能超过s里含有的个数 比如 : 39878,能拼出的最大回文数是
2022-11-26:给定一个字符串s,只含有0~9这些字符 你可以使用来自s中的数字,目的是拼出一个最大的回文数 使用数字的个数,不能超过s里含有的个数 比如 : 39878,能拼出的最大回文数是 ...
- 2020-09-10:java里Object类有哪些方法?
福哥答案2020-09-10: registerNatives:private+static.getClass:返回此 Object 的运行时类. hashCode:返回该对象的哈希码值.equals ...
- 2021-08-15:给定一个字符串Str,返回Str的所有子序列中有多少不同的字面值。
2021-08-15:给定一个字符串Str,返回Str的所有子序列中有多少不同的字面值. 福大大 答案2021-08-15: 返回值=上+新-修正. 时间复杂度:O(N) 空间复杂度:O(N). 代码 ...
- 如何通过Java代码将 PDF文档转为 HTML格式
虽然PDF文件适合用于打印和发布,但不适合所有类型的文档.例如,包含复杂图表和图形的文档可能无法在PDF中呈现得很好.但是HTML文件可以在任何可运行浏览器的计算机上进行阅读并显示.并且HTML还具有 ...
- 企业研发效能度量利器,华为云发布CodeArts Board看板服务
摘要:华为云CodeArts Board正式上线,欢迎体验. 本文分享自华为云社区<企业研发效能度量利器,华为云发布CodeArts Board看板服务>,作者:华为云头条. 数字化时代, ...
- Part2: DDPM as Example of Variational Inference
很多次翻看DDPM,始终不太能理解论文中提到的\(\text{Variational Inference}\)到底是如何在这个工作中起到作用.五一假期在家,无意间又刷到徐亦达老师早些年录制的理论视频, ...
- 源码解析:django的CSRF认证
详解Django的CSRF认证 1.csrf原理 csrf要求发送post,put或delete请求的时候,是先以get方式发送请求,服务端响应时会分配一个随机字符串给客户端,客户端第二次发送post ...
- 改变用户体验:Whirl动画加载库的无限可能
哈喽!欢迎来到程序视点.今天小二哥要分享的不是 Animate.js,也不是 Move.js,而是能提供108种加载动画的库:Whirl. 让加载动画变得丰富多彩! 最省力的加载动画 话不多说,直接来 ...
- shell学习总结
shell教程 第一个shell脚本 打开文本编辑器(可以使用 vi/vim 命令来创建文件), 新建一个文件 test.sh,扩展名为 sh(sh代表shell) #!/bin/bash echo ...
- Java CAS:AtomicInteger、AtomicReference、AtomicStampedReference
Java CAS:AtomicInteger.AtomicReference.AtomicStampedReference 什么是CAS? 什么是CAS? 即比较并替换,实现并发算法时常用到的一种技术 ...