![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=1738871811-6ipVqUx3ZFzlCTuV8tiyt484i44Y2CaY-0-c9876037d9d180ad86dff05a9a1a988b)
下面代码中的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=1738871811-y5SQqXwQndi1X57cBesGvoJDtJfiwTd2-0-38657b5aa5363494e334aab97d778b70)
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/59_01.jpg?sign=1738871811-jdjVJFOKyKZe0nOB0wKYAcFzk6mg6icE-0-3c835dfddcedc5081f2d0ea843c690ff)
下面代码中的TestErrorService添加了拦截器绑定类型@HandleError,因此throwError方法在执行时会被ErrorHandlingInterceptor拦截,从而记录下相关的日志。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/59_02.jpg?sign=1738871811-6wJOxRgnMTFGGE3WrXefxlZ7WeUuevoF-0-0f6c45ee4e680c9f46b4a6d64a870026)
除了@AroundInvoke之外,还可以使用@AroundConstruct来拦截构造器。下面代码中的Con-structionTracker是与注解@TrackingConstruction绑定的拦截器的实现。在construct方法的实现中,只有在InvocationContext的proceed方法执行完成之后,才可以通过getTarget方法得到新创建的对象实例。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/59_03.jpg?sign=1738871811-o1tNy5cq3jQWyzB3GIGXscymSoedFp5b-0-6839c219b9929dc50cb26b1d173513e5)
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/60_01.jpg?sign=1738871811-0xGzzPwVew5lYG15LiUcBfo97ETMLDEm-0-d62d823a2388cab2005cbb8633fcf863)
在每个方法或构造器上,可能存在多个进行处理的拦截器。当存在多个拦截器时,它们按照优先级的顺序组成一个链条来依次执行。优先级的数字越小的拦截器,在执行链条中的位置就越靠前。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=1738871811-w728roFALu7ixQ1OJnD1gTXE7lVYSJiW-0-df13c4b569417a8872f8b9734f86416e)
下面代码中的ToUpperCaseInterceptor拦截器展示了getParameters的用法。对于被拦截的方法中类型为String的参数,将其值转换为大写形式,再传递给实际的方法。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/60_03.jpg?sign=1738871811-fmJA6SlBrDNGj1f8irSQYe7XFKlCmGXX-0-8681ed817df763ca84a15d922cbad055)
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/61_01.jpg?sign=1738871811-c3xcc5pTBQVKcbH8NIfr5jcwkIsAyelZ-0-020f366a98316fb4efe1edc81d4326f1)
拦截器还可以改变方法的返回值。下面代码中的NullValueInterceptor拦截器不会调用实际的目标方法,还是简单地返回null。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/61_02.jpg?sign=1738871811-atcwG2a5i5uAPqxLmQnDU0BitOdu2qKA-0-291561aef279d8ab87b68e6ad33f13bb)
如果处理链条中的拦截器之间存在一定的依赖关系,可以使用InvocationContext的getCon-textData方法返回的Map<String,Object>对象来传递数据。
下面代码中的PreProcessInterceptor拦截器在上下文对象中添加了新的值。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/61_03.jpg?sign=1738871811-XWZvzxdZ49uQWMxIh0y8sSXDucmnT46a-0-0f5af92c2e6572336086af1d3feb2e26)
下面代码中的PostProcessorInterceptor拦截器使用了PreProcessInterceptor在处理时设置的值。由于PostProcessorInterceptor拦截器的优先级数值大于PreProcessInterceptor,可以确保Post-ProcessorInterceptor处于执行链条的后方位置。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/61_04.jpg?sign=1738871811-Idji4o9ekbHgkeDfRhj2x9FqNi5WHv7W-0-94231e6fbc29e53eef45a323b6e3ab02)
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/62_01.jpg?sign=1738871811-5xY1cZA9TlZzISzzyXJTsGmoPMKrHAQe-0-309fad69fe3162257900ca77032934b9)
拦截器经常与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=1738871811-NKHNwm1QrehS84VlpOYpSdrm8T9PfmOY-0-7136da232f6ebf41171e806b3954994f)
下面代码中的使用了注解@WithErrorHandler,相当于同时添加了注解@ApplicationScoped和@HandleError。
![](https://epubservercos.yuewen.com/FA8305/21511157508183006/epubprivate/OEBPS/Images/62_03.jpg?sign=1738871811-fvconP3DCEJYTZlf1NMLLL3CEm9gXWCQ-0-04f1d7e87659c10ea822255fd0670400)
Stereotype除了减少不必要的代码重复之外,也方便了以后的更新。如果希望对特定类型的Bean进行修改,只需要修改对应的stereotype的声明即可,而不需要修改使用该stereotype的Bean。