乐趣区

关于后端:quarkus依赖注入之五拦截器Interceptor

欢送拜访我的 GitHub

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

本篇概览

  • 本文是《quarkus 依赖注入》系列的第五篇,通过后面的学习,咱们相熟了依赖注入的根本个性,接下来进一步理解相干的高级个性,先从本篇的拦截器开始
  • 如果您相熟 spring 的话,对拦截器应该不会生疏,通过拦截器能够将各种附加性能与被拦挡代码的主体解耦合,例如异样解决、日志、数据同步等多种场景
  • 本篇会演示如何自定义拦截器,以及如何对 bean 的办法进行进行拦挡,由以下章节形成
  • 定义和应用拦截器的操作步骤介绍
  • 拦挡异样
  • 拦挡构造方法
  • 获取被拦挡办法的参数
  • 多个拦截器之间传递参数

定义和应用拦截器的操作步骤介绍

  • 定义和应用拦截器一共要做三件事:
  1. 定义:新增一个注解(假如名为 A),要用 @InterceptorBinding 润饰该注解
  2. 实现:拦截器 A 到底要做什么事件,须要在一个类中实现,该类要用两个注解来润饰:A 和 Interceptor
  3. 应用:用 A 来润饰要拦截器的 bean
  • 整个流程如下图所示

<img src=”https://typora-pictures-1253575040.cos.ap-guangzhou.myqcloud.com/%E6%B5%81%E7%A8%8B%E5%9B%BE%20(19).jpg” alt=” 流程图 (19)” style=”zoom:67%;” />

  • 接下来通过实战把握拦截器的开发和应用,从最常见的拦挡异样开始

拦挡异样

  • 写一个拦截器,在程序产生异样的时候能够捕捉到并将异样打印进去
  • 首先是定义一个拦截器,这里的拦截器名为 <font color=”blue”>HandleError</font>,留神要用 <font color=”red”>InterceptorBinding</font> 润饰
package com.bolingcavalry.interceptor.define;

import javax.interceptor.InterceptorBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;

