首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家Spring】Spring AOP核心类Pointcut解析,对PointcutExpression切点表达式解析原理分析(以AspectJExpressionPointcut为例)

【小家Spring】Spring AOP核心类Pointcut解析,对PointcutExpression切点表达式解析原理分析(以AspectJExpressionPointcut为例)

作者头像
YourBatman
发布2019-09-03 16:22:48
4.1K1
发布2019-09-03 16:22:48
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

前言

首先需要说明一点:Pointcut接口有两个。

一个是:org.aspectj.lang.reflect.Pointcut,它是aspectj内部使用的。它只有一个实现类PointcutImpl。是它内部的抽象

另一个是:org.springframework.aop.Pointcut,这是Spring AOP体系中对切点的顶层抽象,贯穿整个AOP过程,非常重要。因此本文主要基于它,介绍一些原理以及它常用子类的一些使用。

备注:关于AspectJ和Spring AOP区别和联系,建议参阅:

【小家Spring】Spring AOP的多种使用方式以及神一样的AspectJ-AOP使用介绍

Pointcut家族

它是Spring AOP对切点的一个顶层首相,非常的重要。

首先得看看这个顶级接口抽象的图谱:

这里面有一个非常重要得子接口:ExpressionPointcut,它是用于解析String类型的切点表达式的接口(这也是我们使用得最最最多的)

Pointcut接口分析

**主要负责对系统的相应的Joinpoint进行捕捉,对系统中所有的对象进行Joinpoint所定义的规则进行匹配。**提供了一个TruePointcut实例,当Pointcut为TruePointcut类型时,则会忽略所有的匹配条件,永远返回true

显然可以看出,这个接口和ClassFilterMethodMatcher有关系

public interface Pointcut {

	ClassFilter getClassFilter();
	MethodMatcher getMethodMatcher();
	/**
	 * Canonical Pointcut instance that always matches.
	 * 意思是:用于匹配上的一个实例(意思是永远返回true嘛)
	 */
	Pointcut TRUE = TruePointcut.INSTANCE;
}

ClassFilter与MethodMatcher分别用于在不同的级别上限定Joinpoint的匹配范围,满足不同粒度的匹配

ClassFilter限定在类级别上,MethodMatcher限定在方法级别上

SpringAop主要支持在方法级别上的匹配,所以对类级别的匹配支持相对简单一些

ClassFilter
@FunctionalInterface
public interface ClassFilter {

	// true表示能够匹配。那就会进行织入的操作
	boolean matches(Class<?> clazz);
	// 常量 会匹配所有的类   TrueClassFilter不是public得class,所以只是Spring内部自己使用的
	ClassFilter TRUE = TrueClassFilter.INSTANCE;
}

Spring给他的实现类也比较多,如下:

RootClassFilter
public class RootClassFilter implements ClassFilter, Serializable {

	private Class<?> clazz;
	public RootClassFilter(Class<?> clazz) {
		this.clazz = clazz;
	}
	// 显然,传进来的candidate必须是clazz的子类才行
	@Override
	public boolean matches(Class<?> candidate) {
		return clazz.isAssignableFrom(candidate);
	}
}
AnnotationClassFilter
public class AnnotationClassFilter implements ClassFilter {
	...
	public AnnotationClassFilter(Class<? extends Annotation> annotationType) {
		// 默认情况下checkInherited给的false:不去看它继承过来的注解
		this(annotationType, false);
	}
	// checkInherited true:表示继承过来得注解也算
	public AnnotationClassFilter(Class<? extends Annotation> annotationType, boolean checkInherited) {
		Assert.notNull(annotationType, "Annotation type must not be null");
		this.annotationType = annotationType;
		this.checkInherited = checkInherited;
	}
	...
	@Override
	public boolean matches(Class<?> clazz) {
		return (this.checkInherited ?
				// 继承的注解也会找出来
				(AnnotationUtils.findAnnotation(clazz, this.annotationType) != null) :
				// 只会看自己本类的注解
				clazz.isAnnotationPresent(this.annotationType));
	}
}
AspectJExpressionPointcut

它既是个Pointcut,它也是个ClassFilter,下面会详细分析本类

MethodMatcher
public interface MethodMatcher {
	
