![Quarkus云原生微服务开发实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/908/41309908/b_41309908.jpg)
3.4 使用拦截器实现横切的业务逻辑
拦截器的作用是实现与业务逻辑无关的横切功能。拦截器在框架中得到了广泛的使用。框架提供注解给应用代码来使用。框架在运行时拦截注解所标记的方法,再进行相应的处理。比如,注解@Transactional提供了声明式的事务处理。事务的提交和回滚由框架的拦截器负责实现。
在使用拦截器之前,首先要定义一个拦截器绑定类型。绑定类型的作用是把拦截器的实现和拦截器的使用绑定起来。
下面代码中的注解@HandleError是一个拦截器绑定类型。@HandleError上的元注解@Inter-ceptorBinding声明了这是一个拦截器绑定类型。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/58_02.jpg?sign=1738848024-KHCEIxrRqEsrWaismodq4YOEFejzK1Ii-0-66b9eb6d59d9d67d5f9f9a5c21e16e91)
下面代码中的ErrorHandlingInterceptor是拦截器的实现。注解@HandleError声明了该拦截器实现所绑定的类型,@Interceptor声明了这是一个拦截器的实现,@Priority用来声明拦截器的优先级。
方法execute上的注解@AroundInvoke表明了该方法用来拦截其他方法的执行。该方法只有一个InvocationContext类型的参数,表示方法执行时的上下文。InvocationContext的proceed方法表示继续执行被拦截的方法,并获得返回值。方法execute的逻辑是用try-catch捕获执行中的错误,并记录到日志中。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/58_03.jpg?sign=1738848024-yxq9qjBj2G5Wo97nVX53oJv8Pg0LwGCS-0-28057420335a0bd061b544615b9df5b2)
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/59_01.jpg?sign=1738848024-uZg6Kt8I9Xq1JHnLPz9CjjgjJ11CaMkf-0-d1d64f9a07289cd7fad5b64f0fbab20d)
下面代码中的TestErrorService添加了拦截器绑定类型@HandleError,因此throwError方法在执行时会被ErrorHandlingInterceptor拦截,从而记录下相关的日志。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/59_02.jpg?sign=1738848024-PziLWrLAde6RbM8LwROWbGoaWCWAw8hV-0-cbe09f14fba5c8a587af8cee3cc6d20a)
除了@AroundInvoke之外,还可以使用@AroundConstruct来拦截构造器。下面代码中的Con-structionTracker是与注解@TrackingConstruction绑定的拦截器的实现。在construct方法的实现中,只有在InvocationContext的proceed方法执行完成之后,才可以通过getTarget方法得到新创建的对象实例。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/59_03.jpg?sign=1738848024-IdKqH2IFJsf14B3K1Db71PscHU7qE7Bn-0-f5d29dbf39961e497e570ee53c4d029b)
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/60_01.jpg?sign=1738848024-YntE9e2vmMfJ8tFDIpBY7izmR8VkvZLf-0-b85bd98b461274881966f21933de8ffb)
在每个方法或构造器上,可能存在多个进行处理的拦截器。当存在多个拦截器时,它们按照优先级的顺序组成一个链条来依次执行。优先级的数字越小的拦截器,在执行链条中的位置就越靠前。Interceptor.Priority类中定义了一些优先级的常量。对于应用中创建的拦截器来说,优先级的范围应该在Priority.APPLICATION和Priority.LIBRARY_AFTER之间。如果两个拦截器的优先级相同,那么它们在执行链条中的位置是不确定的。
拦截器链条中的拦截器按照顺序依次执行。InvocationContext的proceed方法的作用是调用链条中的下一个拦截器。对方法拦截器来说,链条中的最后一个拦截器会调用实际的业务方法;对构造器拦截器来说,链条中的最后一个拦截器会调用实际的构造器来创建对象。
InvocationContext还提供了一些方法来访问上下文相关的信息,如表3-3所示。
表3-3 InvocationContext接口的主要方法
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/60_02.jpg?sign=1738848024-KTIYKHRYXdzRcfsNMD1KSUGP508OhiSq-0-206a127d68bfa0f8ec115351b7c81237)
下面代码中的ToUpperCaseInterceptor拦截器展示了getParameters的用法。对于被拦截的方法中类型为String的参数,将其值转换为大写形式,再传递给实际的方法。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/60_03.jpg?sign=1738848024-p6AMs45piBi4QUo4N7gutR8ESmBpB6Gs-0-15cbda63be0b8f691ddcc8be7f1840a1)
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/61_01.jpg?sign=1738848024-ZCUK8V71JKX1F2yiiGovngALtcFTofTk-0-3e6d52be8a57d4152b5611387e57450a)
拦截器还可以改变方法的返回值。下面代码中的NullValueInterceptor拦截器不会调用实际的目标方法,还是简单地返回null。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/61_02.jpg?sign=1738848024-aQfWf1GbUFhIDrMeLfeikY4660wQLcPf-0-e0ee01b2ff124bd6feff11e5700ae686)
如果处理链条中的拦截器之间存在一定的依赖关系,可以使用InvocationContext的getCon-textData方法返回的Map<String,Object>对象来传递数据。
下面代码中的PreProcessInterceptor拦截器在上下文对象中添加了新的值。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/61_03.jpg?sign=1738848024-JyN06Y6yaK74PIq3DKrkeFmP8xk1oZV1-0-eaf8000dbf8893b4283078a2a3d91caa)
下面代码中的PostProcessorInterceptor拦截器使用了PreProcessInterceptor在处理时设置的值。由于PostProcessorInterceptor拦截器的优先级数值大于PreProcessInterceptor,可以确保Post-ProcessorInterceptor处于执行链条的后方位置。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/61_04.jpg?sign=1738848024-6cUSbDN33xoMXZkiGoNRuSb0P8Fr8kDh-0-78eb856ac9acc1e4480bf19829104a9e)
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/62_01.jpg?sign=1738848024-BHzxj8D5b95eCQ5RGFVf2457xkD3il47-0-d9bd5b41863b339460bc011db4ea10c4)
拦截器经常与stereotype一同使用。某些Bean类型通常具备一些共同的特征,表现在这些Bean上会出现同样的CDI注解。为了避免重复地添加CDI注解,可以创建stereotype。在stereo-type上可以添加默认的作用域和拦截器绑定。CDI中的stereotype是声明了元注解@Stereotype的注解类型。
下面代码中的WithErrorHandler类是stereotype的示例。该stereotype上添加了默认的作用域@ApplicationScoped和拦截器绑定@HandleError。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/62_02.jpg?sign=1738848024-wmpvRu6wYO8PTBDSdcA8KXSN2FhuxogX-0-729fa3e05029eb8dcd1db89aa5aba971)
下面代码中的使用了注解@WithErrorHandler,相当于同时添加了注解@ApplicationScoped和@HandleError。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/62_03.jpg?sign=1738848024-zAp9SENRUE0vQnNM5rjmychLXWfF1GTd-0-4a9a9a48c7f9355b8e5dd8a45fa3af10)
Stereotype除了减少不必要的代码重复之外,也方便了以后的更新。如果希望对特定类型的Bean进行修改,只需要修改对应的stereotype的声明即可,而不需要修改使用该stereotype的Bean。