@InterceptorBinding
@Target({TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandleError {}
  • 其次是实现拦截器的具体性能,上面代码有几处要留神的中央稍后会提到
package com.bolingcavalry.interceptor.impl;

import com.bolingcavalry.interceptor.define.HandleError;
import io.quarkus.arc.Priority;
import io.quarkus.logging.Log;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@HandleError
@Interceptor
@Priority(Interceptor.Priority.APPLICATION +1)
public class HandleErrorInterceptor {

    @AroundInvoke
    Object execute(InvocationContext context) {

        try {
            // 留神 proceed 办法的含意:调用下一个拦截器,直到最初一个才会执行被拦挡的办法
            return context.proceed();} catch (Exception exception) {
            Log.errorf(exception,
                    "method error from %s.%s\n",
                    context.getTarget().getClass().getSimpleName(),
                    context.getMethod().getName());
        }

        return null;
    }
}
  • 上述代码有以下四点须要留神:
  1. <font color=”blue”>Priority</font> 注解的作用是设定 HandleError 拦截器的优先级 (<font color=”red”> 值越小优先级越高 </font>),能够同时用多个拦截器拦挡同一个办法
  2. <font color=”blue”>AroundInvoke</font> 注解的作用,是表明 execute 会在拦挡 bean 办法时被调用
  3. <font color=”blue”>proceed</font> 办法的作用,并非是执行被拦挡的办法,而是执行下一个拦截器,直到最初一个拦截器才会执行被拦挡的办法
  4. 能够从入参 context 处获得被拦挡实例和办法的信息
  • 而后是应用拦截器,这里创立个 bean 来演示拦截器如何应用,bean 外面有个业务办法会抛出异样,可见拦截器应用起来很简略:用 HandleError 润饰 bean 即可
@ApplicationScoped
@HandleError
public class HandleErrorDemo {public void executeThrowError() {throw new IllegalArgumentException("this is business logic exception");
    }
}
  • 验证拦截器的单元测试代码如下,只有执行 HandleErrorDemo 的 executeThrowError 办法就会抛出异样,而后察看日志中是否有拦截器日志信息即可验证拦截器是否合乎预期
@QuarkusTest
public class InterceptorTest {

    @Inject
    HandleErrorDemo handleErrorDemo;

    @Test
    public void testHandleError() {handleErrorDemo.executeThrowError();
    }
}
  • 执行单元测试,如下图红框所示,拦截器捕捉了异样并打印出异样信息

<img src=”https://typora-pictures-1253575040.cos.ap-guangzhou.myqcloud.com/image-20220327145313820.png” alt=”image-20220327145313820″ style=”zoom:67%;” />

  • 至此,拦挡异样的操作就实现了,除了用 <font color=”blue”>AroundInvoke</font> 拦挡一般的 bean 办法,还能用 <font color=”red”>AroundConstruct</font> 拦挡 bean 的构造方法,接下里编码体验

拦挡构造方法

  • 拦截器定义
@InterceptorBinding
@Target({TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandleConstruction {}
  • HandleConstruction 拦截器的实现,要留神的有两点稍后会提到
@HandleConstruction
@Interceptor
@Priority(Interceptor.Priority.APPLICATION +1)
public class HandleConstructionInterceptor {

    @AroundConstruct
    void execute(InvocationContext context) throws Exception {
        // 执行业务逻辑能够在此
        Log.infov("start construction interceptor");

        // 执行 bean 的构造方法
        context.proceed();

        // 留神,对于 context.getTarget() 的返回值,此时不是 null,如果在 context.proceed() 之前,则是 null
        Log.infov("bean instance of {0}", context.getTarget().getClass().getSimpleName());
    }
}
  • 上述代码有两处要留神的
  1. 被 <font color=”blue”>AroundConstruct</font> 注解润饰后,execute 办法会在 bean 的构造方法执行时被调用
  2. context.getTarget() 的返回值,只有在 context.proceed 执行后才不为空
  • 拦截器的应用,用 <font color=”blue”>HandleConstruction</font> 润饰要拦挡的 bean,为了调试和剖析,还在构造方法中打印了日志
@ApplicationScoped
@HandleConstruction
public class HandleonstructionDemo {public HandleonstructionDemo() {super();
        Log.infov("construction of {0}", HandleonstructionDemo.class.getSimpleName());
    }

    public void hello() {Log.info("hello world!");
    }
}
  • 用单元测试来验证拦截器是否胜利拦挡构造方法
@QuarkusTest
public class InterceptorTest {

    @Inject
    HandleonstructionDemo handleonstructionDemo;

    @Test
    public void testHandleonstruction() {handleonstructionDemo.hello();
    }
}
  • 运行单元测试,控制台输入如下,可见构造方法拦挡胜利
2022-03-27 15:51:03,158 INFO  [io.quarkus] (main) Quarkus 2.7.3.Final on JVM started in 0.867s. Listening on: http://localhost:8081
2022-03-27 15:51:03,158 INFO  [io.quarkus] (main) Profile test activated. 
2022-03-27 15:51:03,158 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, narayana-jta, resteasy, smallrye-context-propagation, vertx]
2022-03-27 15:51:03,164 INFO  [com.bol.int.dem.HandleonstructionDemo] (main) construction of HandleonstructionDemo
2022-03-27 15:51:03,397 INFO  [com.bol.int.imp.HandleConstructionInterceptor] (main) start construction interceptor
2022-03-27 15:51:03,398 INFO  [com.bol.int.dem.HandleonstructionDemo] (main) construction of HandleonstructionDemo
2022-03-27 15:51:03,398 INFO  [com.bol.int.imp.HandleConstructionInterceptor] (main) bean instance of HandleonstructionDemo
2022-03-27 15:51:03,398 INFO  [com.bol.int.dem.HandleonstructionDemo] (main) hello world!
2022-03-27 15:51:03,416 INFO  [io.quarkus] (main) Quarkus stopped in 0.015s

获取被拦挡办法的参数

  • 拦挡办法时,可能须要晓得办法入参的值,才好实现具体的拦挡性能(如参数校验),接下来就试试如何获得被拦挡办法的参数并打印到日志中
  • 首先是拦截器定义
@InterceptorBinding
@Target({TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackParams {}
  • 拦截器实现,能够用 <font color=”blue”>context.getParameters</font> 办法获得被拦挡办法的入参数组,而后遍历并打印
@TrackParams
@Interceptor
@Priority(Interceptor.Priority.APPLICATION + 1)
public class TrackParamsInterceptor {

    @AroundInvoke
    Object execute(InvocationContext context) throws Exception {// context.getParameters() 返回拦挡办法的所有参数,// 用 Optional 解决非空时候的数组
        Optional.of(Arrays.stream(context.getParameters()))
                .ifPresent(stream -> {stream.forEach(object -> Log.infov("parameter type [{0}], value [{1}]",
                                                       object.getClass().getSimpleName(),
                                                       object)
                    );
                });

        return context.proceed();}
}
  • 应用拦截器的 bean,其 hello 办法有两个入参,失常状况下会在拦截器中打印进去
@ApplicationScoped
@TrackParams
public class TrackParamsDemo {public void hello(String name, int id) {Log.infov("Hello {0}, your id is {1}", name, id);
    }
}
  • 测试类,调用了 TrackParamsDemo 的 hello 办法
@QuarkusTest
public class InterceptorTest {

    @Inject
    TrackParamsDemo trackParamsDemo;

    @Test
    public void testTrackParams() {trackParamsDemo.hello("Tom", 101);
    }
}
  • 执行单元测试,控制台输入如下,可见 hello 办法的两个入参的类型和值都被拦截器打印进去了

2022-03-27 17:15:46,582 INFO  [io.quarkus] (main) Quarkus 2.7.3.Final on JVM started in 0.905s. Listening on: http://localhost:8081
2022-03-27 17:15:46,582 INFO  [io.quarkus] (main) Profile test activated. 
2022-03-27 17:15:46,582 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, narayana-jta, resteasy, smallrye-context-propagation, vertx]
2022-03-27 17:15:46,587 INFO  [com.bol.int.dem.HandleonstructionDemo] (main) construction of HandleonstructionDemo
2022-03-27 17:15:46,827 INFO  [com.bol.int.imp.TrackParamsInterceptor] (main) parameter type [String], value [Tom]
2022-03-27 17:15:46,827 INFO  [com.bol.int.imp.TrackParamsInterceptor] (main) parameter type [Integer], value [101]
2022-03-27 17:15:46,827 INFO  [com.bol.int.dem.TrackParamsDemo] (main) Hello Tom, your id is 101
2022-03-27 17:15:46,845 INFO  [io.quarkus] (main) Quarkus stopped in 0.015s
  • 以上就是获取被拦挡办法入参的操作了,如果被拦挡的构造方法也有入参,也能用此形式全副获取到

多个拦截器之间传递参数

  • 多个拦截器拦挡同一个办法是很失常的,他们各司其职,依据优先级按程序执行,如果这些拦截器之间有肯定逻辑关系,例如第二个拦截器须要第一个拦截器的执行后果,此时又该如何呢?
  • quarkus 反对不同拦截器间共享同一个上下文的数据(这让我想到了数据总线),接下来就演示多个拦截器之间是如何共享数据的
  • 首先,定义拦截器,这里减少了一个常量 <font color=”blue”>KEY_PROCEED_INTERCEPTORS</font>,前面在拦截器的实现中会用到
@InterceptorBinding
@Target({TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ContextData {String KEY_PROCEED_INTERCEPTORS = "proceedInterceptors";}
  • 其次,首先拦截器,因为要演示多个拦截器共享数据,这里会有两个拦截器,为了简化开发,先写个父类,把两个拦截器的公共代码写入父类,可见拦截器之间共享数据的要害是 <font color=”blue”>context.getContextData()</font> 办法的返回值,这是个 map,某个拦截器向此 map 中放入的数据,能够在前面的拦截器中获得,这里为了演示,将以后实例的类名存入了 map 中
package com.bolingcavalry.interceptor.impl;

import io.quarkus.logging.Log;

import javax.interceptor.InvocationContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.bolingcavalry.interceptor.define.ContextData.KEY_PROCEED_INTERCEPTORS;

public class BaseContextDataInterceptor {Object execute(InvocationContext context) throws Exception {
        // 取出保留拦截器间共享数据的 map
        Map<String, Object> map = context.getContextData();

        List<String> list;

        String instanceClassName = this.getClass().getSimpleName();

        // 依据指定 key 从 map 中获取一个 list
        if (map.containsKey(KEY_PROCEED_INTERCEPTORS)) {list = (List<String>) map.get(KEY_PROCEED_INTERCEPTORS);
        } else {
            // 如果 map 中没有,就在此新建一个 list,存如 map 中
            list = new ArrayList<>();
            map.put(KEY_PROCEED_INTERCEPTORS, list);

            Log.infov("from {0}, this is first processor", instanceClassName);
        }

        // 将本身内容存入 list 中,这样下一个拦截器只有是 BaseContextDataInterceptor 的子类,// 就能获得后面所有执行过拦挡操作的拦截器
        list.add(instanceClassName);

        Log.infov("From {0}, all processors {0}", instanceClassName, list);

        return context.proceed();}
}
  • 再新建一个拦截器实现类 ContextDataInterceptorA,是 BaseContextDataInterceptor 的子类:
@ContextData
@Interceptor
@Priority(Interceptor.Priority.APPLICATION + 1)
public class ContextDataInterceptorA extends BaseContextDataInterceptor {

    @AroundInvoke
    Object execute(InvocationContext context) throws Exception {return super.execute(context);
    }
}
  • 另一个拦截器实现类 ContextDataInterceptorB,留神它的 Priority 注解的值,表明其优先级低于 ContextDataInterceptorA
@ContextData
@Interceptor
@Priority(Interceptor.Priority.APPLICATION + 2)
public class ContextDataInterceptorB extends BaseContextDataInterceptor {

    @AroundInvoke
    Object execute(InvocationContext context) throws Exception {return super.execute(context);
    }
}
  • 而后是被拦挡 bean
@ApplicationScoped
@ContextData
public class ContextDataDemo {public void hello() {Log.info("Hello world!");
    }
}
  • 单元测试代码
@QuarkusTest
public class InterceptorTest {

    @Inject
    ContextDataDemo contextDataDemo;

    @Test
    public void testContextData() {contextDataDemo.hello();
    }
}
  • 执行单元测试,控制台输出如下,可见执行程序别离是 ContextDataInterceptorA、ContextDataInterceptorB、被拦挡办法,另外,寄存在共享数据中的内容也随着拦截器的执行,越来越多,合乎预期
2022-03-27 23:29:27,703 INFO  [io.quarkus] (main) Quarkus 2.7.3.Final on JVM started in 0.903s. Listening on: http://localhost:8081
2022-03-27 23:29:27,703 INFO  [io.quarkus] (main) Profile test activated. 
2022-03-27 23:29:27,703 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, narayana-jta, resteasy, smallrye-context-propagation, vertx]
2022-03-27 23:29:27,708 INFO  [com.bol.int.dem.HandleonstructionDemo] (main) construction of HandleonstructionDemo
2022-03-27 23:29:27,952 INFO  [com.bol.int.imp.BaseContextDataInterceptor] (main) from ContextDataInterceptorA, this is first processor
2022-03-27 23:29:27,953 INFO  [com.bol.int.imp.BaseContextDataInterceptor] (main) From ContextDataInterceptorA, all processors ContextDataInterceptorA
2022-03-27 23:29:27,953 INFO  [com.bol.int.imp.BaseContextDataInterceptor] (main) From ContextDataInterceptorB, all processors ContextDataInterceptorB
2022-03-27 23:29:27,953 INFO  [com.bol.int.dem.ContextDataDemo] (main) Hello world!
2022-03-27 23:29:27,971 INFO  [io.quarkus] (main) Quarkus stopped in 0.015s
  • 至此,无关拦截器的实战曾经实现,往后不论是自建拦截器还是应用已有拦截器,置信您都能从容应对,信手拈来,有了拦截器,咱们在加强利用能力的同时还能放弃低耦合性,用好它,打造更欠缺的利用。

源码下载

  • 本篇实战的残缺源码可在 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 我的项目中有多个文件夹,本次实战的源码在 <font color=”blue”>quarkus-tutorials</font> 文件夹下,如下图红框
    <img src=”https://typora-pictures-1253575040.cos.ap-guangzhou.myqcloud.com/image-20220312091203116.png” alt=”image-20220312091203116″ style=”zoom: 80%;” />
  • <font color=”blue”>quarkus-tutorials</font> 是个父工程,外面有多个 module,本篇实战的 module 是 <font color=”red”>basic-di</font>,如下图红框
    <img src=”https://typora-pictures-1253575040.cos.ap-guangzhou.myqcloud.com/image-20220312091404031.png” alt=”image-20220312091404031″ style=”zoom:80%;” />

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

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

退出移动版