	// 这个称为静态匹配:在匹配条件不是太严格时使用,可以满足大部分场景的使用
	boolean matches(Method method, @Nullable Class<?> targetClass);
	// 这个称为动态匹配(运行时匹配): 它是严格的匹配。在运行时动态的对参数的类型进行匹配
	boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);
	
	//两个方法的分界线就是boolean isRuntime()方法,步骤如下
	// 1、先调用静态匹配,若返回true。此时就会继续去检查isRuntime()的返回值
	// 2、若isRuntime()还返回true,那就继续调用动态匹配
	// (若静态匹配都匹配上,动态匹配那铁定更匹配不上得~~~~)

	// 是否需要执行动态匹配
	boolean isRuntime();
	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

应用场景:比如需要统计用户登录次数时,那么登录传入的参数就是可以忽略的,则静态匹配就足够了

但是若要在登陆时对用户账号执行特殊的操作**(如赋予特殊的操作权限)**,就需要对参数进行一个类似于检验的操作,因此需要动态匹配

它有两个非常重要的抽象实现:StaticMethodMatcherDynamicMethodMatcher

StaticMethodMatcher 静态匹配
public abstract class StaticMethodMatcher implements MethodMatcher {
	// 永远返回false表示只会去静态匹配
	@Override
	public final boolean isRuntime() {
		return false;
	}
	// 三参数matches抛出异常,使其不被调用
	@Override
	public final boolean matches(Method method, @Nullable Class<?> targetClass, Object... args) {
		// should never be invoked because isRuntime() returns false
		throw new UnsupportedOperationException("Illegal MethodMatcher usage");
	}

}

作用:它表示不会考虑具体 方法参数。因为不用每次都检查参数,那么对于同样的类型的方法匹配结果,就可以在框架内部缓存以提高性能。比如常用的实现类:AnnotationMethodMatcher

DynamicMethodMatcher 动态匹配
public abstract class DynamicMethodMatcher implements MethodMatcher {
	
	// 永远返回true
	@Override
	public final boolean isRuntime() {
		return true;
	}
	// 永远返回true,去匹配动态匹配的方法即可
	@Override
	public boolean matches(Method method, @Nullable Class<?> targetClass) {
		return true;
	}
}

说明:因为每次都要对方法参数进行检查,无法对匹配结果进行缓存,所以,匹配效率相对 StatisMethodMatcher 来说要差,但匹配度更高。(实际使用得其实较少)

JdkRegexpMethodPointcut:基于正则的Pointcut

Spring官方为我们提供了一个基于正则表达式来匹配方法名的Pointcut,JdkRegexpMethodPointcut

它提供了最重要的4个属性(patterns和excludedPatterns来自于父类AbstractRegexpMethodPointcut):

这里昂个属性来自于父类,相对来说就是比较简单的匹配signatureString(方法的全路径名称)

  • String[] patterns:匹配的正则表达式。如find.*表示所有方法名以find开始的方法
  • String[] excludedPatterns:排除的正则表达式们

下面两个是子类,也就是JdkRegexpMethodPointcut自己提供的属性

  • Pattern[] compiledPatterns:相当于把正则字符串,Pattern.compile()成正则对象
  • Pattern[] compiledExclusionPatterns:同上

都是数组,正则表达式都可以多个哟~~

需要注意的是,这两组含义相同,请不要同时跨组使用,没有意义,没必要深究。

这种切点表达式,在早期Spring中的使用较多,一般这么使用:

<!-- 自己书写的日志切面 -->
<bean id="logBeforeAdvice" class="com.fsx.aop.LogBeforeAdvice" />

<!-- 使用JDK的正则切点~~~~~~ -->
<bean id="regexPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="patterns">
         <list>
             <value>find.*</value><!-- 拦截所有方法名以find开始的方法 -->
         </list>
    </property>
</bean>

<!-- 切面+切点  组合成一个增强器即可~~~~~~ -->
<aop:config>
     <aop:advisor advice-ref="logBeforeAdvice" pointcut-ref="regexPointcut"/>
 </aop:config>

其实Spring为我们提供了一个简便的Advisor定义,可以方便的让我们同时指定一个JdkRegexpMethodPointcut和其需要对应的Advice,它就是RegexpMethodPointcutAdvisor,这样配置起来非常的方便

	<bean id="logBeforeAdvice" class="com.fsx.aop.LogBeforeAdvice" />
    <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="logBeforeAdvice"/>
        <property name="pattern" value="find.*"/>
    </bean>

