自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring AOP 中,切點有多少種定義方式?

開發(fā) 前端
這個類不是 public 的,所以意味著我們自己開發(fā)中沒法直接使用這個切點。然后大家看到,在 getClassFilter 和 getMethodMatcher 方法中,這里都返回了對應的 TRUE,而這兩個 TRUE 實現(xiàn)非常簡單,就是在需要做比對的地方,不做任何比對,直接返回 true 即可,這就導致了最終將所有東西都攔截下來。

在 Spring AOP 中,我們最常用的切點定義方式主要是兩種:

  1. 使用 execution 進行無侵入攔截。
  2. 使用注解進行攔截。

這應該是是小伙伴們?nèi)粘9ぷ髦惺褂米疃嗟膬煞N切點定義方式了。但是除了這兩種還有沒有其他的呢?今天松哥就來和大家聊一聊這個話題。

1. Pointcut 分類

來看下 Pointcut 的定義:

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

從方法名上就能看出來,getClassFilter 進行類的過濾,getMethodMatcher 進行方法過濾。通過過濾 Class 和過濾 Method,我們就能夠鎖定一個攔截對象了。

再來看下 Pointcut 類的繼承關(guān)系圖:

圖片圖片

可以看到,其實實現(xiàn)類不算多,大部分看名字其實都能猜出來是干嘛的,這些實現(xiàn)類我們大致上又可以分為六大類:

  1. 靜態(tài)方法切點:StaticMethodMatcherPointcut 表示靜態(tài)方法切點的抽象基類,默認情況下匹配所有的類,然后通過不同的規(guī)則去匹配不同的方法。
  2. 動態(tài)方法切點:DynamicMethodMatcherPointcut 表示動態(tài)方法切點的抽象基類,默認情況下它匹配所有的類,然后通過不同的規(guī)則匹配不同的方法,這個有點類似于 StaticMethodMatcherPointcut,不同的是,StaticMethodMatcherPointcut 僅對方法簽名進行匹配并且僅匹配一次,而 DynamicMethodMatcherPointcut 會在運行期間檢查方法入?yún)⒌闹担捎诿看蝹魅氲膮?shù)可能都不一樣,所以沒調(diào)用都要去判斷,因此就導致 DynamicMethodMatcherPointcut 的性能差一些。
  3. 注解切點:AnnotationMatchingPointcut。
  4. 表達式切點:ExpressionPointcut。
  5. 流程切點:ControlFlowPointcut。
  6. 復合切點:ComposablePointcut。

除了上面這六個之外,另外還有一個落單的 TruePointcut,這個從名字上就能看出來是攔截一切。

所以滿打滿算,有七種類型的切點,接下來我們就來逐個分析一下。

2. TruePointcut

這個實現(xiàn)類從名字上看就是攔截所有,攔截一切,我們來看下這個類怎么做的:

final class TruePointcut implements Pointcut, Serializable {
    //...
 @Override
 public ClassFilter getClassFilter() {
  return ClassFilter.TRUE;
 }

 @Override
 public MethodMatcher getMethodMatcher() {
  return MethodMatcher.TRUE;
 }
    //...
}

首先小伙伴們注意,這個類不是 public 的,所以意味著我們自己開發(fā)中沒法直接使用這個切點。然后大家看到,在 getClassFilter 和 getMethodMatcher 方法中,這里都返回了對應的 TRUE,而這兩個 TRUE 實現(xiàn)非常簡單,就是在需要做比對的地方,不做任何比對,直接返回 true 即可,這就導致了最終將所有東西都攔截下來。

3. StaticMethodMatcherPointcut

StaticMethodMatcherPointcut 僅對方法名簽名(包括方法名和入?yún)㈩愋图绊樞颍┻M行匹配,靜態(tài)匹配僅判斷一次。

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,方法的匹配則返回當前對象,也就是看具體的實現(xiàn)。

StaticMethodMatcherPointcut 有幾個寫好的實現(xiàn)類,我們來看下。

3.1 SetterPointcut

看名字就知道,這個可以用來攔截所有的 set 方法:

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 開始,方法只有一個參數(shù)并且方法返回值為 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 + " 方法開始執(zhí)行了。。。");
                Object proceed = invocation.proceed();
                System.out.println(name + " 方法執(zhí)行結(jié)束了。。。");
                return proceed;
            }
        };
    }
    @Override
    public 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();
 @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 類似,對照理解就行了。

