public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); Pointcut TRUE = TruePointcut.INSTANCE; }从方法名上就能看出来,getClassFilter 进行类的过滤,getMethodMatcher 进行方法过滤。通过过滤 Class 和过滤 Method,我们就能够锁定一个拦截对象了。再来看下 Pointcut 类的继承关系图:
复合切点:ComposablePointcut。
final class TruePointcut implements Pointcut, Serializable { // 堆代码 duidaima.com @Override public ClassFilter getClassFilter() { return ClassFilter.TRUE; } @Override public MethodMatcher getMethodMatcher() { return MethodMatcher.TRUE; } //... }首先小伙伴们注意,这个类不是 public 的,所以意味着我们自己开发中没法直接使用这个切点。然后大家看到,在 getClassFilter 和 getMethodMatcher 方法中,这里都返回了对应的 TRUE,而这两个 TRUE 实现非常简单,就是在需要做比对的地方,不做任何比对,直接返回 true 即可,这就导致了最终将所有东西都拦截下来。
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; } @Override public ClassFilter getClassFilter() { return this.classFilter; } @Override public final MethodMatcher getMethodMatcher() { return this; } }可以看到,这里类的匹配默认就是返回 true,方法的匹配则返回当前对象,也就是看具体的实现。StaticMethodMatcherPointcut 有几个写好的实现类,我们来看下。
private static class SetterPointcut extends StaticMethodMatcherPointcut implements Serializable { public static final SetterPointcut INSTANCE = new SetterPointcut(); @Override public boolean matches(Method method, Class<?> targetClass) { return (method.getName().startsWith("set") && method.getParameterCount() == 1 && method.getReturnType() == Void.TYPE); } private Object readResolve() { return INSTANCE; } @Override public 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() { @Override public Pointcut getPointcut() { return Pointcuts.SETTERS; } @Override public Advice getAdvice() { return new MethodInterceptor() { @Override public 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; } }; } @Override public boolean isPerInstance() { return true; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.setA(5);由于 SetterPointcut 是私有的,无法直接 new,可以通过工具类 Pointcuts 获取到实例。
private static class GetterPointcut extends StaticMethodMatcherPointcut implements Serializable { public static final GetterPointcut INSTANCE = new GetterPointcut(); @Override public boolean matches(Method method, Class<?> targetClass) { return (method.getName().startsWith("get") && method.getParameterCount() == 0); } private Object readResolve() { return INSTANCE; } @Override public String toString() { return "Pointcuts.GETTERS"; } }我觉得这个应该就不用过多解释了吧,跟前面的 SetterPointcut 类似,对照理解就行了。
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; } @Override public 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() { @Override public Pointcut getPointcut() { NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut(); pointcut.setMappedNames("add","set*"); return pointcut; } @Override public Advice getAdvice() { return new MethodInterceptor() { @Override public 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; } }; } @Override public boolean isPerInstance() { return true; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add(3,4); calculator.minus(3, 4); calculator.setA(5);这里我设置的是拦截方法名为 add 或者方法名以 set 开头的方法。
public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut { private Pattern[] compiledPatterns = new Pattern[0]; private Pattern[] compiledExclusionPatterns = new Pattern[0]; @Override protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException { this.compiledPatterns = compilePatterns(patterns); } @Override protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException { this.compiledExclusionPatterns = compilePatterns(excludedPatterns); } @Override protected boolean matches(String pattern, int patternIndex) { Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern); return matcher.matches(); } @Override protected 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() { @Override public Pointcut getPointcut() { JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut(); pc.setPatterns("org.javaboy.bean.aop3.ICalculator.set.*"); pc.setExcludedPattern("org.javaboy.bean.aop3.ICalculator.setA"); return pc; } @Override public Advice getAdvice() { return new MethodInterceptor() { @Override public 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; } }; } @Override public boolean isPerInstance() { return true; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add(3,4); calculator.minus(3, 4); calculator.setA(5);上面这个例子也是拦截 setXXX 方法,不过需要注意的是,方法名匹配的时候使用的是方法的全路径。另外还需要注意,在配置匹配规则的时候,还可以设置 ExcludedPattern,实际上在匹配的时候,首先进行正向匹配,就是先看下方法名是否满足规则,如果满足,则方法名再和 ExcludedPattern 进行比对,如果不满足,则这个方法才会被确定下来要拦截。
public abstract class DynamicMethodMatcherPointcut extends DynamicMethodMatcher implements Pointcut { @Override public ClassFilter getClassFilter() { return ClassFilter.TRUE; } @Override public final MethodMatcher getMethodMatcher() { return this; } }可以看到,getClassFilter 直接返回 TRUE,也就是类就直接匹配了,getMethodMatcher 返回的则是当前对象,那是因为当前类实现了 DynamicMethodMatcher 接口,这就是一个方法匹配器:
public abstract class DynamicMethodMatcher implements MethodMatcher { @Override public final boolean isRuntime() { return true; } @Override public boolean matches(Method method, Class<?> targetClass) { return true; } }小伙伴们看到,这里 isRuntime 方法返回 true,该方法为 true,意味着三个参数的 matches 方法将会被调用,所以这里两个参数的 matches 方法可以直接返回 true,不做任何控制。当然,也可以在两个参数的 matches 方法中做一些前置的判断。
public class MyDynamicMethodMatcherPointcut extends DynamicMethodMatcherPointcut { @Override public boolean matches(Method method, Class<?> targetClass) { return method.getName().startsWith("set"); } @Override public 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() { @Override public Pointcut getPointcut() { return new MyDynamicMethodMatcherPointcut(); } @Override public Advice getAdvice() { return new MethodInterceptor() { @Override public 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; } }; } @Override public boolean isPerInstance() { return true; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add(3,4); calculator.minus(3, 4); calculator.setA(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; } } @Override public ClassFilter getClassFilter() { return this.classFilter; } @Override public 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); } }首先小伙伴们注意到,这个类一共有四个构造方法,从上往下分别是:
public class AnnotationClassFilter implements ClassFilter { //... @Override public boolean matches(Class<?> clazz) { return (this.checkInherited ? AnnotatedElementUtils.hasAnnotation(clazz, this.annotationType) : clazz.isAnnotationPresent(this.annotationType)); } //... }这里省略了一些代码,关键地方就是这个匹配方法了,如果需要检查父类是否包含该注解,则调用 AnnotatedElementUtils.hasAnnotation 方法进行查找,否则直接调用 clazz.isAnnotationPresent 方法判断当前类上是否包含指定注解即可。
private static class AnnotationCandidateClassFilter implements ClassFilter { private final Class<? extends Annotation> annotationType; AnnotationCandidateClassFilter(Class<? extends Annotation> annotationType) { this.annotationType = annotationType; } @Override public boolean matches(Class<?> clazz) { return AnnotationUtils.isCandidateClass(clazz, this.annotationType); } }这里就是调用了 AnnotationUtils.isCandidateClass 方法进行判断,这个方法用来判断指定类是不是可以承载指定注解的候选类,返回 true 的规则是:
public class AnnotationMethodMatcher extends StaticMethodMatcher { @Override public 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。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyAction { } 然后将之添加到某一个方法上: public class CalculatorImpl implements ICalculator { @Override public void add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); } @MyAction @Override public int minus(int a, int b) { return a - b; } @Override public 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() { @Override public Pointcut getPointcut() { return new AnnotationMatchingPointcut(null, MyAction.class); } @Override public Advice getAdvice() { return new MethodInterceptor() { @Override public 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; } }; } @Override public boolean isPerInstance() { return true; } }); ICalculator calculator = (ICalculator) proxyFactory.getProxy(); calculator.add(3,4); calculator.minus(3, 4); calculator.setA(5);只有 minus 方法被拦截下来了。
public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements 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); @Override public ClassFilter getClassFilter() { obtainPointcutExpression(); return this; } @Override public 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 方法就可以此为依据,进行匹配了。
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() { @Override public Pointcut getPointcut() { return new ControlFlowPointcut(AopDemo04.class, "evl"); } @Override public Advice getAdvice() { return new MethodInterceptor() { @Override public 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; } }; } @Override public 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 方法,那么切点是不会生效的。
public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable { @Override public boolean matches(Class<?> clazz) { return true; } @Override public boolean matches(Method method, Class<?> targetClass) { return true; } @Override public boolean isRuntime() { return true; } @Override public 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; } @Override public ClassFilter getClassFilter() { return this; } @Override public MethodMatcher getMethodMatcher() { return this; } }大家可以看到,isRuntime 方法返回 true,表示这是一个动态的方法匹配器。关键的 matches 方法,就是根据调用栈中的信息,去比较给定的类名和方法名是否满足。
ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(new CalculatorImpl()); proxyFactory.addInterface(ICalculator.class); proxyFactory.addAdvisor(new PointcutAdvisor() { @Override public Pointcut getPointcut() { NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut(); nameMatchMethodPointcut.setMappedNames("add"); ComposablePointcut pc = new ComposablePointcut((Pointcut) nameMatchMethodPointcut); pc.union(new AnnotationMatchingPointcut(null, MyAction.class)); return pc; } @Override public Advice getAdvice() { return new MethodInterceptor() { @Override public 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; } }; } @Override public 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 即可。
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; } @Override public boolean matches(Class<?> clazz) { for (ClassFilter filter : this.filters) { if (filter.matches(clazz)) { return true; } } return false; } } }可以看到,传入的多个 ClassFilter 被组装到一起,在 matches 方法中逐个遍历,只要其中一个返回 true,就是 true。