欢送拜访我的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@HandleErrorpublic class HandleErrorDemo {    public void executeThrowError() {        throw new IllegalArgumentException("this is business logic exception");    }}
  • 验证拦截器的单元测试代码如下,只有执行HandleErrorDemo的executeThrowError办法就会抛出异样,而后察看日志中是否有拦截器日志信息即可验证拦截器是否合乎预期
@QuarkusTestpublic 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@HandleConstructionpublic class HandleonstructionDemo {    public HandleonstructionDemo() {        super();        Log.infov("construction of {0}", HandleonstructionDemo.class.getSimpleName());    }    public void hello() {        Log.info("hello world!");    }}
  • 用单元测试来验证拦截器是否胜利拦挡构造方法
@QuarkusTestpublic 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:80812022-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 HandleonstructionDemo2022-03-27 15:51:03,397 INFO  [com.bol.int.imp.HandleConstructionInterceptor] (main) start construction interceptor2022-03-27 15:51:03,398 INFO  [com.bol.int.dem.HandleonstructionDemo] (main) construction of HandleonstructionDemo2022-03-27 15:51:03,398 INFO  [com.bol.int.imp.HandleConstructionInterceptor] (main) bean instance of HandleonstructionDemo2022-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@TrackParamspublic class TrackParamsDemo {    public void hello(String name, int id) {        Log.infov("Hello {0}, your id is {1}", name, id);    }}
  • 测试类,调用了TrackParamsDemo的hello办法
@QuarkusTestpublic 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:80812022-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 HandleonstructionDemo2022-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 1012022-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@ContextDatapublic class ContextDataDemo {    public void hello() {        Log.info("Hello world!");    }}
  • 单元测试代码
@QuarkusTestpublic 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:80812022-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 HandleonstructionDemo2022-03-27 23:29:27,952 INFO  [com.bol.int.imp.BaseContextDataInterceptor] (main) from ContextDataInterceptorA, this is first processor2022-03-27 23:29:27,953 INFO  [com.bol.int.imp.BaseContextDataInterceptor] (main) From ContextDataInterceptorA, all processors ContextDataInterceptorA2022-03-27 23:29:27,953 INFO  [com.bol.int.imp.BaseContextDataInterceptor] (main) From ContextDataInterceptorB, all processors ContextDataInterceptorB2022-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%;" />

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

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