歡迎訪問我的GitHub
這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos
本篇概覽
- 本篇是《quarkus依賴注入》系列的終篇,前面十二篇已覆蓋quarkus依賴注入的大部分核心內(nèi)容,但依然漏掉了一些知識(shí)點(diǎn),今天就將剩下的內(nèi)容匯總,來個(gè)一鍋端,輕松愉快的結(jié)束這個(gè)系列
- 總的來說,本篇由以下內(nèi)容構(gòu)成,每個(gè)段落都是個(gè)獨(dú)立的知識(shí)點(diǎn)
- 幾處可以簡(jiǎn)化編碼的地方,如bean注入、構(gòu)造方法等
- WithCaching:特定場(chǎng)景下,減少bean實(shí)例化次數(shù)
- 靜態(tài)方法是否可以被攔截器攔截?
- All注解,讓多個(gè)bean的注入更加直觀
- 統(tǒng)一處理異步事件的異常
- 咱們從最簡(jiǎn)單的看起:表達(dá)方式的簡(jiǎn)化,一共有三個(gè)位置可以簡(jiǎn)化:bean的注入、bean構(gòu)造方法、bean生產(chǎn)方法
簡(jiǎn)化之一:bean注入
-
quarkus在CDI規(guī)范的基礎(chǔ)上做了簡(jiǎn)化,可以讓我們少寫幾行代碼
-
將配置文件中名為greeting.message的配置項(xiàng)注入到bean的成員變量greetingMsg中,按照CDI規(guī)范的寫法如下
@Inject
@ConfigProperty(name = "greeting.message")
String greetingMsg;
- 在quarkus框架下可以略去@Inject,寫成下面這樣的效果和上面的代碼一模一樣
@ConfigProperty(name = "greeting.message")
String greetingMsg;
簡(jiǎn)化之二:bean構(gòu)造方法
- 關(guān)于bean的構(gòu)造方法,CDI有兩個(gè)規(guī)定:首先,必須要有無參構(gòu)造方法,其次,有參數(shù)的構(gòu)造方法需要@Inject注解修飾,實(shí)例代碼如下所示
@ApplicationScoped
public class MyCoolService {
private SimpleProcessor processor;
MyCoolService() { // dummy constructor needed
}
@Inject // constructor injection
MyCoolService(SimpleProcessor processor) {
this.processor = processor;
}
}
- 但是,在quarkus框架下,無參構(gòu)造方法可不寫,有參數(shù)的構(gòu)造方法也可以略去@Inject,寫成下面這樣的效果和上面的代碼一模一樣
@ApplicationScoped
public class MyCoolService {
private SimpleProcessor processor;
MyCoolService(SimpleProcessor processor) {
this.processor = processor;
}
}
簡(jiǎn)化之三:bean生產(chǎn)方法
- 在CDI規(guī)范中,通過方法生產(chǎn)bean的語法如下,可見要同時(shí)使用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);
}
}
- 好了,熱身結(jié)束,接下來看幾個(gè)略有深度的技能
WithCaching注解:避免不必要的多次實(shí)例化
- 在介紹WithCaching注解之前,先來看一個(gè)普通場(chǎng)景
- 下面是一段單元測(cè)試代碼,HelloDependent類型的bean通過Instance的方式被注入,再用Instance#get來獲取此bean
@QuarkusTest
public class WithCachingTest {
@Inject
Instance<HelloDependent> instance;
@Test
public void test() {
// 第一次調(diào)用Instance#get方法
HelloDependent helloDependent = instance.get();
helloDependent.hello();
// 第二次調(diào)用Instance#get方法
helloDependent = instance.get();
helloDependent.hello();
}
}
-
上述代碼是種常見的bean注入和使用方式,我們的本意是在WithCachingTest實(shí)例中多次使用HelloDependent類型的bean,可能是在test方法中使用,也可能在WithCachingTest的其他方法中使用
-
如果HelloDependent的作用域是ApplicationScoped,上述代碼一切正常,但是,如果作用域是Dependent呢?代碼中執(zhí)行了兩次Instance#get,得到的HelloDependent實(shí)例是同一個(gè)嗎?Dependent的特性是每次注入都實(shí)例化一次,這里的Instance#get又算幾次注入呢?
-
最簡(jiǎn)單的方法就是運(yùn)行上述代碼看實(shí)際效果,這里先回顧HelloDependent.java的源碼,如下所示,構(gòu)造方法中會(huì)打印日志,這下好辦了,只要看日志出現(xiàn)幾次,就知道實(shí)例化幾次了
@Dependent
public class HelloDependent {
public HelloDependent(InjectionPoint injectionPoint) {
Log.info("injecting from bean "+ injectionPoint.getMember().getDeclaringClass());
}
public String hello() {
return this.getClass().getSimpleName();
}
}
- 運(yùn)行單元測(cè)試類WithCachingTest,如下圖紅框所示,構(gòu)造方法中的日志打印了兩次,所以:每次Instance#get都相當(dāng)于一次注入,如果bean的作用域是Dependent,就會(huì)創(chuàng)建一個(gè)新的實(shí)例并返回

