
3.4 使用拦截器实现横切的业务逻辑
拦截器的作用是实现与业务逻辑无关的横切功能。拦截器在框架中得到了广泛的使用。框架提供注解给应用代码来使用。框架在运行时拦截注解所标记的方法,再进行相应的处理。比如,注解@Transactional提供了声明式的事务处理。事务的提交和回滚由框架的拦截器负责实现。
在使用拦截器之前,首先要定义一个拦截器绑定类型。绑定类型的作用是把拦截器的实现和拦截器的使用绑定起来。
下面代码中的注解@HandleError是一个拦截器绑定类型。@HandleError上的元注解@Inter-ceptorBinding声明了这是一个拦截器绑定类型。

下面代码中的ErrorHandlingInterceptor是拦截器的实现。注解@HandleError声明了该拦截器实现所绑定的类型,@Interceptor声明了这是一个拦截器的实现,@Priority用来声明拦截器的优先级。
方法execute上的注解@AroundInvoke表明了该方法用来拦截其他方法的执行。该方法只有一个InvocationContext类型的参数,表示方法执行时的上下文。InvocationContext的proceed方法表示继续执行被拦截的方法,并获得返回值。方法execute的逻辑是用try-catch捕获执行中的错误,并记录到日志中。


下面代码中的TestErrorService添加了拦截器绑定类型@HandleError,因此throwError方法在执行时会被ErrorHandlingInterceptor拦截,从而记录下相关的日志。

除了@AroundInvoke之外,还可以使用@AroundConstruct来拦截构造器。下面代码中的Con-structionTracker是与注解@TrackingConstruction绑定的拦截器的实现。在construct方法的实现中,只有在InvocationContext的proceed方法执行完成之后,才可以通过getTarget方法得到新创建的对象实例。


在每个方法或构造器上,可能存在多个进行处理的拦截器。当存在多个拦截器时,它们按照优先级的顺序组成一个链条来依次执行。优先级的数字越小的拦截器,在执行链条中的位置就越靠前。Interceptor.Priority类中定义了一些优先级的常量。对于应用中创建的拦截器来说,优先级的范围应该在Priority.APPLICATION和Priority.LIBRARY_AFTER之间。如果两个拦截器的优先级相同,那么它们在执行链条中的位置是不确定的。
拦截器链条中的拦截器按照顺序依次执行。InvocationContext的proceed方法的作用是调用链条中的下一个拦截器。对方法拦截器来说,链条中的最后一个拦截器会调用实际的业务方法;对构造器拦截器来说,链条中的最后一个拦截器会调用实际的构造器来创建对象。
InvocationContext还提供了一些方法来访问上下文相关的信息,如表3-3所示。
表3-3 InvocationContext接口的主要方法

下面代码中的ToUpperCaseInterceptor拦截器展示了getParameters的用法。对于被拦截的方法中类型为String的参数,将其值转换为大写形式,再传递给实际的方法。


拦截器还可以改变方法的返回值。下面代码中的NullValueInterceptor拦截器不会调用实际的目标方法,还是简单地返回null。

如果处理链条中的拦截器之间存在一定的依赖关系,可以使用InvocationContext的getCon-textData方法返回的Map<String,Object>对象来传递数据。
下面代码中的PreProcessInterceptor拦截器在上下文对象中添加了新的值。

下面代码中的PostProcessorInterceptor拦截器使用了PreProcessInterceptor在处理时设置的值。由于PostProcessorInterceptor拦截器的优先级数值大于PreProcessInterceptor,可以确保Post-ProcessorInterceptor处于执行链条的后方位置。


拦截器经常与stereotype一同使用。某些Bean类型通常具备一些共同的特征,表现在这些Bean上会出现同样的CDI注解。为了避免重复地添加CDI注解,可以创建stereotype。在stereo-type上可以添加默认的作用域和拦截器绑定。CDI中的stereotype是声明了元注解@Stereotype的注解类型。
下面代码中的WithErrorHandler类是stereotype的示例。该stereotype上添加了默认的作用域@ApplicationScoped和拦截器绑定@HandleError。

下面代码中的使用了注解@WithErrorHandler,相当于同时添加了注解@ApplicationScoped和@HandleError。

Stereotype除了减少不必要的代码重复之外,也方便了以后的更新。如果希望对特定类型的Bean进行修改,只需要修改对应的stereotype的声明即可,而不需要修改使用该stereotype的Bean。