这个举例事基于XML的,之前我们都是这么来用的。那么现在用Java代码的方式也实现一遍(不需要Spring容器):

    public static void main(String[] args) {

        ProxyFactory factory = new ProxyFactory(new Person());

        //声明一个aspectj切点,一张切面
        JdkRegexpMethodPointcut cut = new JdkRegexpMethodPointcut();
        //cut.setPattern("com.fsx.maintest.Person.run"); //它会拦截Person类下所有run的方法(无法精确到方法签名)
        //cut.setPattern(".*run.*");//.号匹配除"\r\n"之外的任何单个字符。*号代表零次或多次匹配前面的字符或子表达式  所以它拦截任意包下任意类的run方法
        cut.setPatterns(new String[]{".*run.*", ".*say.*"}); //可以配置多个正则表达  式...  sayHi方法也会被拦截 

        // 声明一个通知(此处使用环绕通知 MethodInterceptor )
        Advice advice = (MethodInterceptor) invocation -> {
            System.out.println("============>放行前拦截...");
            Object obj = invocation.proceed();
            System.out.println("============>放行后拦截...");
            return obj;
        };

        //切面=切点+通知
        // 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数
        // Pointcut.TRUE:表示啥都返回true,也就是说这个切面作用于所有的方法上/所有的方法
        // addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是Pointcut.TRUE切面,因此需要注意这些区别  相当于new DefaultPointcutAdvisor(Pointcut.TRUE,advice);
        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
        factory.addAdvisor(advisor);
        Person p = (Person) factory.getProxy();

        // 执行方法
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);

    }
}

class Person {

    public int run() {
        System.out.println("我在run...");
        return 0;
    }

    public void run(int i) {
        System.out.println("我在run...<" + i + ">");
    }

    public void say() {
        System.out.println("我在say...");
    }

    public void sayHi(String name) {
        System.out.println("Hi," + name + ",你好");
    }

    public int say(String name, int i) {
        System.out.println(name + "----" + i);
        return 0;
    }

}
输出:
============>放行前拦截...
我在run...
============>放行后拦截...
============>放行前拦截...
我在run...<10>
============>放行后拦截...
============>放行前拦截...
我在say...
============>放行后拦截...
============>放行前拦截...
Hi,Jack,你好
============>放行后拦截...
============>放行前拦截...
Tom----666
============>放行后拦截...

最后需要注意的是:RegexpMethodPointcutAdvisor没有提供不匹配的正则表达式注入方法,即没有excludedPatterns注入,如果需要该功能请还是使用JdkRegexpMethodPointcut

AspectJExpressionPointcut详细使用和分析

AspectJExpressionPointcut如字面,它和Expression有关,而这个切点表达式,最终还是依赖于AspectJ的jar包去解析的 Spring在使用@Aspect注解时,会大量的用到它

AspectJExpressionPointcut实现的切点比JdkRegexpMethodPointcut实现切点的好处就是,在设置切点的时候可以用切点语言来更加精确的表示拦截哪个方法。(可以精确到返回参数,参数类型,方法名,当然,也可以模糊匹配)

这里面就必须要先了解一下Pointcut的切点表达式的写法,以及Spring支持哪些呢?推荐博文:

【小家Spring】Spring AOP中@Pointcut切入点表达式最全面使用介绍

纯Java方式Demo:

下面我先用一个纯Java的方式的例子,先体验一把:

    public static void main(String[] args) {
        //String pointcutExpression = "execution( int com.fsx.maintest.Person.run() )"; // 会拦截Person.run()方法
        //String pointcutExpression = "args()"; // 所有没有入参的方法会被拦截。  比如:run()会拦截,但是run(int i)不会被拦截
        // ... AspectJExpressionPointcut支持的表达式 一共有11种(也就是Spring全部支持的切点表达式类型)
        String pointcutExpression = "@annotation(org.springframework.test.context.transaction.AfterTransaction)"; // 拦截上方法标有@AfterTransaction此注解的任意方法们


        // =============================================================
        ProxyFactory factory = new ProxyFactory(new Person());

        //声明一个aspectj切点,一张切面
        AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
        cut.setExpression(pointcutExpression); // 设置切点表达式

        // 声明一个通知(此处使用环绕通知 MethodInterceptor )
        Advice advice = (MethodInterceptor) invocation -> {
            System.out.println("============>放行前拦截...");
            Object obj = invocation.proceed();
            System.out.println("============>放行后拦截...");
            return obj;
        };

        //切面=切点+通知
        // 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数
        // Pointcut.TRUE:表示啥都返回true,也就是说这个切面作用于所有的方法上/所有的方法
        // addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是Pointcut.TRUE切面,因此需要注意这些区别  相当于new DefaultPointcutAdvisor(Pointcut.TRUE,advice);
        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
        factory.addAdvisor(advisor);
        Person p = (Person) factory.getProxy();

        // 执行方法
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);

    }

}

class Person {

    @AfterTransaction
    public int run() {
        System.out.println("我在run...");
        return 0;
    }

    public void run(int i) {
        System.out.println("我在run...<" + i + ">");
    }

    public void say() {
        System.out.println("我在say...");
    }

    public void sayHi(String name) {
        System.out.println("Hi," + name + ",你好");
    }

    public int say(String name, int i) {
        System.out.println(name + "----" + i);
        return 0;
    }

}

如上面的图,其实Spring也我们提供了AspectJExpressionPointcutAdvisor来专门处理基于AspectJ的通知+切点的

XML方式
public class Person {

    @AfterTransaction
    public int run() {
        System.out.println("我在run...");
        return 0;
    }

    public void run(int i) {
        System.out.println("我在run...<" + i + ">");
    }

    public void say() {
        System.out.println("我在say...");
    }

    public void sayHi(String name) {
        System.out.println("Hi," + name + ",你好");
    }

    public int say(String name, int i) {
        System.out.println(name + "----" + i);
        return 0;
    }

}

// 相当于准备了一个Advice
public class MyMethodInteceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("============>放行前拦截...");
        Object obj = invocation.proceed();
        System.out.println("============>放行后拦截...");
        return obj;
    }
}

书写Spring的xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 作为示例,这是需要被切入的目标类 -->
    <bean class="com.fsx.bean.Person"/>

    <!-- 切面=切点+通知 (采用面向切点语言进行配置切面)   此处为了便捷 直接使用 AspectJExpressionPointcutAdvisor -->
    <bean id="advisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
        <property name="expression"
                  value="@annotation(org.springframework.test.context.transaction.AfterTransaction)"></property>

        <!-- 一个Advisor里面对应一个advice~~~ -->
        <property name="advice">
            <bean class="com.fsx.aop.MyMethodInteceptor"/>
        </property>
    </bean>
</beans>

把该xml配置文件导入Config配置类,让它生效

...
@Configuration
@ImportResource(locations = "classpath:spring.xml")
public class RootConfig { ... }

启动Spring容器(采用JUnit测试):

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {

    @Autowired
    private Person p;

    @Test
    public void test1() {

        System.out.println(p.getClass()); //class com.fsx.bean.Person$$EnhancerBySpringCGLIB$$cba1d735

        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);
    }
}

输出:
class com.fsx.bean.Person$$EnhancerBySpringCGLIB$$cba1d735 // 说明它是CGLIB的代理
============>放行前拦截...
我在run...
============>放行后拦截...
我在run...<10>
我在say...
Hi,Jack,你好
Tom----666

备注:此xml的配置方式其实是最原始的Spring AOP的使用。并没有使用到<aop:config>、<aop:pointcut>这种标签注解,关于使用它们的xml配置方式,此处不做过多介绍了~~~因为也比较简单~