3.3 NameMatchMethodPointcut

這個是根據(jù)方法名來做匹配。

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 方法中進行匹配即可,匹配的時候直接調(diào)用 equals 方法進行匹配,如果 equals 方法沒有匹配上,則調(diào)用 isMatch 方法進行匹配,這個最終調(diào)用到 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 + " 方法開始執(zhí)行了。。。");
                Object proceed = invocation.proceed();
                System.out.println(name + " 方法執(zhí)行結(jié)束了。。。");
                return proceed;
            }
        };
    }
    @Override
    public boolean isPerInstance() {
        return true;
    }
});
ICalculator calculator = (ICalculator) proxyFactory.getProxy();
calculator.add(3,4);
calculator.minus(3, 4);
calculator.setA(5);

這里我設(shè)置的是攔截方法名為 add 或者方法名以 set 開頭的方法。

3.4 JdkRegexpMethodPointcut

這個是支持通過正則去匹配方法名,匹配上的方法就會被攔截下來。

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;
 }
}

可以看到,這里實際上就是傳入正則表達式,然后通過正則表達式去匹配方法名是否滿足條件。正則表達式可以傳入多個,系統(tǒng)會在 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 + " 方法開始執(zhí)行了。。。");
                Object proceed = invocation.proceed();
                System.out.println(name + " 方法執(zhí)行結(jié)束了。。。");
                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 方法,不過需要注意的是,方法名匹配的時候使用的是方法的全路徑。

另外還需要注意,在配置匹配規(guī)則的時候,還可以設(shè)置 ExcludedPattern,實際上在匹配的時候,首先進行正向匹配,就是先看下方法名是否滿足規(guī)則,如果滿足,則方法名再和 ExcludedPattern 進行比對,如果不滿足,則這個方法才會被確定下來要攔截。

StaticMethodMatcherPointcut 中主要就給我們提供了這些規(guī)則。

4. DynamicMethodMatcherPointcut

這個是動態(tài)方法匹配的切點,默認也是匹配所有類,但是在方法匹配這個問題,每次都會匹配,我們來看下:

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 返回的則是當前對象,那是因為當前類實現(xiàn)了 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,意味著三個參數(shù)的 matches 方法將會被調(diào)用,所以這里兩個參數(shù)的 matches 方法可以直接返回 true,不做任何控制。

當然,也可以在兩個參數(shù)的 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());
    }
}

在實際執(zhí)行過程中,兩個參數(shù)的 matches 方法返回 true,三個參數(shù)的 matches 方法才會執(zhí)行,如果兩個參數(shù)的 matches 方法返回 false,則三個參數(shù)的 matches 就不會執(zhí)行了。所以也可以兩個參數(shù)的 matches 方法直接固定返回 true,只在三個參數(shù)的 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 + " 方法開始執(zhí)行了。。。");
                Object proceed = invocation.proceed();
                System.out.println(name + " 方法執(zhí)行結(jié)束了。。。");
                return proceed;
            }
        };
    }
    @Override
    public 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;
  }
 }


 @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);
 }

}

首先小伙伴們注意到,這個類一共有四個構(gòu)造方法,從上往下分別是:

  1. 傳入類上的注解名稱,根據(jù)類上的注解去判斷是否需要攔截。
  2. 在 1 的基礎(chǔ)之上,再增加一個 checkInherited,這個表示是否需要檢查父類上是否存在相關(guān)的注解。
  3. 傳入類上和方法上的注解類型,根據(jù)這個注解類型去判斷是否需要攔截。
  4. 在 3 的基礎(chǔ)之上,再增加一個 checkInherited,這個表示是否需要檢查父類上或者方法上是否存在相關(guān)的注解。

其中,第四個構(gòu)造方法中處理的情況類型比較多,如果用戶傳入了 classAnnotationType,則構(gòu)建 AnnotationClassFilter 類型的 ClassFilter,否則構(gòu)建 AnnotationCandidateClassFilter 類型的 ClassFilter;如果用戶傳入了 methodAnnotationType,則構(gòu)建 AnnotationMethodMatcher 類型的 MethodMatcher,否則方法匹配器就直接返回匹配所有方法。

那么接下來我們就來看下這幾種不同的匹配器。

