Spring AOP 中,切点有多少种定义方式?

在 Spring AOP 中,我们最常用的切点定义方式主要是两种:

  1. 使用 execution 进行无侵入拦截。
  2. 使用注解进行拦截。

这应该是是小伙伴们日常工作中使用最多的两种切点定义方式了。但是除了这两种还有没有其他的呢?今天松哥就来和大家聊一聊这个话题。

1. Pointcut 分类

来看下 Pointcut 的定义:

public interface Pointcut {ClassFilter getClassFilter();MethodMatcher getMethodMatcher();Pointcut TRUE = TruePointcut.INSTANCE;
}

从方法名上就能看出来,getClassFilter 进行类的过滤,getMethodMatcher 进行方法过滤。通过过滤 Class 和过滤 Method,我们就能够锁定一个拦截对象了。

再来看下 Pointcut 类的继承关系图:

可以看到,其实实现类不算多,大部分看名字其实都能猜出来是干嘛的,这些实现类我们大致上又可以分为六大类:

  1. 静态方法切点:StaticMethodMatcherPointcut 表示静态方法切点的抽象基类,默认情况下匹配所有的类,然后通过不同的规则去匹配不同的方法。
  2. 动态方法切点:DynamicMethodMatcherPointcut 表示动态方法切点的抽象基类,默认情况下它匹配所有的类,然后通过不同的规则匹配不同的方法,这个有点类似于 StaticMethodMatcherPointcut,不同的是,StaticMethodMatcherPointcut 仅对方法签名进行匹配并且仅匹配一次,而 DynamicMethodMatcherPointcut 会在运行期间检查方法入参的值,由于每次传入的参数可能都不一样,所以没调用都要去判断,因此就导致 DynamicMethodMatcherPointcut 的性能差一些。
  3. 注解切点:AnnotationMatchingPointcut。
  4. 表达式切点:ExpressionPointcut。
  5. 流程切点:ControlFlowPointcut。
  6. 复合切点:ComposablePointcut。

除了上面这六个之外,另外还有一个落单的 TruePointcut,这个从名字上就能看出来是拦截一切。

所以满打满算,有七种类型的切点,接下来我们就来逐个分析一下。

2. TruePointcut

这个实现类从名字上看就是拦截所有,拦截一切,我们来看下这个类怎么做的:

final class TruePointcut implements Pointcut, Serializable {//...@Overridepublic ClassFilter getClassFilter() {return ClassFilter.TRUE;}@Overridepublic MethodMatcher getMethodMatcher() {return MethodMatcher.TRUE;}//...
}

首先小伙伴们注意,这个类不是 public 的,所以意味着我们自己开发中没法直接使用这个切点。然后大家看到,在 getClassFilter 和 getMethodMatcher 方法中,这里都返回了对应的 TRUE,而这两个 TRUE 实现非常简单,就是在需要做比对的地方,不做任何比对,直接返回 true 即可,这就导致了最终将所有东西都拦截下来。

3. StaticMethodMatcherPointcut

StaticMethodMatcherPointcut 仅对方法名签名(包括方法名和入参类型及顺序)进行匹配,静态匹配仅判断一次。

public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut {private ClassFilter classFilter = ClassFilter.TRUE;/*** Set the {@link ClassFilter} to use for this pointcut.* Default is {@link ClassFilter#TRUE}.*/public void setClassFilter(ClassFilter classFilter) {this.classFilter = classFilter;}@Overridepublic ClassFilter getClassFilter() {return this.classFilter;}@Overridepublic final MethodMatcher getMethodMatcher() {return this;}}

可以看到,这里类的匹配默认就是返回 true,方法的匹配则返回当前对象,也就是看具体的实现。

StaticMethodMatcherPointcut 有几个写好的实现类,我们来看下。

3.1 SetterPointcut

看名字就知道,这个可以用来拦截所有的 set 方法:

private static class SetterPointcut extends StaticMethodMatcherPointcut implements Serializable {public static final SetterPointcut INSTANCE = new SetterPointcut();@Overridepublic boolean matches(Method method, Class<?> targetClass) {return (method.getName().startsWith("set") &&method.getParameterCount() == 1 &&method.getReturnType() == Void.TYPE);}private Object readResolve() {return INSTANCE;}@Overridepublic String toString() {return "Pointcuts.SETTERS";}
}

可以看到,方法的匹配就是断定当前方法是否为 set 方法,要求方法名以 set 开始,方法只有一个参数并且方法返回值为 null,精准定位到一个 set 方法。