AspectJExpressionPointcut源码分析
// 很容易发现,自己即是ClassFilter,也是MethodMatcher   
// 它是子接口:ExpressionPointcut的实现类
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
		implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
		...
		private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
		
	// 从此处可以看出,Spring支持的AspectJ的切点语言表达式一共有10中(加上后面的自己的Bean方式一共11种)
	// AspectJ框架本省支持的非常非常多,详解枚举类:org.aspectj.weaver.tools.PointcutPrimitive
	static {
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
		SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
	}
	...
		
	// 它持有BeanFactory 的引用,但是是可以为null的,也就是说它脱离容器也能够正常work
	@Nullable
	private BeanFactory beanFactory;
	...
	// PointcutExpression是org.aspectj.weaver.tools.PointcutExpression是AspectJ的类
	// 它最终通过一系列操作,由org.aspectj.weaver.tools.PointcutParser#parsePointcutExpression从字符串表达式解析出来
	@Nullable
	private transient PointcutExpression pointcutExpression;
	
	...
	// 由此可见,我们不仅仅可议写&& ||  !这种。也支持 and or not这种哦~~~
	private String replaceBooleanOperators(String pcExpr) {
		String result = StringUtils.replace(pcExpr, " and ", " && ");
		result = StringUtils.replace(result, " or ", " || ");
		result = StringUtils.replace(result, " not ", " ! ");
		return result;
	}
	...
	// 这是ClassFilter 匹配类。借助的PointcutExpression#couldMatchJoinPointsInType 去匹配
	public boolean matches(Class<?> targetClass) { ... }
	// MethodMatcher 匹配方法,借助的PointcutExpression和ShadowMatch去匹配的
	public boolean matches(Method method, @Nullable Class<?> targetClass, boolean hasIntroductions) { ... }
	@Override
	public boolean isRuntime() {
		//mayNeedDynamicTest 相当于由AspectJ框架去判断的(是否有动态内容)
		return obtainPointcutExpression().mayNeedDynamicTest();
	}
	...

	// 初始化一个Pointcut的解析器。我们发现最后一行,新注册了一个BeanPointcutDesignatorHandler  它是准们处理Spring自己支持的bean() 的切点表达式的
	private PointcutParser initializePointcutParser(@Nullable ClassLoader classLoader) {
		PointcutParser parser = PointcutParser
				.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
						SUPPORTED_PRIMITIVES, classLoader);
		parser.registerPointcutDesignatorHandler(new BeanPointcutDesignatorHandler());
		return parser;
	}

	// 真正的解析,依赖于Spring自己实现的这个内部类(主要是ContextBasedMatcher 这个类,就会使用到BeanFactory了)
	private class BeanPointcutDesignatorHandler implements PointcutDesignatorHandler {

		private static final String BEAN_DESIGNATOR_NAME = "bean";
		@Override
		public String getDesignatorName() {
			return BEAN_DESIGNATOR_NAME;
		}
		// ContextBasedMatcher由Spring自己实现,对容器内Bean的匹配
		@Override
		public ContextBasedMatcher parse(String expression) {
			return new BeanContextMatcher(expression);
		}
	}

}

NameMatchMethodPointcut

如果创建切入点时候,我们往往只需要方法名字匹配,无需理会方法的签名和返回类型,这种情况下,我们可以使用 NameMatchMethodPointCut方法名字匹配切入点。(这种功能最弱,但显然效率是最高的)

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new Person());





        // 声明一个通知(此处使用环绕通知 MethodInterceptor )
        Advice advice = (MethodInterceptor) invocation -> {
            System.out.println("============>放行前拦截...");
            Object obj = invocation.proceed();
            System.out.println("============>放行后拦截...");
            return obj;
        };
        ////声明一个aspectj切点,一张切面
        //NameMatchMethodPointcut cut = new NameMatchMethodPointcut();
        //cut.setMappedName("run"); //会匹配所有的方法名为run的方法
        //// 切点+通知
        //Advisor advisor = new DefaultPointcutAdvisor(cut, advice);

        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        advisor.setMappedName("run");
        advisor.setAdvice(advice);

        factory.addAdvisor(advisor);
        Person p = (Person) factory.getProxy();

        // 执行方法
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);

    }

}
输出:
============>放行前拦截...
我在run...
============>放行后拦截...
============>放行前拦截...
我在run...<10>
============>放行后拦截...
我在say...
Hi,Jack,你好
Tom----666

其它Pointcut

上面已经介绍了Spring中使用得比较多的Pointcut,接下来简单的讲述一下稍微偏门些的Pointcut。

从顶部的pointcut的继承图中可以看出,有很多实现类。

ControlFlowPointCut:流程切入点

