欢送拜访我的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();  }}
  • 上述代码有以下几处须要留神
  1. 容许在同一地位重复使用同一个注解,这是java注解的通用性能,并非quarkus独有
  2. 重复使用注解时,必须定义注解容器,用来搁置反复的注解,这里的容器是SendMessageList**
  3. 应用Repeatable润饰SendMessage,这样就能在同一地位重复使用SendMessage注解了,留神Repeatable的属性值是容器SendMessageList
  4. sendType是注解属性,用来保留告诉类型,任何应用SendMessage注解的中央都能通过设置sendType来指定告诉类型,如果不指定则应用默认值sms
  5. 要留神sendType的注解Nonbinding,此注解十分重要,如果不增加此注解,在应用SendMessage的时候,设置sendType为email时拦截器不会失效

quarkus对重复使用同一拦截器注解的限度

  • 尽管能够在同一地位重复使用SendMessage拦截器,然而要留神quarkus的限度
  1. 能够作用在办法上
  2. 不能作用在类上
  3. 不能作用在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@Interceptorpublic 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");    }}
  • 上述代码,有以下几处须要留神
  1. 发送短信和邮件不是本篇的重点,因而,对应的sendSms和sendEmail办法中只是日志打印,示意代码曾经走到了此处
  2. getAllTypes办法是重点,演示了如何从拦截器上下文对象invocationContext中获取所有注解,并过滤出所有SendMessage类型,再取其type属性
  3. 对取出的sendType属性逐个解决,这样就做到了每个设置的类型都会被解决
  4. 在某个办法上屡次用SendMessage注解润饰,最终只会执行一次SendMessageInterceptor#execute办法,这是要害!试想,如果SendMessageInterceptor#execute办法执行了屡次,而每次都会取出所有SendMessage类型去解决,那么每种SendMessage类型都会反复解决

编码:应用拦截器

  • 拦截器的定义和实现都曾经实现,接下来就是应用拦截器了,留神后面提到的限度,这里要用SendMessage去润饰办法,而不能润饰类
  1. 首先是SayHelloA,拦挡它的时候,业务需要是发送短信,批改后的残缺源码如下,用SendMessage注解润饰hello办法,这里的SendMessage没有指定其sendType的值,因而会应用默认值sms
@ApplicationScoped@Named("A")public class SayHelloA implements SayHello {    @SendMessage    @Override    public void hello() {        Log.info("hello from A");    }}
  1. 而后是SayHelloB,拦挡它的时候,业务需要是发送邮件,留神sendType值等于email
@ApplicationScoped@Named("B")public class SayHelloB implements SayHello {    @SendMessage(sendType = "email")    @Override    public void hello() {        Log.info("hello from B");    }}
  1. 最初是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办法即可
@QuarkusTestpublic 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,如下图红框

欢送关注思否:程序员欣宸

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