举一个使用的例子,如下:

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new CalculatorImpl());
proxyFactory.addInterface(ICalculator.class);
proxyFactory.addAdvisor(new PointcutAdvisor() {@Overridepublic Pointcut getPointcut() {return Pointcuts.SETTERS;}@Overridepublic Advice getAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();String name = method.getName();System.out.println(name + " 方法开始执行了。。。");Object proceed = invocation.proceed();System.out.println(name + " 方法执行结束了。。。");return proceed;}};}@Overridepublic boolean isPerInstance() {return true;}
});
ICalculator calculator = (ICalculator) proxyFactory.getProxy();
calculator.setA(5);

由于 SetterPointcut 是私有的,无法直接 new,可以通过工具类 Pointcuts 获取到实例。

3.2 GetterPointcut

GetterPointcut 和 SetterPointcut 类似,如下:

private static class GetterPointcut extends StaticMethodMatcherPointcut implements Serializable {public static final GetterPointcut INSTANCE = new GetterPointcut();@Overridepublic boolean matches(Method method, Class<?> targetClass) {return (method.getName().startsWith("get") &&method.getParameterCount() == 0);}private Object readResolve() {return INSTANCE;}@Overridepublic String toString() {return "Pointcuts.GETTERS";}
}

我觉得这个应该就不用过多解释了吧,跟前面的 SetterPointcut 类似,对照理解就行了。

3.3 NameMatchMethodPointcut

这个是根据方法名来做匹配。

public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut implements Serializable {private List<String> mappedNames = new ArrayList<>();public void setMappedName(String mappedName) {setMappedNames(mappedName);}public void setMappedNames(String... mappedNames) {this.mappedNames = new ArrayList<>(Arrays.asList(mappedNames));}public NameMatchMethodPointcut addMethodName(String name) {this.mappedNames.add(name);return this;}@Overridepublic boolean matches(Method method, Class<?> targetClass) {for (String mappedName : this.mappedNames) {if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) {return true;}}return false;}protected boolean isMatch(String methodName, String mappedName) {return PatternMatchUtils.simpleMatch(mappedName, methodName);}
}

可以看到,这个就是从外部传一个方法名称列表进来,然后在 matches 方法中进行匹配即可,匹配的时候直接调用 equals 方法进行匹配,如果 equals 方法没有匹配上,则调用 isMatch 方法进行匹配,这个最终调用到 PatternMatchUtils.simpleMatch 方法,这是 Spring 中提供的一个工具类,支持通配符的匹配。

举个简单例子:

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new CalculatorImpl());
proxyFactory.addInterface(ICalculator.class);
proxyFactory.addAdvisor(new PointcutAdvisor() {@Overridepublic Pointcut getPointcut() {NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();pointcut.setMappedNames("add","set*");return pointcut;}@Overridepublic Advice getAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();String name = method.getName();System.out.println(name + " 方法开始执行了。。。");Object proceed = invocation.proceed();System.out.println(name + " 方法执行结束了。。。");return proceed;}};}@Overridepublic boolean isPerInstance() {return true;}
});
ICalculator calculator = (ICalculator) proxyFactory.getProxy();
calculator.add(3,4);
calculator.minus(3, 4);
calculator.setA(5);

这里我设置的是拦截方法名为 add 或者方法名以 set 开头的方法。

3.4 JdkRegexpMethodPointcut

这个是支持通过正则去匹配方法名,匹配上的方法就会被拦截下来。

public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut {private Pattern[] compiledPatterns = new Pattern[0];private Pattern[] compiledExclusionPatterns = new Pattern[0];@Overrideprotected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException {this.compiledPatterns = compilePatterns(patterns);}@Overrideprotected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException {this.compiledExclusionPatterns = compilePatterns(excludedPatterns);}@Overrideprotected boolean matches(String pattern, int patternIndex) {Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);return matcher.matches();}@Overrideprotected boolean matchesExclusion(String candidate, int patternIndex) {Matcher matcher = this.compiledExclusionPatterns[patternIndex].matcher(candidate);return matcher.matches();}private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException {Pattern[] destination = new Pattern[source.length];for (int i = 0; i < source.length; i++) {destination[i] = Pattern.compile(source[i]);}return destination;}
}