如果有这样的特殊需求:我们对一个方法进行切入通知,但只有这个方法在一个特定方法中被调用的时候执行通知(即存在流程上行的依赖关系),我们可以使用ControlFlowPointCut流程切入点

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new Person());

        // 声明一个通知(此处使用环绕通知 MethodInterceptor )
        Advice advice = (MethodInterceptor) invocation -> {
            System.out.println("============>放行前拦截...");
            Object obj = invocation.proceed();
            System.out.println("============>放行后拦截...");
            return obj;
        };
        ////声明一个aspectj切点,一张切面
        // 含义:Main类里面,方法名为funabc执行时,内部调用的任何代理的方法都会被拦截~~~ 它控制的是整个流程
        ControlFlowPointcut cut = new ControlFlowPointcut(Main.class, "funabc");

        // 切点+通知
        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
        factory.addAdvisor(advisor);
        Person p = (Person) factory.getProxy();

        // 执行方法
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);

        // 此处调用Main类,方法名为funabc的方法。内部代理对象的方法就都会被拦截上了
        funabc(p);
    }

    private static void funabc(Person person) {
        person.run();
        person.say();
    }

}

输出:
我在run...
我在run...<10>
我在say...
Hi,Jack,你好
Tom----666
============>放行前拦截...
我在run...
============>放行后拦截...
============>放行前拦截...
我在say...
============>放行后拦截...

使用流程切入点有时候可以解决不少问题,但值得注意的是:

使用流程切入点在jdk1.4中比其他切入点要慢5倍,在1.3上则要慢10倍,追求高性能的要慎重使用

ComposablePointcut 组合切入点

从上面的例子中,每次我们只能定义一个切入点(切点表达式)。有的时候,一个切点可能难以描述目标连接点的信息,而是需要同时满足两个切入点才行,那么ComposablePointcut就派上了用场(aspectJ里面的&& ||等其实也能达到类似的效果)。

但是更好的方式是使用Spring提供的ComposalbePointcut把两个切点组合起来,通过切点的复合运行算表示,ComposalbePointcut可以将多个切点以并集或者交集的方式组合起来,提供切点之间复合运算的功能。

先看一个Demo:

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new Person());

        // 声明一个通知(此处使用环绕通知 MethodInterceptor )
        Advice advice = (MethodInterceptor) invocation -> {
            System.out.println("============>放行前拦截...");
            Object obj = invocation.proceed();
            System.out.println("============>放行后拦截...");
            return obj;
        };

        // 先创建一个流程切入点
        ControlFlowPointcut controlFlowPointcut = new ControlFlowPointcut(Main.class, "funabc");
        // 再创建一个方法名切入点
        NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();
        nameMatchMethodPointcut.addMethodName("say");

        // 创建一个复合切点 把上面两者并且进来
        ComposablePointcut cut = new ComposablePointcut();
        cut.intersection((Pointcut) controlFlowPointcut).intersection((Pointcut)nameMatchMethodPointcut);

        // 切点+通知(注意:此处放的是复合切面)
        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
        factory.addAdvisor(advisor);
        Person p = (Person) factory.getProxy();

        // 执行方法
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);

        funabc(p);
    }

    private static void funabc(Person person) {
        person.run();
        person.say();

    }

}

输出:
我在run...
我在run...<10>
我在say...
Hi,Jack,你好
Tom----666
我在run...
============>放行前拦截...
我在say...
============>放行后拦截...

从结果中和上面对比我们能看出,两个切入点有并且的效果。(只有say方法被拦截了,run方法并没有被拦截)

ComposablePointcut 源码分析
public class ComposablePointcut implements Pointcut, Serializable {

	// 它持有ClassFilter 和 MethodMatcher ,最终通过它去组合匹配
	private ClassFilter classFilter;
	private MethodMatcher methodMatcher;

	// 构造函数一个共5个

