quarkus依赖注入之五:拦截器(Interceptor)

这篇具有很好参考价值的文章主要介绍了quarkus依赖注入之五:拦截器(Interceptor)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

欢迎访问我的GitHub

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

本篇概览

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

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

  • 定义和使用拦截器一共要做三件事:
  1. 定义:新增一个注解(假设名为A),要用@InterceptorBinding修饰该注解
  2. 实现:拦截器A到底要做什么事情,需要在一个类中实现,该类要用两个注解来修饰:A和Interceptor
  3. 使用:用A来修饰要拦截器的bean
  • 整个流程如下图所示
quarkus依赖注入之五:拦截器(Interceptor)
  • 接下来通过实战掌握拦截器的开发和使用,从最常见的拦截异常开始

拦截异常

  • 写一个拦截器,在程序发生异常的时候可以捕获到并将异常打印出来

  • 首先是定义一个拦截器,这里的拦截器名为HandleError,注意要用InterceptorBinding修饰

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. Priority注解的作用是设定HandleError拦截器的优先级(值越小优先级越高),可以同时用多个拦截器拦截同一个方法
  2. AroundInvoke注解的作用,是表明execute会在拦截bean方法时被调用
  3. proceed方法的作用,并非是执行被拦截的方法,而是执行下一个拦截器,直到最后一个拦截器才会执行被拦截的方法
  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();
    }
}
  • 执行单元测试,如下图红框所示,拦截器捕获了异常并打印出异常信息
quarkus依赖注入之五:拦截器(Interceptor)
  • 至此,拦截异常的操作就完成了,除了用AroundInvoke拦截普通的bean方法,还能用AroundConstruct拦截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. AroundConstruct注解修饰后,execute方法会在bean的构造方法执行时被调用
  2. context.getTarget()的返回值,只有在context.proceed执行后才不为空
  • 拦截器的使用,用HandleConstruction修饰要拦截的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 {
}
  • 拦截器实现,可以用context.getParameters方法取得被拦截方法的入参数组,然后遍历并打印
@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支持不同拦截器间共享同一个上下文的数据(这让我想到了数据总线),接下来就演示多个拦截器之间是如何共享数据的
  • 首先,定义拦截器,这里增加了一个常量KEY_PROCEED_INTERCEPTORS,后面在拦截器的实现中会用到
@InterceptorBinding
@Target({TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ContextData {
    String KEY_PROCEED_INTERCEPTORS = "proceedInterceptors";
}
  • 其次,首先拦截器,因为要演示多个拦截器共享数据,这里会有两个拦截器,为了简化开发,先写个父类,把两个拦截器的公共代码写入父类,可见拦截器之间共享数据的关键是context.getContextData()方法的返回值,这是个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项目中有多个文件夹,本次实战的源码在quarkus-tutorials文件夹下,如下图红框
    quarkus依赖注入之五:拦截器(Interceptor)
  • quarkus-tutorials是个父工程,里面有多个module,本篇实战的module是basic-di,如下图红框
    quarkus依赖注入之五:拦截器(Interceptor)

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

学习路上,你不孤单,欣宸原创一路相伴...文章来源地址https://www.toymoban.com/news/detail-622691.html

到了这里,关于quarkus依赖注入之五:拦截器(Interceptor)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • SpringMVC拦截器 (Interceptor)

            Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、判断用户是否登录等。         拦截器依赖于web框架,在实现上基于Java的反射机制,属于面向切面编程(AOP)的

    2024年01月22日
    浏览(14)
  • SpringMVC的拦截器(Interceptor)

    SpringMVC的拦截器(Interceptor)

    对于拦截器这节的知识,我们需要学习如下内容: 拦截器概念 入门案例 拦截器参数 拦截器工作流程分析 讲解拦截器的概念之前,我们先看一张图: (1)浏览器发送一个请求会先到Tomcat的web服务器 (2)Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源 (3)如果是

    2024年02月09日
    浏览(6)
  • SpringBoot(八)拦截器Interceptor

    SpringBoot(八)拦截器Interceptor

        上篇介绍了Filter过滤器的使用,提起过滤器,就不得不再提起另外一个叫做拦截器的东西。两者的作用类似,都可以实现拦截请求的作用,但其实两者有着非常大的区别。本篇,我们就来学习下拦截器的使用。     如果你是新手,且没看过我之前的一系列SpringBoot文章,

    2024年02月17日
    浏览(6)
  • 【SpringBoot】拦截器(Interceptor)的使用

            拦截器(Interceptor)是一种特殊的组件,它可以在请求处理的过程中对请求和响应进行拦截和处理。拦截器可以在请求到达目标处理器之前、处理器处理请求之后以及视图渲染之前执行特定的操作。拦截器的主要目的是在不修改原有代码的情况下,实现对请求和响

    2024年02月04日
    浏览(10)
  • Spring Boot拦截器(Interceptor)详解

    **拦截器(Interceptor)**同 Filter 过滤器一样,它俩都是面向切面编程——AOP 的具体实现(AOP切面编程只是一种编程思想而已)。 你可以使用 Interceptor 来执行某些任务,例如在 Controller 处理请求之前编写日志,添加或更新配置…… 在 Spring 中,当请求发送到 Controller 时,在被 Contr

    2024年02月03日
    浏览(8)
  • 过滤器Filter,拦截器Interceptor

    过滤器Filter,拦截器Interceptor

    过滤器Filter 快速入门   详情 登录校验-Filter 拦截器Interceptor 简介快速入门 定义拦截器 配置拦截器 详解(拦截路径,执行流程) 登录校验-Interceptor

    2024年02月07日
    浏览(10)
  • SpringBoot自定义拦截器interceptor使用详解

    SpringBoot自定义拦截器interceptor使用详解

    Spring Boot拦截器Intercepter详解 Intercepter是由Spring提供的Intercepter拦截器,主要应用在日志记录、权限校验等安全管理方便。 使用过程 1.创建自定义拦截器,实现HandlerInterceptor接口,并按照要求重写指定方法 HandlerInterceptor接口源码: 根据源码可看出HandlerInterceptor接口提供了三个

    2024年02月13日
    浏览(16)
  • Spring MVC拦截器Interceptor使用(判断用户登录)

    Spring MVC拦截器Interceptor使用(判断用户登录)

    Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。 拦截器可以在进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是

    2024年02月09日
    浏览(9)
  • 7.5 SpringBoot 拦截器Interceptor实战 统一角色权限校验

    7.5 SpringBoot 拦截器Interceptor实战 统一角色权限校验

    在【 7.1 】管理员图书录入和修改API,当时预告过:并没有写【校验是否是管理员】的逻辑,因为是通用逻辑,会单写一篇来细讲,那么今天就来安排! 角色权限校验 ,是保证接口安全必备的能力:有权限才可以操作!所以,一般对于这种通用逻辑,推荐不与主业务逻辑耦合

    2024年02月16日
    浏览(6)
  • 过滤器(Filter)和拦截器(Interceptor)有什么不同?

    过滤器(Filter)和拦截器(Interceptor)有什么不同?

    过滤器(Filter)和拦截器(Interceptor)是用于处理请求和响应的中间件组件,但它们在实现方式和应用场景上有一些不同。 过滤器 是Servlet规范中定义的一种组件,通常以Java类的形式实现。过滤器通过在 web.xml 配置文件中声明来注册,并在Web应用程序的请求和响应链中拦截请

    2024年02月07日
    浏览(9)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包