可以看到,这里实际上就是传入正则表达式,然后通过正则表达式去匹配方法名是否满足条件。正则表达式可以传入多个,系统会在 JdkRegexpMethodPointcut 的父类中进行遍历逐个进行匹配,我举一个例子:

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new CalculatorImpl());
proxyFactory.addInterface(ICalculator.class);
proxyFactory.addAdvisor(new PointcutAdvisor() {@Overridepublic Pointcut getPointcut() {JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut();pc.setPatterns("org.javaboy.bean.aop3.ICalculator.set.*");pc.setExcludedPattern("org.javaboy.bean.aop3.ICalculator.setA");return pc;}@Overridepublic Advice getAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();String name = method.getName();System.out.println(name + " 方法开始执行了。。。");Object proceed = invocation.proceed();System.out.println(name + " 方法执行结束了。。。");return proceed;}};}@Overridepublic boolean isPerInstance() {return true;}
});
ICalculator calculator = (ICalculator) proxyFactory.getProxy();
calculator.add(3,4);
calculator.minus(3, 4);
calculator.setA(5);

上面这个例子也是拦截 setXXX 方法,不过需要注意的是,方法名匹配的时候使用的是方法的全路径。

另外还需要注意,在配置匹配规则的时候,还可以设置 ExcludedPattern,实际上在匹配的时候,首先进行正向匹配,就是先看下方法名是否满足规则,如果满足,则方法名再和 ExcludedPattern 进行比对,如果不满足,则这个方法才会被确定下来要拦截。

StaticMethodMatcherPointcut 中主要就给我们提供了这些规则。

4. DynamicMethodMatcherPointcut

这个是动态方法匹配的切点,默认也是匹配所有类,但是在方法匹配这个问题,每次都会匹配,我们来看下:

public abstract class DynamicMethodMatcherPointcut extends DynamicMethodMatcher implements Pointcut {@Overridepublic ClassFilter getClassFilter() {return ClassFilter.TRUE;}@Overridepublic final MethodMatcher getMethodMatcher() {return this;}}

可以看到,getClassFilter 直接返回 TRUE,也就是类就直接匹配了,getMethodMatcher 返回的则是当前对象,那是因为当前类实现了 DynamicMethodMatcher 接口,这就是一个方法匹配器:

public abstract class DynamicMethodMatcher implements MethodMatcher {@Overridepublic final boolean isRuntime() {return true;}@Overridepublic boolean matches(Method method, Class<?> targetClass) {return true;}}

小伙伴们看到,这里 isRuntime 方法返回 true,该方法为 true,意味着三个参数的 matches 方法将会被调用,所以这里两个参数的 matches 方法可以直接返回 true,不做任何控制。

当然,也可以在两个参数的 matches 方法中做一些前置的判断。

我们来看一个简单例子:

public class MyDynamicMethodMatcherPointcut extends DynamicMethodMatcherPointcut {@Overridepublic boolean matches(Method method, Class<?> targetClass) {return method.getName().startsWith("set");}@Overridepublic boolean matches(Method method, Class<?> targetClass, Object... args) {return method.getName().startsWith("set") && args.length == 1 && Integer.class.isAssignableFrom(args[0].getClass());}
}

在实际执行过程中,两个参数的 matches 方法返回 true,三个参数的 matches 方法才会执行,如果两个参数的 matches 方法返回 false,则三个参数的 matches 就不会执行了。所以也可以两个参数的 matches 方法直接固定返回 true,只在三个参数的 matches 方法中做匹配操作即可。

然后使用这个切点:

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new CalculatorImpl());
proxyFactory.addInterface(ICalculator.class);
proxyFactory.addAdvisor(new PointcutAdvisor() {@Overridepublic Pointcut getPointcut() {return new MyDynamicMethodMatcherPointcut();}@Overridepublic Advice getAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();String name = method.getName();System.out.println(name + " 方法开始执行了。。。");Object proceed = invocation.proceed();System.out.println(name + " 方法执行结束了。。。");return proceed;}};}@Overridepublic boolean isPerInstance() {return true;}
});
ICalculator calculator = (ICalculator) proxyFactory.getProxy();
calculator.add(3,4);
calculator.minus(3, 4);
calculator.setA(5);

5. AnnotationMatchingPointcut

这个就是判断类或者方法上是否存在某个注解,如果存在,则将之拦截下来,否则不拦截。

先来看下这个类的定义:

