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,一经查实,立即删除!

相关文章

安全运维 -- splunk 集群配置归档

0x00 背景 splunk 集群索引服务器容量满了以后&#xff0c;为了防止数据丢失&#xff0c;需要对旧数据进行归档保存。 0x01 原理 指定一台大容量服务器&#xff0c;创建共享文件夹&#xff0c;并将集群里的所有indexer指向这个归档共享目录。 0x02 实施 集群的每个indexer都…

Python音频和视频格式转换

1.音频转换 使用Python中的一些库来进行音频格式转换。其中一个常用的库是pydub。首先&#xff0c;你需要安装pydub库。你可以使用以下命令来安装它&#xff1a; pip install pydub安装完成后&#xff0c;你可以使用以下代码来进行音频格式转换&#xff1a; from pydub impo…

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 理…

Vue 3:玩一下web前端技术(二)

前言 本章内容为VUE目录结构解析与相关工程技术讨论。 下一篇文章地址&#xff1a; Vue 3&#xff1a;玩一下web前端技术&#xff08;一&#xff09;_Lion King的博客-CSDN博客 下一篇文章地址&#xff1a; &#xff08;暂无&#xff09; 一、目录结构 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被广泛应用于…

Linux Tips

1 查找某字段在哪个文件出现过 发现某个网址或者字段有问题&#xff0c;但是down的项目找不到在哪&#xff0c;可以&#xff1a; grep -r "https://xxxxx.com"# 递归搜索当前文件夹及其子文件夹下的所有 CMakeLists.txt 文件 find . -name "CMakeLists.txt&qu…

什么是 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;指的是图片中存在的不必要或者多余的干扰数…