5.1 AnnotationClassFilter

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

這里省略了一些代碼,關(guān)鍵地方就是這個匹配方法了,如果需要檢查父類是否包含該注解,則調(diào)用 AnnotatedElementUtils.hasAnnotation 方法進行查找,否則直接調(diào)用 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;
 }
 @Override
 public boolean matches(Class<?> clazz) {
  return AnnotationUtils.isCandidateClass(clazz, this.annotationType);
 }
}

這里就是調(diào)用了 AnnotationUtils.isCandidateClass 方法進行判斷,這個方法用來判斷指定類是不是可以承載指定注解的候選類,返回 true 的規(guī)則是:

  1. 以 java. 開頭的注解,所有的類都能承載,這種情況會返回 true。
  2. 目標類不能以 java. 開頭,也就是說 JDK 中的類都不行,不是以 java. 開頭的類就可以返回 true。
  3. 給定類也不能是 Ordered 類。

滿足如上條件,這個類就是符合規(guī)定的。

AnnotationCandidateClassFilter 主要是針對用戶沒有傳入類上注解的情況,這種情況一般都是根據(jù)方法上的注解進行匹配的,所以這里主要是排除一些系統(tǒng)類。

5.3 AnnotationMethodMatcher

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,則去檢查一下父類對應的方法上是否有相關(guān)的注解,如果有,則表示方法匹配上了,返回 true。

否則先去檢查一下當前類是否是一個代理對象,代理對象中對應的方法肯定是沒有注解的,直接返回 false。

如果前面兩步還沒返回,最后考慮到目前這個方法可能是在一個接口上,要檢查一下它的實現(xiàn)類是否包含該注解。

這就是 AnnotationMatchingPointcut。松哥也舉一個簡單例子吧。

5.4 實踐

首先我自定義一個注解,如下:

@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 + " 方法開始執(zhí)行了。。。");
                Object proceed = invocation.proceed();
                System.out.println(name + " 方法執(zhí)行結(jié)束了。。。");
                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 方法被攔截下來了。

6. ExpressionPointcut

這個其實就是我們?nèi)粘i_發(fā)中使用最多的一種切點定義方式,可能項目中 99% 的切點定義都是使用的 ExpressionPointcut。這個具體用法我這里就不說了,因為比較豐富,都能單獨整一篇文章了,如果小伙伴對 ExpressionPointcut 的基礎(chǔ)用法還不熟悉的話,可以在公眾號【江南一點雨】后臺回復 ssm,有松哥之前錄制的入門視頻教程可以參考。

我這里就簡單把它的實現(xiàn)思路來和小伙伴們梳理一下,ExpressionPointcut 的實現(xiàn)都在 AspectJExpressionPointcut 類中,該類支持使用切點語言來對要攔截的方法做一個非常精確的描述,精確到要攔截方法的返回值,AspectJExpressionPointcut 類的實現(xiàn)比較長也比較復雜,我這里貼其中一些關(guān)鍵的代碼來看下:

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;
 }
}

其實關(guān)鍵還是要獲取到 ClassFilter 和 MethodMatcher,然后調(diào)用其 matches 方法,當前類剛好就是實現(xiàn)了這兩個,所以直接返回 this 就可以了。在 getClassFilter 或者 getMethodMatcher 方法執(zhí)行之前,都會先調(diào)用 obtainPointcutExpression 方法,去解析我們傳入的 expression 字符串,將之解析為一個 PointcutExpression 對象,接下來的 matches 方法就可以此為依據(jù),進行匹配了。

7. ControlFlowPointcut

ControlFlowPointcut 主要是指目標方法從某一個指定類的指定方法中執(zhí)行,切點才生效,否則不生效。

舉個簡單例子,如下:

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 + " 方法開始執(zhí)行了。。。");
                        Object proceed = invocation.proceed();
                        System.out.println(name + " 方法執(zhí)行結(jié)束了。。。");
                        return proceed;
                    }
                };
            }

            @Override
            public boolean isPerInstance() {
                return true;
            }
        });
        ICalculator calculator = (ICalculator) proxyFactory.getProxy();
        calculator.add(3,4);
        System.out.println("http://///////////////");
        evl(calculator);
    }

    public static void evl(ICalculator iCalculator) {
        iCalculator.add(3, 4);
    }
}