public class AnnotationMatchingPointcut implements Pointcut {private final ClassFilter classFilter;private final MethodMatcher methodMatcher;public AnnotationMatchingPointcut(Class<? extends Annotation> classAnnotationType) {this(classAnnotationType, false);}public AnnotationMatchingPointcut(Class<? extends Annotation> classAnnotationType, boolean checkInherited) {this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited);this.methodMatcher = MethodMatcher.TRUE;}public AnnotationMatchingPointcut(@Nullable Class<? extends Annotation> classAnnotationType,@Nullable Class<? extends Annotation> methodAnnotationType) {this(classAnnotationType, methodAnnotationType, false);}public AnnotationMatchingPointcut(@Nullable Class<? extends Annotation> classAnnotationType,@Nullable Class<? extends Annotation> methodAnnotationType, boolean checkInherited) {if (classAnnotationType != null) {this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited);}else {this.classFilter = new AnnotationCandidateClassFilter(methodAnnotationType);}if (methodAnnotationType != null) {this.methodMatcher = new AnnotationMethodMatcher(methodAnnotationType, checkInherited);}else {this.methodMatcher = MethodMatcher.TRUE;}}@Overridepublic ClassFilter getClassFilter() {return this.classFilter;}@Overridepublic MethodMatcher getMethodMatcher() {return this.methodMatcher;}public static AnnotationMatchingPointcut forClassAnnotation(Class<? extends Annotation> annotationType) {Assert.notNull(annotationType, "Annotation type must not be null");return new AnnotationMatchingPointcut(annotationType);}public static AnnotationMatchingPointcut forMethodAnnotation(Class<? extends Annotation> annotationType) {Assert.notNull(annotationType, "Annotation type must not be null");return new AnnotationMatchingPointcut(null, annotationType);}}

首先小伙伴们注意到,这个类一共有四个构造方法,从上往下分别是:

  1. 传入类上的注解名称,根据类上的注解去判断是否需要拦截。
  2. 在 1 的基础之上,再增加一个 checkInherited,这个表示是否需要检查父类上是否存在相关的注解。
  3. 传入类上和方法上的注解类型,根据这个注解类型去判断是否需要拦截。
  4. 在 3 的基础之上,再增加一个 checkInherited,这个表示是否需要检查父类上或者方法上是否存在相关的注解。

其中,第四个构造方法中处理的情况类型比较多,如果用户传入了 classAnnotationType,则构建 AnnotationClassFilter 类型的 ClassFilter,否则构建 AnnotationCandidateClassFilter 类型的 ClassFilter;如果用户传入了 methodAnnotationType,则构建 AnnotationMethodMatcher 类型的 MethodMatcher,否则方法匹配器就直接返回匹配所有方法。

那么接下来我们就来看下这几种不同的匹配器。

5.1 AnnotationClassFilter

public class AnnotationClassFilter implements ClassFilter {//...@Overridepublic boolean matches(Class<?> clazz) {return (this.checkInherited ? AnnotatedElementUtils.hasAnnotation(clazz, this.annotationType) :clazz.isAnnotationPresent(this.annotationType));}//...
}

这里省略了一些代码,关键地方就是这个匹配方法了,如果需要检查父类是否包含该注解,则调用 AnnotatedElementUtils.hasAnnotation 方法进行查找,否则直接调用 clazz.isAnnotationPresent 方法判断当前类上是否包含指定注解即可。

5.2 AnnotationCandidateClassFilter

private static class AnnotationCandidateClassFilter implements ClassFilter {private final Class<? extends Annotation> annotationType;AnnotationCandidateClassFilter(Class<? extends Annotation> annotationType) {this.annotationType = annotationType;}@Overridepublic boolean matches(Class<?> clazz) {return AnnotationUtils.isCandidateClass(clazz, this.annotationType);}
}

这里就是调用了 AnnotationUtils.isCandidateClass 方法进行判断,这个方法用来判断指定类是不是可以承载指定注解的候选类,返回 true 的规则是:

  1. java. 开头的注解,所有的类都能承载,这种情况会返回 true。
  2. 目标类不能以 java. 开头,也就是说 JDK 中的类都不行,不是以 java. 开头的类就可以返回 true。
  3. 给定类也不能是 Ordered 类。

满足如上条件,这个类就是符合规定的。

AnnotationCandidateClassFilter 主要是针对用户没有传入类上注解的情况,这种情况一般都是根据方法上的注解进行匹配的,所以这里主要是排除一些系统类。

5.3 AnnotationMethodMatcher

