前言
首先需要说明一点:Pointcut接口有两个。
一个是:org.aspectj.lang.reflect.Pointcut,它是aspectj内部使用的。它只有一个实现类PointcutImpl。是它内部的抽象
另一个是:org.springframework.aop.Pointcut,这是Spring AOP体系中对切点的顶层抽象,贯穿整个AOP过程,非常重要。因此本文主要基于它,介绍一些原理以及它常用子类的一些使用。
备注:关于AspectJ和Spring AOP区别和联系,建议参阅:
【小家Spring】Spring AOP的多种使用方式以及神一样的AspectJ-AOP使用介绍
Pointcut家族
它是Spring AOP对切点的一个顶层首相,非常的重要。
首先得看看这个顶级接口抽象的图谱:
这里面有一个非常重要得子接口:ExpressionPointcut,它是用于解析String类型的切点表达式的接口(这也是我们使用得最最最多的)
Pointcut接口分析
**主要负责对系统的相应的Joinpoint进行捕捉,对系统中所有的对象进行Joinpoint所定义的规则进行匹配。**提供了一个TruePointcut实例,当Pointcut为TruePointcut类型时,则会忽略所有的匹配条件,永远返回true
显然可以看出,这个接口和ClassFilter和MethodMatcher有关系
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); /** * Canonical Pointcut instance that always matches. * 意思是:用于匹配上的一个实例(意思是永远返回true嘛) */ Pointcut TRUE = TruePointcut.INSTANCE; }
ClassFilter与MethodMatcher分别用于在不同的级别上限定Joinpoint的匹配范围,满足不同粒度的匹配
ClassFilter限定在类级别上,MethodMatcher限定在方法级别上
SpringAop主要支持在方法级别上的匹配,所以对类级别的匹配支持相对简单一些
ClassFilter
@FunctionalInterface public interface ClassFilter { // true表示能够匹配。那就会进行织入的操作 boolean matches(Class<?> clazz); // 常量 会匹配所有的类 TrueClassFilter不是public得class,所以只是Spring内部自己使用的 ClassFilter TRUE = TrueClassFilter.INSTANCE; }
Spring给他的实现类也比较多,如下:
RootClassFilter
public class RootClassFilter implements ClassFilter, Serializable { private Class<?> clazz; public RootClassFilter(Class<?> clazz) { this.clazz = clazz; } // 显然,传进来的candidate必须是clazz的子类才行 @Override public boolean matches(Class<?> candidate) { return clazz.isAssignableFrom(candidate); } }
AnnotationClassFilter
public class AnnotationClassFilter implements ClassFilter { ... public AnnotationClassFilter(Class<? extends Annotation> annotationType) { // 默认情况下checkInherited给的false:不去看它继承过来的注解 this(annotationType, false); } // checkInherited true:表示继承过来得注解也算 public AnnotationClassFilter(Class<? extends Annotation> annotationType, boolean checkInherited) { Assert.notNull(annotationType, "Annotation type must not be null"); this.annotationType = annotationType; this.checkInherited = checkInherited; } ... @Override public boolean matches(Class<?> clazz) { return (this.checkInherited ? // 继承的注解也会找出来 (AnnotationUtils.findAnnotation(clazz, this.annotationType) != null) : // 只会看自己本类的注解 clazz.isAnnotationPresent(this.annotationType)); } }
AspectJExpressionPointcut
它既是个Pointcut,它也是个ClassFilter
,下面会详细分析本类
MethodMatcher
public interface MethodMatcher { // 这个称为静态匹配:在匹配条件不是太严格时使用,可以满足大部分场景的使用 boolean matches(Method method, @Nullable Class<?> targetClass); // 这个称为动态匹配(运行时匹配): 它是严格的匹配。在运行时动态的对参数的类型进行匹配 boolean matches(Method method, @Nullable Class<?> targetClass, Object... args); //两个方法的分界线就是boolean isRuntime()方法,步骤如下 // 1、先调用静态匹配,若返回true。此时就会继续去检查isRuntime()的返回值 // 2、若isRuntime()还返回true,那就继续调用动态匹配 // (若静态匹配都匹配上,动态匹配那铁定更匹配不上得~~~~) // 是否需要执行动态匹配 boolean isRuntime(); MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; }
应用场景:比如需要统计用户登录次数时,那么登录传入的参数就是可以忽略的,则静态匹配就足够了
但是若要在登陆时对用户账号执行特殊的操作**(如赋予特殊的操作权限)**,就需要对参数进行一个类似于检验的操作,因此需要动态匹配
它有两个非常重要的抽象实现:StaticMethodMatcher和DynamicMethodMatcher
StaticMethodMatcher 静态匹配
public abstract class StaticMethodMatcher implements MethodMatcher { // 永远返回false表示只会去静态匹配 @Override public final boolean isRuntime() { return false; } // 三参数matches抛出异常,使其不被调用 @Override public final boolean matches(Method method, @Nullable Class<?> targetClass, Object... args) { // should never be invoked because isRuntime() returns false throw new UnsupportedOperationException("Illegal MethodMatcher usage"); } }
作用:它表示不会考虑具体 方法参数。因为不用每次都检查参数,那么对于同样的类型的方法匹配结果,就可以在框架内部缓存
以提高性能。比如常用的实现类:AnnotationMethodMatcher
DynamicMethodMatcher 动态匹配
public abstract class DynamicMethodMatcher implements MethodMatcher { // 永远返回true @Override public final boolean isRuntime() { return true; } // 永远返回true,去匹配动态匹配的方法即可 @Override public boolean matches(Method method, @Nullable Class<?> targetClass) { return true; } }
说明:因为每次都要对方法参数进行检查,无法对匹配结果进行缓存,所以,匹配效率相对 StatisMethodMatcher 来说要差,但匹配度更高。(实际使用得其实较少)
JdkRegexpMethodPointcut:基于正则的Pointcut
Spring官方为我们提供了一个基于正则表达式来匹配方法名的Pointcut,JdkRegexpMethodPointcut。
它提供了最重要的4个属性(patterns和excludedPatterns来自于父类AbstractRegexpMethodPointcut):
这里昂个属性来自于父类,相对来说就是比较简单的匹配signatureString(方法的全路径名称)
- String[] patterns:匹配的正则表达式。如find.*表示所有方法名以find开始的方法
- String[] excludedPatterns:排除的正则表达式们
下面两个是子类,也就是JdkRegexpMethodPointcut自己提供的属性
- Pattern[] compiledPatterns:相当于把正则字符串,Pattern.compile()成正则对象
- Pattern[] compiledExclusionPatterns:同上
都是数组,正则表达式都可以多个哟~~
需要注意的是,这两组含义相同,请不要同时跨组使用,没有意义,没必要深究。
这种切点表达式,在早期Spring中的使用较多,一般这么使用:
<!-- 自己书写的日志切面 --> <bean id="logBeforeAdvice" class="com.fsx.aop.LogBeforeAdvice" /> <!-- 使用JDK的正则切点~~~~~~ --> <bean id="regexPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="patterns"> <list> <value>find.*</value><!-- 拦截所有方法名以find开始的方法 --> </list> </property> </bean> <!-- 切面+切点 组合成一个增强器即可~~~~~~ --> <aop:config> <aop:advisor advice-ref="logBeforeAdvice" pointcut-ref="regexPointcut"/> </aop:config>
其实Spring为我们提供了一个简便的Advisor定义,可以方便的让我们同时指定一个JdkRegexpMethodPointcut和其需要对应的Advice,它就是RegexpMethodPointcutAdvisor,这样配置起来非常的方便
<bean id="logBeforeAdvice" class="com.fsx.aop.LogBeforeAdvice" /> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" ref="logBeforeAdvice"/> <property name="pattern" value="find.*"/> </bean>