让我们看看Spring是如何处理切入点这个重要概念的。
Spring的切入点模型使得切入点可以独立于通知类型进行重用,这就使得针对不同 advice使用相同的pointcut成为可能。
org.springframework.aop.Pointcut
是最核心的接口,用来将
通知应用于特定的类和方法,完整的接口定义如下:
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); }
将Pointcut
接口分割成有利于重用类和方法匹配的两部分,以及进行更细粒度的操作组合(例如与另一个方法匹配实现进行“或操作”)。
ClassFilter
接口用来将切入点限定在一个给定的类集合中。如果matches()
方法总是返回true,所有目标类都将被匹配:
public interface ClassFilter { boolean matches(Class clazz); }
MethodMatcher
接口通常更重要,完整的接口定义如下:
public interface MethodMatcher { boolean matches(Method m, Class targetClass); boolean isRuntime(); boolean matches(Method m, Class targetClass, Object[] args); }
matches(Method, Class)
方法用来测试这个切入点是否匹配目标类的指定方法。这将在AOP代理被创建的时候执行,这样可以避免在每次方法调用的时候都执行。如果matches(Method, Class )
对于一个给定的方法返回true,并且isRuntime()
也返回true,那么matches(Method, Class , Object[])
将在每个方法调用的时候被调用。这使得切入点在通知将被执行前可以查看传入到方法的参数。
大多数MethodMatcher是静态的,这意味着isRuntime()
方法返回
false。在这种情况下,matches(Method, Class , Object[])
永远不会被调用。
提示
应尽可能地使切入点是静态的,这就允许AOP框架在AOP代理被创建时缓存对切入点的计算结果。
Spring在切入点上支持以下运算: 或和与。
或运算表示只需有一个切入点被匹配就执行方法。
与运算表示所有的切入点都匹配的情况下才执行。
通常或运算要更有用一些。
切入点可以使用org.springframework.aop.support.Pointcuts类中的静态方法来编写,或者使用同一个包内的ComposablePointcut类。不过使用AspectJ切入点表达式通常会更简单一些。
从2.0开始,Spring中使用的最重要的切入点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut
。 这个切入点使用AspectJ提供的库来解析满足AspectJ语法切入点表达式字符串。
可以查看前一章关于所支持的AspectJ切入点原语的讨论。
Spring提供了一些很方便的切入点实现。一些是开箱即用的,其它的是切入点应用规范的子集。
静态切入点基于方法和目标类进行切入点判断而不考虑方法的参数。在多数情况下,静态切入点是高效的、最好的选择。 Spring只在第一次调用方法时执行静态切入点:以后每次调用这个方法时就不需要再执行。
让我们考虑Spring中的一些静态切入点实现。
显而易见,一种描述静态切入点的方式是使用正则表达式。包含Spring在内的一些AOP框架都支持这种方式。
org.springframework.aop.support.Perl5RegexpMethodPointcut
是一个最基本的正则表达式切入点,
它使用Perl 5正则表达式语法。Perl5RegexpMethodPointcut
依赖Jakarta ORO进行正则表达式匹配。
Spring也提供了JdkRegexpMethodPointcut
类,它使用JDK 1.4或更高版本里提供的正则表达式支持。
使用Perl5RegexpMethodPointcut
类,你可以提供一组模式字符串。
如果其中任意一个模式匹配,切入点将被解析为true。(实际上就是这些切入点的或集。)
下面显示用法:
<bean id="settersAndAbsquatulatePointcut" class="org.springframework.aop.support.Perl5RegexpMethodPointcut"> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean>
Spring提供了一个方便的类
RegexpMethodPointcutAdvisor
,
它也允许我们引用一个通知(记住这里一个通知可以是拦截器,前置通知(before advice),异常通知(throws advice)等类型中的一个)。
在背后,如果使用J2SE 1.4或者以上版本,Spring将使用JdkRegexpMethodPointcut
,在之前版本的虚拟机上,Spring将退回去使用Perl5RegexpMethodPointcut
。
可以通过设置perl5
属性为true来强制使用Perl5RegexpMethodPointcut
。使用RegexpMethodPointcutAdvisor
可以简化织入,因为一个bean可以同时作为切入点和advisor,如下所示:
<bean id="settersAndAbsquatulateAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local="beanNameOfAopAllianceInterceptor"/> </property> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </bean>
RegexpMethodPointcutAdvisor可以和任何通知类型一起使用
动态切入点比起静态切入点在执行时要消耗更多的资源。它们同时计算方法参数和静态信息。 这意味着它们必须在每次方法调用时都被执行;由于参数的不同,评估结果不能被缓存。
动态切入点的常见例子是控制流
切入点。
Spring控制流切入点在概念上和AspectJ的cflow 切入点很相似,
虽然它的功能不如后者那么强大。(目前还不能让一个切入点在另外一个切入点所评估的连接点处执行)。
一个控制流切入点匹配当前的调用栈。例如,一个连接点被com.mycompany.web
包内的一个
方法或者SomeCaller
类调用,切入点就可能被激活。
控制流切入点是由org.springframework.aop.support.ControlFlowPointcut
类声明的。
注意
在执行时控制流切入点的开销是非常昂贵的,甚至与其它动态切入点比起来也是如此。在Java 1.4里,它的开销差不多是其它动态切入点的5倍;在Java 1.3中,这个比例甚至达到10倍。
Spring提供了很多有用的切入点基础类来帮助你实现你自己的切入点。
因为静态切入点是最常用的,你可能会像下面那样继承StaticMethodMatcherPointcut。这只需要实现一个抽象方法 (虽然也可以覆盖其它方法来定制行为):
class TestStaticPointcut extends StaticMethodMatcherPointcut { public boolean matches(Method m, Class targetClass) { // return true if custom criteria match } }
动态切入点也有很多基类。
你可以用Spring 1.0 RC2和更高版本里的自定义切入点及通知类型。