public class AnnotationMethodMatcher extends StaticMethodMatcher {@Overridepublic boolean matches(Method method, Class<?> targetClass) {if (matchesMethod(method)) {return true;}// Proxy classes never have annotations on their redeclared methods.if (Proxy.isProxyClass(targetClass)) {return false;}// The method may be on an interface, so let's check on the target class as well.Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);return (specificMethod != method && matchesMethod(specificMethod));}private boolean matchesMethod(Method method) {return (this.checkInherited ? AnnotatedElementUtils.hasAnnotation(method, this.annotationType) :method.isAnnotationPresent(this.annotationType));}
}

方法匹配就是首先检查方法上是否有注解,如果开启了 checkInherited,则去检查一下父类对应的方法上是否有相关的注解,如果有,则表示方法匹配上了,返回 true。

否则先去检查一下当前类是否是一个代理对象,代理对象中对应的方法肯定是没有注解的,直接返回 false。

如果前面两步还没返回,最后考虑到目前这个方法可能是在一个接口上,要检查一下它的实现类是否包含该注解。

这就是 AnnotationMatchingPointcut。松哥也举一个简单例子吧。

5.4 实践

首先我自定义一个注解,如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAction {
}

然后将之添加到某一个方法上:

public class CalculatorImpl implements ICalculator {@Overridepublic void add(int a, int b) {System.out.println(a + "+" + b + "=" + (a + b));}@MyAction@Overridepublic int minus(int a, int b) {return a - b;}@Overridepublic void setA(int a) {System.out.println("a = " + a);}
}

最后来实践一下:

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new CalculatorImpl());
proxyFactory.addInterface(ICalculator.class);
proxyFactory.addAdvisor(new PointcutAdvisor() {@Overridepublic Pointcut getPointcut() {return new AnnotationMatchingPointcut(null, MyAction.class);}@Overridepublic Advice getAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();String name = method.getName();System.out.println(name + " 方法开始执行了。。。");Object proceed = invocation.proceed();System.out.println(name + " 方法执行结束了。。。");return proceed;}};}@Overridepublic boolean isPerInstance() {return true;}
});
ICalculator calculator = (ICalculator) proxyFactory.getProxy();
calculator.add(3,4);
calculator.minus(3, 4);
calculator.setA(5);

只有 minus 方法被拦截下来了。

6. ExpressionPointcut

这个其实就是我们日常开发中使用最多的一种切点定义方式,可能项目中 99% 的切点定义都是使用的 ExpressionPointcut。这个具体用法我这里就不说了,因为比较丰富,都能单独整一篇文章了,如果小伙伴对 ExpressionPointcut 的基础用法还不熟悉的话,可以在公众号【江南一点雨】后台回复 ssm,有松哥之前录制的入门视频教程可以参考。

我这里就简单把它的实现思路来和小伙伴们梳理一下,ExpressionPointcut 的实现都在 AspectJExpressionPointcut 类中,该类支持使用切点语言来对要拦截的方法做一个非常精确的描述,精确到要拦截方法的返回值,AspectJExpressionPointcut 类的实现比较长也比较复杂,我这里贴其中一些关键的代码来看下:

public class AspectJExpressionPointcut extends AbstractExpressionPointcutimplements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = Set.of(PointcutPrimitive.EXECUTION,PointcutPrimitive.ARGS,PointcutPrimitive.REFERENCE,PointcutPrimitive.THIS,PointcutPrimitive.TARGET,PointcutPrimitive.WITHIN,PointcutPrimitive.AT_ANNOTATION,PointcutPrimitive.AT_WITHIN,PointcutPrimitive.AT_ARGS,PointcutPrimitive.AT_TARGET);@Overridepublic ClassFilter getClassFilter() {obtainPointcutExpression();return this;}@Overridepublic MethodMatcher getMethodMatcher() {obtainPointcutExpression();return this;}/*** Check whether this pointcut is ready to match,* lazily building the underlying AspectJ pointcut expression.*/private PointcutExpression obtainPointcutExpression() {if (getExpression() == null) {throw new IllegalStateException("Must set property 'expression' before attempting to match");}if (this.pointcutExpression == null) {this.pointcutClassLoader = determinePointcutClassLoader();this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);}return this.pointcutExpression;}
}

其实关键还是要获取到 ClassFilter 和 MethodMatcher,然后调用其 matches 方法,当前类刚好就是实现了这两个,所以直接返回 this 就可以了。在 getClassFilter 或者 getMethodMatcher 方法执行之前,都会先调用 obtainPointcutExpression 方法,去解析我们传入的 expression 字符串,将之解析为一个 PointcutExpression 对象,接下来的 matches 方法就可以此为依据,进行匹配了。

7. ControlFlowPointcut

ControlFlowPointcut 主要是指目标方法从某一个指定类的指定方法中执行,切点才生效,否则不生效。

举个简单例子,如下:

public class AopDemo04 {public static void main(String[] args) {ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setTarget(new CalculatorImpl());proxyFactory.addInterface(ICalculator.class);proxyFactory.addAdvisor(new PointcutAdvisor() {@Overridepublic Pointcut getPointcut() {return new ControlFlowPointcut(AopDemo04.class, "evl");}@Overridepublic Advice getAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();String name = method.getName();System.out.println(name + " 方法开始执行了。。。");Object proceed = invocation.proceed();System.out.println(name + " 方法执行结束了。。。");return proceed;}};}@Overridepublic boolean isPerInstance() {return true;}});ICalculator calculator = (ICalculator) proxyFactory.getProxy();calculator.add(3,4);System.out.println("/");evl(calculator);}public static void evl(ICalculator iCalculator) {iCalculator.add(3, 4);}
}

这里切点的意思就是说,必须要从 AopDemo04 这个类的 evl 方法中调用 add 方法,这个切点才会生效,如果是拿到了 ICalculator 对象后直接调用 add 方法,那么切点是不会生效的。

ControlFlowPointcut 的原理也很简单,就是比较一下类名和方法名,如下:

public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable {@Overridepublic boolean matches(Class<?> clazz) {return true;}@Overridepublic boolean matches(Method method, Class<?> targetClass) {return true;}@Overridepublic boolean isRuntime() {return true;}@Overridepublic boolean matches(Method method, Class<?> targetClass, Object... args) {this.evaluations.incrementAndGet();for (StackTraceElement element : new Throwable().getStackTrace()) {if (element.getClassName().equals(this.clazz.getName()) &&(this.methodName == null || element.getMethodName().equals(this.methodName))) {return true;}}return false;}@Overridepublic ClassFilter getClassFilter() {return this;}@Overridepublic MethodMatcher getMethodMatcher() {return this;}
}

大家可以看到,isRuntime 方法返回 true,表示这是一个动态的方法匹配器。关键的 matches 方法,就是根据调用栈中的信息,去比较给定的类名和方法名是否满足。

8. ComposablePointcut

看名字就知道,这个可以将多个切点组合到一起,组合关系有求交集和求并集两种,分别对应 ComposablePointcut 中的 intersection 方法和 union 方法。

如下案例:

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new CalculatorImpl());
proxyFactory.addInterface(ICalculator.class);
proxyFactory.addAdvisor(new PointcutAdvisor() {@Overridepublic Pointcut getPointcut() {NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();nameMatchMethodPointcut.setMappedNames("add");ComposablePointcut pc = new ComposablePointcut((Pointcut) nameMatchMethodPointcut);pc.union(new AnnotationMatchingPointcut(null, MyAction.class));return pc;}@Overridepublic Advice getAdvice() {return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();String name = method.getName();System.out.println(name + " 方法开始执行了。。。");Object proceed = invocation.proceed();System.out.println(name + " 方法执行结束了。。。");return proceed;}};}@Overridepublic boolean isPerInstance() {return true;}
});
ICalculator calculator = (ICalculator) proxyFactory.getProxy();
calculator.add(3,4);
calculator.minus(3, 4);
calculator.setA(5);

在上面的案例中,就是把 NameMatchMethodPointcut 和 AnnotationMatchingPointcut 两个切点联合起来,既拦截方法名为 add 的方法,也拦截含有 @MyAction 注解的方法。

如果将 union 方法改为 intersection,就表示拦截方法名为 add 且被 @MyAction 注解标记的方法。如下:

@Override
public Pointcut getPointcut() {NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();nameMatchMethodPointcut.setMappedNames("add");ComposablePointcut pc = new ComposablePointcut((Pointcut) nameMatchMethodPointcut);pc.intersection(new AnnotationMatchingPointcut(null, MyAction.class));return pc;
}

其实这种组合切点的原理很简单,先把我们提供的 ClassFilter 和 MethodMatcher 收集到一个集合中,如果是 union,就直接遍历集合,只要有一个满足,就返回 true;如果是 intersection,也是直接遍历,如果有一个返回 false 就直接返回 false 即可。

以 ClassFilter 为例,我们来简单看下源码:

public ComposablePointcut union(ClassFilter other) {this.classFilter = ClassFilters.union(this.classFilter, other);return this;
}
public abstract class ClassFilters {public static ClassFilter union(ClassFilter cf1, ClassFilter cf2) {return new UnionClassFilter(new ClassFilter[] {cf1, cf2});}private static class UnionClassFilter implements ClassFilter, Serializable {private final ClassFilter[] filters;UnionClassFilter(ClassFilter[] filters) {this.filters = filters;}@Overridepublic boolean matches(Class<?> clazz) {for (ClassFilter filter : this.filters) {if (filter.matches(clazz)) {return true;}}return false;}}
}

可以看到,传入的多个 ClassFilter 被组装到一起,在 matches 方法中逐个遍历,只要其中一个返回 true,就是 true。

9. 小结

