关键词:
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- 本文是《quarkus依赖注入》系列的第五篇,经过前面的学习,咱们熟悉了依赖注入的基本特性,接下来进一步了解相关的高级特性,先从本篇的拦截器开始
- 如果您熟悉spring的话,对拦截器应该不会陌生,通过拦截器可以将各种附加功能与被拦截代码的主体解耦合,例如异常处理、日志、数据同步等多种场景
- 本篇会演示如何自定义拦截器,以及如何对bean的方法进行进行拦截,由以下章节构成
- 定义和使用拦截器的操作步骤介绍
- 拦截异常
- 拦截构造方法
- 获取被拦截方法的参数
- 多个拦截器之间传递参数
定义和使用拦截器的操作步骤介绍
- 定义和使用拦截器一共要做三件事:
- 定义:新增一个注解(假设名为A),要用@InterceptorBinding修饰该注解
- 实现:拦截器A到底要做什么事情,需要在一个类中实现,该类要用两个注解来修饰:A和Interceptor
- 使用:用A来修饰要拦截器的bean
- 整个流程如下图所示
- 接下来通过实战掌握拦截器的开发和使用,从最常见的拦截异常开始
拦截异常
-
写一个拦截器,在程序发生异常的时候可以捕获到并将异常打印出来
-
首先是定义一个拦截器,这里的拦截器名为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;
- 上述代码有以下四点需要注意:
- Priority注解的作用是设定HandleError拦截器的优先级(值越小优先级越高),可以同时用多个拦截器拦截同一个方法
- AroundInvoke注解的作用,是表明execute会在拦截bean方法时被调用
- proceed方法的作用,并非是执行被拦截的方法,而是执行下一个拦截器,直到最后一个拦截器才会执行被拦截的方法
- 可以从入参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();
- 执行单元测试,如下图红框所示,拦截器捕获了异常并打印出异常信息
- 至此,拦截异常的操作就完成了,除了用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());
- 上述代码有两处要注意的
- 被AroundConstruct注解修饰后,execute方法会在bean的构造方法执行时被调用
- 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-tutorials是个父工程,里面有多个module,本篇实战的module是basic-di,如下图红框
你不孤单,欣宸原创一路相伴
quarkus依赖注入之八:装饰器(decorator)(代码片段)
...源码):https://github.com/zq2599/blog_demos本篇概览本篇是《quarkus依赖注入》系列的第八篇,目标是掌握quarkus实现的一个CDI特性:装饰器(Decorator)提到装饰器,熟悉设计模式的读者应该会想到装饰器 查看详情
quarkus依赖注入之四:选择注入bean的高级手段(代码片段)
...源码):https://github.com/zq2599/blog_demos本篇概览本文是《quarkus依赖注入》系列的第四篇,在应用中,一个接口有多个实现是很常见的,那么依赖注入时,如果类型是接口,如何准确选择实现呢?前文介绍... 查看详情
quarkus依赖注入之六:发布和消费事件(代码片段)
...源码):https://github.com/zq2599/blog_demos本篇概览本文是《quarkus依赖注入》系列的第六篇,主要内容是学习事件的发布和接收如果您用过Kafka、RabbitMQ等消息中间件,对消息的作用应该不会陌生,通过消息的订阅和发布... 查看详情
quarkus依赖注入之七:生命周期回调(代码片段)
欢迎访问我的GitHub这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos本篇概览本篇的知识点是bean的生命周期回调:在bean生命周期的不同阶段,都可以触发自定义代码的执行触发自定义代码... 查看详情
使用unity进行aop对象拦截
Unity 是一款知名的依赖注入容器(dependencyinjectioncontainer),其支持通过自定义扩展来扩充功能。在Unity软件包内默认包含了一个对象拦截(Interception)扩展定义。本篇文章将介绍如何使用对象拦截(Interception)来分离横切关注... 查看详情
quarkus依赖注入之十:学习和改变bean懒加载规则(代码片段)
...源码):https://github.com/zq2599/blog_demos本篇概览本篇是《quarkus依赖注入》系列的第十篇,来看一个容易被忽略的知识点:bean的懒加载,咱们先去了解quarkus框架下的懒加载规则,然后更重要的是掌握如何改变规则&... 查看详情
将 TranslateService 注入拦截器时的 Angular 循环依赖
】将TranslateService注入拦截器时的Angular循环依赖【英文标题】:AngularCirculardependencywheninjectTranslateServicetointerceptor【发布时间】:2021-07-1301:50:00【问题描述】:我在将依赖项注入拦截器时遇到问题。我想将TranslateService注入HttpErrorIn... 查看详情
quarkus依赖注入之六:发布和消费事件(代码片段)
...源码):https://github.com/zq2599/blog_demos本篇概览本文是《quarkus依赖注入》系列的第六篇,主要内容是学习事件的发布和接收如果您用过Kafka、RabbitMQ等消息中间件,对消息的作用应该不会陌生,通过消息的订阅和发布... 查看详情
quarkus依赖注入之九:bean读写锁(代码片段)
...源码):https://github.com/zq2599/blog_demos本篇概览本篇是《quarkus依赖注入》的第九篇,目标是在轻松的气氛中学习一个小技能:bean锁quarkus的bean锁本身很简单:用两个注解修饰bean和方法即可,但涉及到多线程同步... 查看详情
将 $state (ui-router) 注入 $http 拦截器会导致循环依赖
】将$state(ui-router)注入$http拦截器会导致循环依赖【英文标题】:Injecting$state(ui-router)into$httpinterceptorcausescirculardependency【发布时间】:2013-12-1209:35:32【问题描述】:我正在努力实现的目标如果$http请求返回401错误,我想转换到某... 查看详情
为啥 Quarkus 会警告我关于私有字段的注入?
】为啥Quarkus会警告我关于私有字段的注入?【英文标题】:WhydoesQuarkuswarnmeaboutinjectioninprivatefields?为什么Quarkus会警告我关于私有字段的注入?【发布时间】:2019-08-0117:19:52【问题描述】:当我在Quarkus应用程序中使用类似以下内... 查看详情
「设计模式」六大原则之五:依赖倒置原则小结(代码片段)
文章目录1.依赖倒置原则(DIP)定义4.DIP举例说明2.如何理解控制反转(IOC)3.如何理解依赖注入(DI)4.小结「设计模式」六大原则系列链接:「设计模式」六大原则之一:单一职责小结「设计模式」六大... 查看详情
「设计模式」六大原则之五:依赖倒置原则小结(代码片段)
文章目录1.依赖倒置原则(DIP)定义4.DIP举例说明2.如何理解控制反转(IOC)3.如何理解依赖注入(DI)4.小结「设计模式」六大原则系列链接:「设计模式」六大原则之一:单一职责小结「设计模式」六大... 查看详情
「设计模式」六大原则之五:依赖倒置原则小结(代码片段)
文章目录1.依赖倒置原则(DIP)定义4.DIP举例说明2.如何理解控制反转(IOC)3.如何理解依赖注入(DI)4.小结「设计模式」六大原则系列链接:「设计模式」六大原则之一:单一职责小结「设计模式」六大... 查看详情
本地依赖的 Quarkus ClassNotFoundException [重复]
】本地依赖的QuarkusClassNotFoundException[重复]【英文标题】:QuarkusClassNotFoundExceptionforlocaldependency[duplicate]【发布时间】:2020-08-0203:00:43【问题描述】:我正在尝试将一个项目迁移到Quarkus。该项目依赖于本地.jar文件。对于驻留在所... 查看详情
vue项目常见之五:路由拦截器(permission),导航守卫(代码片段)
//处理路由拦截器导航守卫importrouterfrom‘../router‘importprogresssfrom‘nprogress‘import‘nprogress/nprogress.css‘//全局前置守卫当路由发生变化时这个方法里的回调函数就会执行router.beforeEach(function(to,from,next)progresss.start()//开启进度条//权... 查看详情
Quarkus 和提供的依赖项 (sapjco3.jar)
】Quarkus和提供的依赖项(sapjco3.jar)【英文标题】:Quarkusandprovideddependencies(sapjco3.jar)【发布时间】:2021-03-0805:51:57【问题描述】:我目前尝试将通过RFC连接到SAP系统的旧JavaEE解决方案迁移到使用Quarkus的方法。由于该项目使用maven,... 查看详情
「设计模式」六大原则之五:依赖倒置原则小结(代码片段)
文章目录1.依赖倒置原则(DIP)定义4.DIP举例说明2.如何理解控制反转(IOC)3.如何理解依赖注入(DI)4.小结「设计模式」六大原则系列链接:「设计模式」六大原则之一:单一职责小结「设计模式」六大... 查看详情