quarkus依赖注入之十一:拦截器高级特性上篇(属性设置和重复使用) ...

打印 上一主题 下一主题

主题 868|帖子 868|积分 2619

欢迎访问我的GitHub

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


  • 本篇是《quarkus依赖注入》系列的第十一篇,之前的[《拦截器》]学习了拦截器的基础知识,现在咱们要更加深入的了解拦截器,掌握两种高级用法:拦截器属性和重复使用拦截器
  • 先来回顾拦截器的基本知识,定义一个拦截器并用来拦截bean中的方法,总共需要完成以下三步

业务需求设定


  • 为了让本篇所学知识点显得有实用型,这里假定一个业务需求,然后咱们用拦截器来满足这个需求
  • 假设有个名为SayHello的普通接口,此接口有三个实现类:SayHelloA、SayHelloB、SayHelloC,这些实现类都是bean,它们的源码如下

  • 接口SayHello.java
  1. public interface SayHello {
  2.     String hello();
  3. }
复制代码

  • 实现类SayHelloA.java
  1. @ApplicationScoped
  2. @Named("A")
  3. public class SayHelloA implements SayHello {
  4.     @Override
  5.     public void hello() {
  6.         Log.info("hello from A");
  7.     }
  8. }
复制代码

  • 实现类SayHelloB.java
  1. @ApplicationScoped
  2. @Named("B")
  3. public class SayHelloB implements SayHello {
  4.     @Override
  5.     public void hello() {
  6.         Log.info("hello from B");
  7.     }
  8. }
复制代码

  • 实现类SayHelloC.java
  1. @ApplicationScoped
  2. @Named("C")
  3. public class SayHelloC implements SayHello {
  4.     @Override
  5.     public void hello() {
  6.         Log.info("hello from C");
  7.     }
  8. }
复制代码

  • 以上是已知条件,现在来看业务需求

  • 要求设计一个拦截器,名为SendMessage,功能是对外发送通知,通知的方式有短信和邮件两种,具体用哪种是可以设置的
  • SendMessage拦截器拦截SayHelloA,通知类型是短信
  • SendMessage拦截器拦截SayHelloB,通知类型是邮件
  • SendMessage拦截器拦截SayHelloC,通知类型是短信和邮件都发送