好啦,这就是松哥今天和小伙伴们介绍的 7 中 Pointcut 了,希望借此小伙伴们对 Spring AOP 中切点的类型有一个完整的了解。再来回顾一下这其中切点:

  1. 静态方法切点:StaticMethodMatcherPointcut 表示静态方法切点的抽象基类,默认情况下匹配所有的类,然后通过不同的规则去匹配不同的方法。
  2. 动态方法切点:DynamicMethodMatcherPointcut 表示动态方法切点的抽象基类,默认情况下它匹配所有的类,然后通过不同的规则匹配不同的方法,这个有点类似于 StaticMethodMatcherPointcut,不同的是,StaticMethodMatcherPointcut 仅对方法签名进行匹配并且仅匹配一次,而 DynamicMethodMatcherPointcut 会在运行期间检查方法入参的值,由于每次传入的参数可能都不一样,所以没调用都要去判断,因此就导致 DynamicMethodMatcherPointcut 的性能差一些。
  3. 注解切点:AnnotationMatchingPointcut 根据制定注解拦截目标方法或者类。
  4. 表达式切点:ExpressionPointcut 这个是我们日常开发中使用最多的一种切点定义方式。
  5. 流程切点:ControlFlowPointcut 这个是要求必须从某一个位置调用目标方法,切点才会生效。
  6. 复合切点:ComposablePointcut 这个是把多个拦截器组装在一起使用,有交集和并集两种组装方式。
  7. TruePointcut 这是框架内部使用的一个切点,表示拦截一切。

哦了~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/11517.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux-Shell

1.什么是Bash shell(壳) Bash Shell是一个命令解释器&#xff0c;它在操作系统的最外层&#xff0c;负责用户程序与内核进行交互操作的一种接口&#xff0c;将用户输入的命令翻译给操作系统&#xff0c;并将处理后的结果输出至屏幕。 通过xshell连接&#xff0c;就是打开了一…

uniAPP 视频图片预览组件

效果图 思路&#xff1a;处理文件列表&#xff0c;根据文件类型归类 已兼容 H5 ios 设备&#xff0c;测试已通过 浙政钉&#xff0c;微信小程序 视频资源因为&#xff0c;没有预览图&#xff0c;用灰色图层加播放按钮代替 <template><!--视频图片预览组件 -->&l…

《面试1v1》Kafka与传统消息系统区别

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

React AntDesign写一个导出数据的提示语 上面有跳转的路径,或者点击知道了,关闭该弹层

效果如下&#xff1a; 代码如下&#xff1a; ForwardDataCenterModal(_blank);export const ForwardDataCenterModal (target?: string) > {let contentBefore React.createElement(span, null, 数据正在处理中&#xff0c;请稍后前往);let contentAfter React.creat…

lightGBM实例——特征筛选和评分卡模型构建

数据还是采用这个例子里的数据&#xff0c;具体背景也同上。 添模型构建——使用逻辑回归构建模型&#xff0c;lightGBM进行特征筛选 lightGBM模型介绍请看这个链接&#xff1a;集成学习——Boosting算法&#xff1a;Adaboost、GBDT、XGBOOST和lightGBM的简要原理和区别 具体代…

Docker复习

目录 1. Docker的理解1.1 Docker三要素 2 安装Docker2.1 安装命令2.2 配置阿里云加速器 3 Docker命令3.1 启动类命令3.2 镜像类命令 4 实战4.1 启动容器&#xff0c;自动创建实例4.2 查看Docker内启动的容器4.3 退出容器4.4 其他4.5 导入导出文件4.6 commit 5 Dockerfile5.1 理…

uniapp:H5定位当前省市区街道信息

高德地图api&#xff0c;H5定位省市区街道信息。 由于uniapp的uni.getLocation在H5不能获取到省市区街道信息&#xff0c;所以这里使用高德的逆地理编码接口地址接口&#xff0c;通过传key和当前经纬度&#xff0c;获取到省市区街道数据。 这里需要注意的是&#xff1a;**高德…

0基础学习VR全景平台篇 第69篇:VR直播-如何设置广告

直播间可以插入轮播广告&#xff0c;并且支持外链跳转&#xff0c;能够有效地提升VR直播活动的转化率。 1、点击&#xff0c;添加广告 2、广告图展现形式分为两种&#xff1a;普通广告和全屏广告&#xff0c;普通广告在非全屏播放的直播间显示&#xff0c;全屏广告在全屏播放的…

Raki的读paper小记:LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS

Abstract&Introduction&Related Work 研究任务 对大模型进行部分微调 已有方法和相关工作 现有技术通常通过扩展模型深度引入推理延迟&#xff08;Houlsby 等人&#xff0c;2019&#xff1b;Rebuffi 等人&#xff0c;2017&#xff09;&#xff0c;或通过减少模型可用序…

redis的四种模式优缺点

redis简介 Redis是一个完全开源的内存数据结构存储工具&#xff0c;它支持多种数据结构&#xff0c;以及多种功能。Redis还提供了持久化功能&#xff0c;可以将数据存储到磁盘上&#xff0c;以便在重启后恢复数据。由于其高性能、可靠性和灵活性&#xff0c;Redis被广泛应用于…

什么是 MyBatis?

经过前几篇博客的学习 Spring 系列的基本操作已经实现的差不多了&#xff0c;接下来&#xff0c;我们来学习更重要的知识&#xff0c;将前端传递的数据存储起来&#xff0c;或者查询数据库里面的数据。 一、MyBatis 是什么&#xff1f; MyBatis 是一款优秀的持久层框架&…

东南大学轴承故障诊断(Python代码,CNN模型,适合复合故障诊断研究)

运行代码要求&#xff1a; 代码运行环境要求&#xff1a;Keras版本>2.4.0&#xff0c;python版本>3.6.0 本次实验主要是在两种不同工况数据下&#xff0c;进行带有复合故障的诊断实验&#xff0c;没有复合故障的诊断实验。 实验结果证明&#xff0c;针对具有复合故障的…

面试—Redis相关

文章目录 一、概述二、缓存1、缓存穿透2、缓存击穿3、缓存雪崩4、双写一致性5、持久化6、数据过期策略7、数据淘汰策略 三、分布式锁四、其它面试题1、主从复制2、哨兵3、分片集群结构4、I/O多路复用 一、概述 使用场景&#xff1a; Redis的数据持久化策略有哪些什么是缓存穿透…

智能安全配电装置应用场景有哪些?

安科瑞 华楠 一、应用背景 电力作为一种清洁能源&#xff0c;给人们带来了舒适、便捷的电气化生活。与此同时&#xff0c;由于使用不当&#xff0c;维护不及时等原因引发的漏电触电和电气火灾事故&#xff0c;也给人们的生命和财产带来了巨大的威胁和损失。 为了防止低压配电…

SkyEye与Jenkins的DevOps持续集成解决方案

在技术飞速发展的当下&#xff0c;随着各行各业的软件逻辑复杂程度提升带来的需求变更&#xff0c;传统测试已无法满足与之相对应的一系列测试任务&#xff0c;有必要引入一个自动化、可持续集成构建的DevOps平台来解决此类问题。本文将主要介绍SkyEye与Jenkins的持续集成解决方…

C++OpenCV(5):图像模糊操作(四种滤波方法)

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 &#x1f506; OpenCV项目地址及源代码&#xff1a;点击这里 文章目录 图像模糊操作均值滤波高斯滤波中值滤波双边滤波 图像模糊操作 关于图片的噪声&#xff1a;指的是图片中存在的不必要或者多余的干扰数…

windows下搭建php开发环境

http://wed.xjx100.cn/news/139397.html?actiononClick https://www.bilibili.com/read/cv23429835/ https://www.php.cn/faq/498307.html 安装iis 选择卸载程序 安装php 官网https://www.php.net/下载 选择线程安全 国内地址 下载完成后解压放到想存放的路径 添加p…

数据可视化 - 动态柱状图

基础柱状图 通过Bar构建基础柱状图 from pyecharts.charts import Bar from pyecharts.options import LabelOpts # 使用Bar构建基础柱状图 bar Bar() # 添加X轴 bar.add_xaxis(["中国", "美国", "英国"]) # 添加Y轴 # 设置数值标签在右侧 b…

深入浅出之Docker Compose详解

目录 1.Docker Compose概述 1.1 Docker Compose 定义 1.2 Docker Compose产生背景 1.3 Docker Compose 核心概念 1.4 Docker Compose 使用步骤 1.5 Docker Compose 常用命令 2. Docker Compose 实战 2.1 Docker Compose下载和卸载 2.2 Docker Compose 项目概述 2.3 Do…

北航投资已投企业四象科技成功发射三颗卫星

1箭4星&#xff01;2023年7月23日10时50分&#xff0c;我国在太原卫星发射中心使用长征二号丁运载火箭&#xff0c;成功将四象科技“矿大南湖号”SAR遥感卫星、“虹口复兴号”光学遥感卫星、“中电农创号”热红外遥感卫星以及银河航天灵犀03星共4颗卫星发射升空&#xff0c;卫星…