共计 6378 个字符,预计需要花费 16 分钟才能阅读完成。
欢送拜访我的 GitHub
这里分类和汇总了欣宸的全副原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- 本篇是《quarkus 依赖注入》系列的第十一篇,之前的 [《拦截器》] 学习了拦截器的基础知识,当初咱们要更加深刻的理解拦截器,把握两种高级用法:拦截器属性和重复使用拦截器
- 先来回顾拦截器的基本知识,定义一个拦截器并用来拦挡 bean 中的办法,总共须要实现以下三步
业务需要设定
- 为了让本篇所学知识点显得有实用型,这里假设一个业务需要,而后咱们用拦截器来满足这个需要
- 假如有个名为 SayHello 的一般接口,此接口有三个实现类:SayHelloA、SayHelloB、SayHelloC,这些实现类都是 bean,它们的源码如下
接口 SayHello.java
public interface SayHello {String hello(); } 实现类 SayHelloA.java
@ApplicationScoped @Named("A") public class SayHelloA implements SayHello { @Override public void hello() {Log.info("hello from A"); } } 实现类 SayHelloB.java
@ApplicationScoped @Named("B") public class SayHelloB implements SayHello { @Override public void hello() {Log.info("hello from B"); } } 实现类 SayHelloC.java
@ApplicationScoped @Named("C") public class SayHelloC implements SayHello { @Override public void hello() {Log.info("hello from C"); } } - 以上是已知条件,当初来看业务需要
- 要求设计一个拦截器,名为SendMessage,性能是对外发送告诉,告诉的形式有短信和邮件两种,具体用哪种是能够设置的
- 用 SendMessage 拦截器拦挡 SayHelloA,告诉类型是 短信
- 用 SendMessage 拦截器拦挡 SayHelloB,告诉类型是 邮件
用 SendMessage 拦截器拦挡 SayHelloC,告诉类型是 短信和邮件都发送
性能实现剖析
- 上述业务需要第二项和第三项,很显然拦截器的实现要同时反对短信告诉和邮件告诉两种性能,而问题的要害是:拦截器在工作的时候,如何晓得以后应该发送短信还是邮件,或者说如何将告诉类型精确的通知拦截器?
这就牵扯到一个知识点:拦截器属性 ,拦截器本人是个注解,而注解是有属性的,咱们新增一个 告诉类型 的属性(名为 sendType),只有在应用注解的中央配置 sendType,而后在拦截器实现中获取到 sendType 的值,就解决了告诉类型的设置和获取的问题,业务需要 2 和 3 也就迎刃而解了,拦截器配置的成果大抵如下
@ApplicationScoped @SendMessage(sendType="sms") public class SayHelloA implements SayHello { 再来看需要 4,这又设计到拦截器的另一个知识点:同一个拦截器重复使用,只有间断两次用 SendMessage 注解润饰 SayHelloC,而每个注解的 sendType 别离是短信和邮件,这样就能达到目标了,拦截器配置的成果大抵如下
@ApplicationScoped @SendMessage(sendType="sms") @SendMessage(sendType="email") public class SayHelloC implements SayHello { 以上就是解决问题的大抵思路,接下来编码实现,将波及的知识点在代码中体现进去
编码:定义拦截器
首先是拦截器定义 SendMessage.java,有几处要留神的中央稍后会提到
package com.bolingcavalry.interceptor.define; import javax.enterprise.util.Nonbinding; import javax.interceptor.InterceptorBinding; import java.lang.annotation.*; @InterceptorBinding @Repeatable(SendMessage.SendMessageList.class) @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface SendMessage { /** * 音讯类型 : "sms" 示意短信,"email" 示意邮件 * @return */ @Nonbinding String sendType() default "sms"; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface SendMessageList {SendMessage[] value();} } - 上述代码有以下几处须要留神
- 容许在同一地位重复使用同一个注解,这是 java 注解的通用性能,并非 quarkus 独有
- 重复使用注解时,必须定义注解容器,用来搁置反复的注解,这里的容器是 SendMessageList**
- 应用 Repeatable 润饰 SendMessage,这样就能在同一地位重复使用 SendMessage 注解了,留神 Repeatable 的属性值是容器 SendMessageList
- sendType是注解属性,用来保留告诉类型,任何应用 SendMessage 注解的中央都能通过设置 sendType 来指定告诉类型,如果不指定则应用默认值sms
- 要留神 sendType 的注解 Nonbinding,此注解 十分重要,如果不增加此注解,在应用 SendMessage 的时候,设置 sendType 为 email 时拦截器不会失效
quarkus 对重复使用同一拦截器注解的限度
- 尽管能够在同一地位重复使用 SendMessage 拦截器,然而要留神 quarkus 的限度
- 能够作用在办法上
- 不能作用在类上
- 不能作用在 stereotypes 上
- 对于 2 和 3,官网的说法是未来会解决(This might be added in the future)
编码:实现拦截器
- 接下来是实现具体拦挡性能的 SendMessageInterceptor.java,代码如下,有几处要留神的中央稍后会提到
package com.bolingcavalry.interceptor.impl; | |
import com.bolingcavalry.interceptor.define.SendMessage; | |
import com.bolingcavalry.interceptor.define.TrackParams; | |
import io.quarkus.arc.Priority; | |
import io.quarkus.arc.runtime.InterceptorBindings; | |
import io.quarkus.logging.Log; | |
import javax.interceptor.AroundInvoke; | |
import javax.interceptor.Interceptor; | |
import javax.interceptor.InvocationContext; | |
import java.lang.annotation.Annotation; | |
import java.util.*; | |
import static io.quarkus.arc.ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS; | |
@SendMessage | |
@Interceptor | |
public class SendMessageInterceptor { | |
@AroundInvoke | |
Object execute(InvocationContext context) throws Exception { | |
// 先执行被拦挡的办法 | |
Object rlt = context.proceed(); | |
// 获取被拦挡办法的类名 | |
String interceptedClass = context.getTarget().getClass().getSimpleName(); | |
// 代码能走到这里,示意被拦挡的办法已执行胜利,未出现异常 | |
// 从 context 中获取告诉类型,因为容许反复注解,因而告诉类型可能有多个 | |
List<String> allTypes = getAllTypes(context); | |
// 将所有音讯类型打印进去 | |
Log.infov("{0} messageTypes : {1}", interceptedClass, allTypes); | |
// 遍历所有音讯类型,调用对应的办法解决 | |
for (String type : allTypes) {switch (type) { | |
// 短信 | |
case "sms": | |
sendSms(); | |
break; | |
// 邮件 | |
case "email": | |
sendEmail(); | |
break; | |
} | |
} | |
// 最初再返回办法执行后果 | |
return rlt; | |
} | |
/** | |
* 从 InvocationContext 中取出所有注解,过滤出 SendMessage 类型的,将它们的 type 属性放入 List 中返回 | |
* @param invocationContext | |
* @return | |
*/ | |
private List<String> getAllTypes(InvocationContext invocationContext) { | |
// 取出所有注解 | |
Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(invocationContext); | |
List<String> allTypes = new ArrayList<>(); | |
// 遍历所有注解,过滤出 SendMessage 类型的 | |
for (Annotation binding : bindings) {if (binding instanceof SendMessage) {allTypes.add(((SendMessage) binding).sendType()); | |
} | |
} | |
return allTypes; | |
} | |
/** | |
* 模仿发送短信 | |
*/ | |
private void sendSms() {Log.info("operating success, from sms"); | |
} | |
/** | |
* 模仿发送邮件 | |
*/ | |
private void sendEmail() {Log.info("operating success, from email"); | |
} | |
} |
- 上述代码,有以下几处须要留神
- 发送短信和邮件不是本篇的重点,因而,对应的 sendSms 和 sendEmail 办法中只是日志打印,示意代码曾经走到了此处
- getAllTypes 办法是重点,演示了如何从拦截器上下文对象 invocationContext 中获取所有注解,并过滤出所有 SendMessage 类型,再取其 type 属性
- 对取出的 sendType 属性逐个解决,这样就做到了每个设置的类型都会被解决
- 在某个办法上屡次用 SendMessage 注解润饰,最终只会执行一次 SendMessageInterceptor#execute 办法,这是要害!试想,如果 SendMessageInterceptor#execute 办法执行了屡次,而每次都会取出所有 SendMessage 类型去解决,那么每种 SendMessage 类型都会反复解决
编码:应用拦截器
- 拦截器的定义和实现都曾经实现,接下来就是应用拦截器了,留神后面提到的限度,这里要用 SendMessage 去润饰办法,而不能润饰类
- 首先是 SayHelloA,拦挡它的时候,业务需要是发送短信,批改后的残缺源码如下,用 SendMessage 注解润饰 hello 办法,这里的 SendMessage 没有指定其 sendType 的值,因而会应用默认值sms
@ApplicationScoped | |
@Named("A") | |
public class SayHelloA implements SayHello { | |
@SendMessage | |
@Override | |
public void hello() {Log.info("hello from A"); | |
} | |
} |
- 而后是 SayHelloB,拦挡它的时候,业务需要是发送邮件,留神 sendType 值等于email
@ApplicationScoped | |
@Named("B") | |
public class SayHelloB implements SayHello {@SendMessage(sendType = "email") | |
@Override | |
public void hello() {Log.info("hello from B"); | |
} | |
} |
- 最初是 SayHelloC,拦挡它的时候,也无需要是短信和邮件都要发送,留神这里应用了两次 SendMessage
@ApplicationScoped | |
@Named("C") | |
public class SayHelloC implements SayHello { | |
@SendMessage | |
@SendMessage(sendType = "email") | |
@Override | |
public void hello() {Log.info("hello from C"); | |
} | |
} |
- 拦截器的定义、实现、应用都曾经实现,接下来思考如何验证,还是用单元测试吧,简略不便
编码:单元测试
- 单元测试类的逻辑很简略,运行几个 bean 的 hello 办法即可
@QuarkusTest | |
public class SendMessageTest {@Named("A") | |
SayHello sayHelloA; | |
@Named("B") | |
SayHello sayHelloB; | |
@Named("C") | |
SayHello sayHelloC; | |
@Test | |
public void testSendMessage() {sayHelloA.hello(); | |
sayHelloB.hello(); | |
sayHelloC.hello();} | |
} |
- 编码实现,能够运行起来验证后果了
运行单元测试
- 单元测试类 SendMessageTestd 的执行后果如下图,红黄蓝三个框中,别离是 SayHelloA、SayHelloB、SayHelloC 的拦挡后果,可见全副合乎预期
- 至此,拦截器的两个高级个性曾经实战实现,心愿这些知识点可能帮忙您写出更弱小和精准的拦截器,实现简单的业务需要
源码下载
- 本篇实战的残缺源码可在 GitHub 下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos)
名称 | 链接 | 备注 |
---|---|---|
我的项目主页 | https://github.com/zq2599/blog_demos | 该我的项目在 GitHub 上的主页 |
git 仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该我的项目源码的仓库地址,https 协定 |
git 仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该我的项目源码的仓库地址,ssh 协定 |
- 这个 git 我的项目中有多个文件夹,本次实战的源码在 quarkus-tutorials 文件夹下,如下图红框
- quarkus-tutorials是个父工程,外面有多个 module,本篇实战的 module 是basic-di,如下图红框
欢送关注思否:程序员欣宸
学习路上,你不孤独,欣宸原创一路相伴 …
正文完