功能实现分析


  • 上述业务需求第二项和第三项,很显然拦截器的实现要同时支持短信通知和邮件通知两种功能,而问题的关键是:拦截器在工作的时候,如何知道当前应该发送短信还是邮件,或者说如何将通知类型准确的告诉拦截器?
  • 这就牵扯到一个知识点:拦截器属性,拦截器自己是个注解,而注解是有属性的,咱们新增一个通知类型的属性(名为sendType),只要在使用注解的地方配置sendType,然后在拦截器实现中获取到sendType的值,就解决了通知类型的设置和获取的问题,业务需求2和3也就迎刃而解了,拦截器配置的效果大致如下
  1. @ApplicationScoped
  2. @SendMessage(sendType="sms")
  3. public class SayHelloA implements SayHello {
复制代码

  • 再来看需求4,这又设计到拦截器的另一个知识点:同一个拦截器重复使用,只要连续两次用SendMessage注解修饰SayHelloC,而每个注解的sendType分别是短信和邮件,这样就能达到目的了,拦截器配置的效果大致如下
  1. @ApplicationScoped
  2. @SendMessage(sendType="sms")
  3. @SendMessage(sendType="email")
  4. public class SayHelloC implements SayHello {
复制代码

  • 以上就是解决问题的大致思路,接下来编码实现,将涉及的知识点在代码中体现出来
编码:定义拦截器


  • 首先是拦截器定义SendMessage.java,有几处要注意的地方稍后会提到
  1. package com.bolingcavalry.interceptor.define;
  2. import javax.enterprise.util.Nonbinding;
  3. import javax.interceptor.InterceptorBinding;
  4. import java.lang.annotation.*;
  5. @InterceptorBinding
  6. @Repeatable(SendMessage.SendMessageList.class)
  7. @Target({ElementType.TYPE, ElementType.METHOD})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. public @interface SendMessage {
  10.     /**
  11.      * 消息类型 : "sms"表示短信,"email"表示邮件
  12.      * @return
  13.      */
  14.     @Nonbinding
  15.     String sendType() default "sms";
  16.     @Target({ElementType.TYPE, ElementType.METHOD})
  17.     @Retention(RetentionPolicy.RUNTIME)
  18.     @interface SendMessageList {
  19.         SendMessage[] value();
  20.     }
  21. }
复制代码

  • 上述代码有以下几处需要注意

  • 允许在同一位置重复使用同一个注解,这是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,代码如下,有几处要注意的地方稍后会提到
  1. package com.bolingcavalry.interceptor.impl;
  2. import com.bolingcavalry.interceptor.define.SendMessage;
  3. import com.bolingcavalry.interceptor.define.TrackParams;
  4. import io.quarkus.arc.Priority;
  5. import io.quarkus.arc.runtime.InterceptorBindings;
  6. import io.quarkus.logging.Log;
  7. import javax.interceptor.AroundInvoke;
  8. import javax.interceptor.Interceptor;
  9. import javax.interceptor.InvocationContext;
  10. import java.lang.annotation.Annotation;
  11. import java.util.*;
  12. import static io.quarkus.arc.ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS;
  13. @SendMessage
  14. @Interceptor
  15. public class SendMessageInterceptor {
  16.     @AroundInvoke
  17.     Object execute(InvocationContext context) throws Exception {
  18.         // 先执行被拦截的方法
  19.         Object rlt = context.proceed();
  20.         // 获取被拦截方法的类名
  21.         String interceptedClass = context.getTarget().getClass().getSimpleName();
  22.         // 代码能走到这里,表示被拦截的方法已执行成功,未出现异常
  23.         // 从context中获取通知类型,由于允许重复注解,因此通知类型可能有多个
  24.         List<String> allTypes = getAllTypes(context);
  25.         // 将所有消息类型打印出来
  26.         Log.infov("{0} messageTypes : {1}", interceptedClass, allTypes);
  27.         // 遍历所有消息类型,调用对应的方法处理
  28.         for (String type : allTypes) {
  29.             switch (type) {
  30.                 // 短信
  31.                 case "sms":
  32.                     sendSms();
  33.                     break;
  34.                 // 邮件
  35.                 case "email":
  36.                     sendEmail();
  37.                     break;
  38.             }
  39.         }
  40.         // 最后再返回方法执行结果
  41.         return rlt;
  42.     }
  43.     /**
  44.      * 从InvocationContext中取出所有注解,过滤出SendMessage类型的,将它们的type属性放入List中返回
  45.      * @param invocationContext
  46.      * @return
  47.      */
  48.     private List<String> getAllTypes(InvocationContext invocationContext) {
  49.         // 取出所有注解
  50.         Set<Annotation> bindings = InterceptorBindings.getInterceptorBindings(invocationContext);
  51.         List<String> allTypes = new ArrayList<>();
  52.         // 遍历所有注解,过滤出SendMessage类型的
  53.         for (Annotation binding : bindings) {
  54.             if (binding instanceof SendMessage) {
  55.                allTypes.add(((SendMessage) binding).sendType());
  56.             }
  57.         }
  58.         return allTypes;
  59.     }
  60.     /**
  61.      * 模拟发送短信
  62.      */
  63.     private void sendSms() {
  64.         Log.info("operating success, from sms");
  65.     }
  66.     /**
  67.      * 模拟发送邮件
  68.      */
  69.     private void sendEmail() {
  70.         Log.info("operating success, from email");
  71.     }
  72. }
复制代码

  • 上述代码,有以下几处需要注意

  • 发送短信和邮件不是本篇的重点,因此,对应的sendSms和sendEmail方法中只是日志打印,表示代码已经走到了此处
  • getAllTypes方法是重点,演示了如何从拦截器上下文对象invocationContext中获取所有注解,并过滤出所有SendMessage类型,再取其type属性
  • 对取出的sendType属性逐一处理,这样就做到了每个设置的类型都会被处理
  • 在某个方法上多次用SendMessage注解修饰,最终只会执行一次SendMessageInterceptor#execute方法,这是关键!试想,如果SendMessageInterceptor#execute方法执行了多次,而每次都会取出所有SendMessage类型去处理,那么每种SendMessage类型都会重复处理
编码:使用拦截器


  • 拦截器的定义和实现都已经完成,接下来就是使用拦截器了,注意前面提到的限制,这里要用SendMessage去修饰方法,而不能修饰类

  • 首先是SayHelloA,拦截它的时候,业务需求是发送短信,修改后的完整源码如下,用SendMessage注解修饰hello方法,这里的SendMessage没有指定其sendType的值,因此会使用默认值sms
  1. @ApplicationScoped
  2. @Named("A")
  3. public class SayHelloA implements SayHello {
  4.     @SendMessage
  5.     @Override
  6.     public void hello() {
  7.         Log.info("hello from A");
  8.     }
  9. }
复制代码

  • 然后是SayHelloB,拦截它的时候,业务需求是发送邮件,注意sendType值等于email
  1. @ApplicationScoped
  2. @Named("B")
  3. public class SayHelloB implements SayHello {
  4.     @SendMessage(sendType = "email")
  5.     @Override
  6.     public void hello() {
  7.         Log.info("hello from B");
  8.     }
  9. }
复制代码

  • 最后是SayHelloC,拦截它的时候,也无需求是短信和邮件都要发送,注意这里使用了两次SendMessage
  1. @ApplicationScoped
  2. @Named("C")
  3. public class SayHelloC implements SayHello {
  4.     @SendMessage
  5.     @SendMessage(sendType = "email")
  6.     @Override
  7.     public void hello() {
  8.         Log.info("hello from C");
  9.     }
  10. }
复制代码

  • 拦截器的定义、实现、使用都已经完成,接下来考虑如何验证,还是用单元测试吧,简单方便
编码:单元测试


  • 单元测试类的逻辑很简单,运行几个bean的hello方法即可
  1. @QuarkusTest
  2. public class SendMessageTest {
  3.     @Named("A")
  4.     SayHello sayHelloA;
  5.     @Named("B")
  6.     SayHello sayHelloB;
  7.     @Named("C")
  8.     SayHello sayHelloC;
  9.     @Test
  10.     public void testSendMessage() {
  11.         sayHelloA.hello();
  12.         sayHelloB.hello();
  13.         sayHelloC.hello();
  14.     }
  15. }
复制代码

  • 编码完成,可以运行起来验证结果了
运行单元测试


  • 单元测试类SendMessageTestd的执行结果如下图,红黄蓝三个框中,分别是SayHelloA、SayHelloB、SayHelloC的拦截结果,可见全部符合预期


  • 至此,拦截器的两个高级特性已经实战完成,希望这些知识点能够帮助您写出更强大和精准的拦截器,实现复杂的业务需求
源码下载

名称链接备注项目主页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,如下图红框

欢迎关注博客园:程序员欣宸

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

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

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

大连全瓷种植牙齿制作中心

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

标签云

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