- 現(xiàn)在問題來了:如果bean的作用域必須是Dependent,又希望多次Instance#get返回的是同一個(gè)bean實(shí)例,這樣的要求可以做到嗎?
- 答案是可以,用WithCaching注解修飾Instance即可,改動(dòng)如下圖紅框1,改好后再次運(yùn)行,紅框2顯示HelloDependent只實(shí)例化了一次
攔截靜態(tài)方法
- 先回顧一下攔截器的基本知識(shí),定義一個(gè)攔截器并用來攔截bean中的方法,總共需要完成以下三步

- 實(shí)現(xiàn)攔截器的具體功能時(shí),還要用注解指明攔截器類型,一共有四種類型
- AroundInvoke:攔截bean方法
- PostConstruct:生命周期攔截器,bean創(chuàng)建后執(zhí)行
- PreDestroy:生命周期攔截器,bean銷毀前執(zhí)行
- AroundConstruct:生命周期攔截器,攔截bean構(gòu)造方法
- 現(xiàn)在問題來了:攔截器能攔截靜態(tài)方法嗎?
- 答案是可以,但是有限制,具體的限制如下
- 僅支持方法級(jí)別的攔截(即攔截器修飾的是方法)
- private型的靜態(tài)方法不會(huì)被攔截
- 下圖是攔截器實(shí)現(xiàn)的常見代碼,通過入?yún)?font color="blue">InvocationContext的getTarget方法,可以得到被攔截的對(duì)象,然而,在攔截靜態(tài)方法時(shí),getTarget方法的返回值是null,這一點(diǎn)尤其要注意,例如下圖紅框中的代碼,在攔截靜態(tài)方法是就會(huì)拋出空指針異常

