3.4.1 活用切点表达式
Spring AOP的一大特色在于为开发人员提供了非常灵活的切点机制。Spring在编译期间处理切入点,并尝试进行优化匹配。然而,检查代码中的匹配规则将是一个代价高昂的过程。因此,为了获得最佳性能,我们需要仔细考虑想要实现的目标,并尽可能缩小搜索或匹配条件的范围。
我们在3.1.2节中已经看到过一个切点表达式,如代码清单3-21所示。
代码清单3-21 切点表达式代码
@Pointcut("execution(* com.springboot.aop.service.AccountService.doAccountTransaction(..))") public void doAccountTransaction() {}
这里的execution()代表的就是表达式的主体,它的基本语法如代码清单3-22所示,其中“?”部分表示可选项,可以为空。
代码清单3-22 execution()基本语法
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
这个语法看似复杂,但是我们逐个分解所有的模式,它们其实就是描述了一个方法的特征。
- modifiers-pattern:表示方法的修饰符。
- ret-type-pattern:表示方法的返回值。
- declaring-type-pattern:表示方法所在的类的路径。
- name-pattern:表示方法名。
- param-pattern:表示方法的参数。
- throws-pattern:表示方法抛出的异常。
这些模式的作用就是完成切点的匹配。在各个模式中,可以使用“*”来表示匹配所有选项。Spring AOP还为开发人员提供了一组非常有用的描述符来简化切点表达式的使用过程。例如,args描述符表示方法的参数属于一个特定的类;within描述符表示方法属于一个特定的类;target描述符表示方法所属的类等。关于这些描述符的具体使用方法,可以参考Spring AOP的官方文档:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core。
为了获得良好的性能,在设计切点表达式时,至少应该包含方法和类型模式。这并不是说如果只使用方法或类型模式中的一种,匹配就会不生效,而是因为类型模式的匹配过程非常快,它通过快速选择无法进一步处理的连接点来缩小搜索空间。
同时,建议在空方法上声明切点,并通过空方法名引用这些切点。我们在3.1.2节中定义的doAccountTransaction()方法就是一个很好的空方法。基于这种定义,针对需要对切点表达式进行任何更改的场景,只需要修改一个位置即可。
另外一项最佳实践在于尽量声明小的切点,并把它们组合起来构建复杂的切点。代码清单3-23展示了定义小切点并将它们连接起来的代码示例。
代码清单3-23 定义并连接小切点代码示例
@Pointcut("execution(public * *(..))") private void anyPublicMethod() {} @Pointcut("execution(* com.springboot.aop.service.AccountService.doAccountTransaction(..))") public void doAccountTransaction() {} @Pointcut("anyPublicMethod() && doAccountTransaction()") private void transactionOperation() {}
这里的transactionOperation()就是由anyPublicMethod()和doAccountTransaction()这两个切点组合而成的。在日常开发过程中,我们可以根据需要定义各种粒度的切点,并把它们灵活地进行组合。