這里切點的意思就是說,必須要從 AopDemo04 這個類的 evl 方法中調(diào)用 add 方法,這個切點才會生效,如果是拿到了 ICalculator 對象后直接調(diào)用 add 方法,那么切點是不會生效的。

ControlFlowPointcut 的原理也很簡單,就是比較一下類名和方法名,如下:

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,表示這是一個動態(tài)的方法匹配器。關(guān)鍵的 matches 方法,就是根據(jù)調(diào)用棧中的信息,去比較給定的類名和方法名是否滿足。

8. ComposablePointcut

看名字就知道,這個可以將多個切點組合到一起,組合關(guān)系有求交集和求并集兩種,分別對應 ComposablePointcut 中的 intersection 方法和 union 方法。

如下案例:

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 + " 方法開始執(zhí)行了。。。");
                Object proceed = invocation.proceed();
                System.out.println(name + " 方法執(zhí)行結(jié)束了。。。");
                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 兩個切點聯(lián)合起來,既攔截方法名為 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;
  }

  @Override
  public boolean matches(Class<?> clazz) {
   for (ClassFilter filter : this.filters) {
    if (filter.matches(clazz)) {
     return true;
    }
   }
   return false;
  }

 }
}

可以看到,傳入的多個 ClassFilter 被組裝到一起,在 matches 方法中逐個遍歷,只要其中一個返回 true,就是 true。

9. 小結(jié)

好啦,這就是松哥今天和小伙伴們介紹的 7 中 Pointcut 了,希望借此小伙伴們對 Spring AOP 中切點的類型有一個完整的了解。再來回顧一下這其中切點:

  1. 靜態(tài)方法切點:StaticMethodMatcherPointcut 表示靜態(tài)方法切點的抽象基類,默認情況下匹配所有的類,然后通過不同的規(guī)則去匹配不同的方法。
  2. 動態(tài)方法切點:DynamicMethodMatcherPointcut 表示動態(tài)方法切點的抽象基類,默認情況下它匹配所有的類,然后通過不同的規(guī)則匹配不同的方法,這個有點類似于 StaticMethodMatcherPointcut,不同的是,StaticMethodMatcherPointcut 僅對方法簽名進行匹配并且僅匹配一次,而 DynamicMethodMatcherPointcut 會在運行期間檢查方法入?yún)⒌闹担捎诿看蝹魅氲膮?shù)可能都不一樣,所以沒調(diào)用都要去判斷,因此就導致 DynamicMethodMatcherPointcut 的性能差一些。
  3. 注解切點:AnnotationMatchingPointcut 根據(jù)制定注解攔截目標方法或者類。
  4. 表達式切點:ExpressionPointcut 這個是我們?nèi)粘i_發(fā)中使用最多的一種切點定義方式。
  5. 流程切點:ControlFlowPointcut 這個是要求必須從某一個位置調(diào)用目標方法,切點才會生效。
  6. 復合切點:ComposablePointcut 這個是把多個攔截器組裝在一起使用,有交集和并集兩種組裝方式。
  7. TruePointcut 這是框架內(nèi)部使用的一個切點,表示攔截一切。
責任編輯:武曉燕 來源: 江南一點雨
相關(guān)推薦

2015-05-06 10:05:22

javajava框架spring aop

2024-04-01 08:38:57

Spring@AspectAOP

2024-09-26 14:48:35

SpringAOP范式

2021-07-05 18:05:40

SpringBean方法

2010-09-09 15:45:15

IT認證

2011-06-03 11:53:06

Spring接口

2025-03-17 08:10:00

aviatorSpringJVM

2022-07-01 09:39:58

SpringAOPIOC

2020-06-17 08:31:10

權(quán)限控制Spring Secu

2021-07-27 10:49:10

SpringSecurity權(quán)限

2019-11-29 16:21:22

Spring框架集成

2009-06-19 13:28:30

Spring AOPSpring 2.0

2022-06-07 07:58:45

SpringSpring AOP

2011-07-22 17:22:20

Spring

2024-12-18 16:19:51

2024-07-08 09:03:31

2011-11-25 10:25:27

SpringJava

2009-06-19 18:26:38

Spring事務配置

2012-07-17 09:16:16

SpringSSH

2011-02-28 13:51:30

Spring事物配置
點贊
收藏

51CTO技術(shù)棧公眾號