All更加直觀的注入
- 假設(shè)有個(gè)名為SayHello的接口,源碼如下
public interface SayHello {
void hello();
}
-
現(xiàn)在有三個(gè)bean都實(shí)現(xiàn)了SayHello接口,如果想要調(diào)用這三個(gè)bean的hello方法,應(yīng)該怎么做呢?
-
按照CDI的規(guī)范,應(yīng)該用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規(guī)范相比,使用All注解可以讓代碼顯得更為直觀,另外還有以下三個(gè)特點(diǎn)
-
此list是immutable的(內(nèi)容不可變)
-
list中的bean是按照priority排序的
-
如果您需要的不僅僅是注入bean,還需要bean的元數(shù)據(jù)信息(例如bean的scope),可以將List中的類型從SayHello改為InstanceHandle<SayHello>,這樣即可以得到注入bean,也能得到注入bean的元數(shù)據(jù)(在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的元數(shù)據(jù)信息
InjectableBean<SayHello> injectableBean = instanceHandle.getBean();
// 例如bean的作用域就能從InjectableBean中取得
Class clazz = injectableBean.getScope();
// 打印出來驗(yàn)證
Log.infov("bean [{0}], scope [{1}]", sayHello.getClass().getSimpleName(), clazz.getSimpleName() );
}
}
}
- 代碼的執(zhí)行結(jié)果如下圖紅框所示,可見注入bean及其作用域都能成功取得(要注意的是注入bean是代理bean)
統(tǒng)一處理異步事件的異常
-
需要提前說一下,本段落涉及的知識(shí)點(diǎn)和AsyncObserverExceptionHandler類有關(guān),而《quarkus依賴注入》系列所用的quarkus-2.7.3.Final版本中并沒有AsyncObserverExceptionHandler類,后來將quarkus版本更新為2.8.2.Final,就可以正常使用AsyncObserverExceptionHandler類了
-
本段落的知識(shí)點(diǎn)和異步事件有關(guān):如果消費(fèi)異步事件的過程中發(fā)生異常,而開發(fā)者有沒有專門寫代碼處理異步消費(fèi)結(jié)果,那么此異常就默默無聞的被忽略了,我們也可能因此錯(cuò)失了及時(shí)發(fā)現(xiàn)和處理問題的時(shí)機(jī)
-
來寫一段代碼復(fù)現(xiàn)上述問題,首先是事件定義TestEvent.java,就是個(gè)普通類,啥都沒有
public class TestEvent {
}
- 然后是事件的生產(chǎn)者TestEventProducer.java,注意其調(diào)用fireAsync方法發(fā)送了一個(gè)異步事件
@ApplicationScoped
public class TestEventProducer {
@Inject
Event<TestEvent> event;
/**
* 發(fā)送異步事件
*/
public void asyncProduce() {
event.fireAsync(new TestEvent());
}
}
- 事件的消費(fèi)者TestEventConsumer.java,這里在消費(fèi)TestEvent事件的時(shí)候,故意拋出了異常
@ApplicationScoped
public class TestEventConsumer {
/**
* 消費(fèi)異步事件,這里故意拋出異常
*/
public void aSyncConsume(@ObservesAsync TestEvent testEvent) throws Exception {
throw new Exception("exception from aSyncConsume");
}
}
- 最后是單元測(cè)試類將事件的生產(chǎn)和消費(fèi)運(yùn)行起來
@QuarkusTest
public class EventExceptionHandlerTest {
@Inject
TestEventProducer testEventProducer;
@Test
public void testAsync() throws InterruptedException {
testEventProducer.asyncProduce();
}
}
- 運(yùn)行EventExceptionHandlerTest,結(jié)果如下圖,DefaultAsyncObserverExceptionHandler處理了這個(gè)異常,這是quarkus框架的默認(rèn)處理邏輯
- DefaultAsyncObserverExceptionHandler只是輸出了日志,這樣的處理對(duì)于真實(shí)業(yè)務(wù)是不夠的(可能需要記錄到特定地方,調(diào)用其他告警服務(wù)等),所以,我們需要自定義默認(rèn)的異步事件異常處理器
- 自定義的全局異步事件異常處理器如下
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());
}
}
- 此刻,咱們?cè)賵?zhí)行一次單元測(cè)試,如下圖所示,異常已經(jīng)被NoopAsyncObserverExceptionHandler#handler處理,異常和事件相關(guān)的信息都能拿到,您可以按照實(shí)際的業(yè)務(wù)需求來進(jìn)行定制了
文章來源:http://www.zghlxwxcb.cn/news/detail-642051.html
- 另外還要說明一下,自定義的全局異步事件異常處理器,其作用域只能是ApplicationScoped或者Singleton
- 至此,《quarkus依賴注入》系列全部完成,與bean相關(guān)的故事也就此結(jié)束了,十三篇文章凝聚了欣宸對(duì)quarkus框架bean容器的思考和實(shí)踐,希望能幫助您更快的掌握和理解quarkus最核心的領(lǐng)域
- 雖然《quarkus依賴注入》已經(jīng)終結(jié),但是《quarkus實(shí)戰(zhàn)》系列依然還在持續(xù)更新中,有了依賴注入的知識(shí)作為基礎(chǔ),接下來的quarkus之旅會(huì)更加輕松和高效
歡迎關(guān)注博客園:程序員欣宸
學(xué)習(xí)路上,你不孤單,欣宸原創(chuàng)一路相伴...文章來源地址http://www.zghlxwxcb.cn/news/detail-642051.html
到了這里,關(guān)于quarkus依賴注入之十三:其他重要知識(shí)點(diǎn)大串講(終篇)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!