	// 匹配所有类所有方法的复合切点
	public ComposablePointcut() {
		this.classFilter = ClassFilter.TRUE;
		this.methodMatcher = MethodMatcher.TRUE;
	}
	// 匹配特定切点的复合切点(相当于把这个节点包装了一下而已)
	public ComposablePointcut(Pointcut pointcut) {
		Assert.notNull(pointcut, "Pointcut must not be null");
		this.classFilter = pointcut.getClassFilter();
		this.methodMatcher = pointcut.getMethodMatcher();
	}
	// 匹配特定类**所有方法**的复合切点
	public ComposablePointcut(ClassFilter classFilter) {
		Assert.notNull(classFilter, "ClassFilter must not be null");
		this.classFilter = classFilter;
		this.methodMatcher = MethodMatcher.TRUE;
	}
	// 匹配**所有类**特定方法的复合切点
	public ComposablePointcut(MethodMatcher methodMatcher) {
		Assert.notNull(methodMatcher, "MethodMatcher must not be null");
		this.classFilter = ClassFilter.TRUE;
		this.methodMatcher = methodMatcher;
	}
	// 匹配特定类特定方法的复合切点(这个是最为强大的)
	public ComposablePointcut(ClassFilter classFilter, MethodMatcher methodMatcher) {
		Assert.notNull(classFilter, "ClassFilter must not be null");
		Assert.notNull(methodMatcher, "MethodMatcher must not be null");
		this.classFilter = classFilter;
		this.methodMatcher = methodMatcher;
	}
	
	// 匹配特定类特定方法的复合切点(这个是最为强大的)
	public ComposablePointcut union(ClassFilter other) {
		this.classFilter = ClassFilters.union(this.classFilter, other);
		return this;
	}

	// ==========3个并集(union) / 3个交集(intersection) 运算的方法========
	public ComposablePointcut intersection(ClassFilter other) {
		this.classFilter = ClassFilters.intersection(this.classFilter, other);
		return this;
	}
	public ComposablePointcut union(MethodMatcher other) {
		this.methodMatcher = MethodMatchers.union(this.methodMatcher, other);
		return this;
	}
	public ComposablePointcut intersection(MethodMatcher other) {
		this.methodMatcher = MethodMatchers.intersection(this.methodMatcher, other);
		return this;
	}
	public ComposablePointcut union(Pointcut other) {
		this.methodMatcher = MethodMatchers.union(
				this.methodMatcher, this.classFilter, other.getMethodMatcher(), other.getClassFilter());
		this.classFilter = ClassFilters.union(this.classFilter, other.getClassFilter());
		return this;
	}
	public ComposablePointcut intersection(Pointcut other) {
		this.classFilter = ClassFilters.intersection(this.classFilter, other.getClassFilter());
		this.methodMatcher = MethodMatchers.intersection(this.methodMatcher, other.getMethodMatcher());
		return this;
	}
	...
}

ComposablePointcut没有提供直接对两个切点类型并集交集的运算的方法。若需要,请参照org.springframework.aop.support.Pointcuts这个工具类里面有对两个Pointcut进行并集、交集的操作(后面再介绍)

AnnotationMatchingPointcut 注解切入点

根据对象是否有指定类型的注解来匹配Pointcut

有两种注解,类级别注解和方法级别注解

//仅指定类级别的注解, 标注了 ClassLevelAnnotation 注解的类中的**所有方法**执行的时候,将全部匹配。  
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);  
// === 还可以使用静态方法创建 pointcut 实例  
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);  


//仅指定方法级别的注解,标注了 MethodLeavelAnnotaion 注解的**方法(忽略类匹配)都将匹配**  
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);  

==========这个是同时想限定:===============
//同时限定类级别和方法级别的注解,只有标注了 ClassLevelAnnotation 的类中 ***同时***标注了 MethodLevelAnnotation 的方法才会匹配  
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);  

Demo:略

总结

其实,这些基础的知识也是为了去更好的理解Spring的自动代理创建器铺路

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年04月13日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Pointcut家族
  • Pointcut接口分析
    • ClassFilter
      • MethodMatcher
      • JdkRegexpMethodPointcut:基于正则的Pointcut
      • AspectJExpressionPointcut详细使用和分析
        • AspectJExpressionPointcut源码分析
        • NameMatchMethodPointcut
        • 其它Pointcut
          • ControlFlowPointCut:流程切入点
            • ComposablePointcut 组合切入点
              • AnnotationMatchingPointcut 注解切入点
                • 总结
                相关产品与服务
                云顾问
                云顾问(Tencent Cloud Smart Advisor)是一款提供可视化云架构IDE和多个ITOM领域垂直应用的云上治理平台,以“一个平台,多个应用”为产品理念,依托腾讯云海量运维专家经验,助您打造卓越架构,实现便捷、灵活的一站式云上治理。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档