【源码】Spring AOP 4 Pointcut

前言

spring aop
Spring AOP 同样也定义好几大族接口,以辅助 AOP 的实现,这章节我们来解析 Pointcut 接口

Pointcut ,切点。它是对 Joinpoint 的匹配点的抽象,可以理解为 Advice 通过 Pointcut 找到 Joinpoint

Pointcut

public interface Pointcut {

	// 类匹配
	ClassFilter getClassFilter();

	// 方法匹配
	MethodMatcher getMethodMatcher();

	// 匹配所有 Pointcut 
	Pointcut TRUE = TruePointcut.INSTANCE;

}

PointcutClassFilterMethodMatcher 组成

ClassFilter

@FunctionalInterface
public interface ClassFilter {

	// 给定类是否匹配
	boolean matches(Class<?> clazz);

	// 匹配所有类
	ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

ClassFilter 相对比较简单,主要提供 match 方法校验目标类是否匹配

AnnotationClassFilter

// 检查给定类是否包含指定注解
public class AnnotationClassFilter implements ClassFilter {

	// 目标注解
	private final Class<? extends Annotation> annotationType;

	// 是否检查父类
	private final boolean checkInherited;

	// ...

	/**
	 * 层级检查依赖方法 AnnotatedElementUtils#hasAnnotation
	 * 非层级检查依赖方法 Class#isAnnotationPresent
	 */
	@Override
	public boolean matches(Class<?> clazz) {
		return (this.checkInherited ? AnnotatedElementUtils.hasAnnotation(clazz, this.annotationType) :
				clazz.isAnnotationPresent(this.annotationType));
	}

	// ...

}
  • 常用的实现类 AnnotationClassFilter ,主要是基于注解匹配类
  • 属性 checkInherited 决定是否检查上层类,分别基于方法 AnnotatedElementUtils#hasAnnotationClass#isAnnotationPresent

AnnotationCandidateClassFilter

	private static class AnnotationCandidateClassFilter implements ClassFilter {

		// 指定注解
		private final Class<? extends Annotation> annotationType;

		AnnotationCandidateClassFilter(Class<? extends Annotation> annotationType) {
			this.annotationType = annotationType;
		}

		// 依赖 AnnotationUtils#isCandidateClass:类、方法、属性上是否有指定注解
		@Override
		public boolean matches(Class<?> clazz) {
			return AnnotationUtils.isCandidateClass(clazz, this.annotationType);
		}

		// ...

	}

一个常用的内部类实现,也是基于注解匹配,但是主要类、方法、属性任一含有目标注解就匹配,比如 @Async 注解

MethodMatcher

public interface MethodMatcher {

	// 静态匹配
	boolean matches(Method method, Class<?> targetClass);

	// true 动态 false 静态
	boolean isRuntime();

	// 动态匹配
	boolean matches(Method method, Class<?> targetClass, Object... args);

	// 匹配所有方法
	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

MethodMatcher 主要区分两类:

  • 静态匹配,不会对方法的参数进行匹配
  • 动态匹配,会同时匹配方法的参数,但不常用

主要看下静态相关实现

StaticMethodMatcher

public abstract class StaticMethodMatcher implements MethodMatcher {

	// 静态
	@Override
	public final boolean isRuntime() {
		return false;
	}

	// 因为是静态,所以动态匹配方法 UnsupportedOperationException
	@Override
	public final boolean matches(Method method, Class<?> targetClass, Object... args) {
		throw new UnsupportedOperationException("Illegal MethodMatcher usage");
	}

}

静态抽象基类,isRuntime 返回 false,动态匹配方法调用抛出 UnsupportedOperationException 异常,主要聚焦于 matches(Method method, Class<?> targetClass) 方法的实现

AnnotationMethodMatcher

public class AnnotationMethodMatcher extends StaticMethodMatcher {

	// 目标注解
	private final Class<? extends Annotation> annotationType;

	// 是否检查层级
	private final boolean checkInherited;

	// ...

	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		
		// 先进行一次快速匹配
		if (matchesMethod(method)) {
			return true;
		}
		
		// 忽略代理类,因为代理类没有注解信息
		if (Proxy.isProxyClass(targetClass)) {
			return false;
		}
		
		// 假设是接口方法,尝试获取实现类方法
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
		
		// 如果获取到了再匹配一次
		return (specificMethod != method && matchesMethod(specificMethod));
	}

	// 根据 checkInherited 区分是否需要检查父类方法
	private boolean matchesMethod(Method method) {
		return (this.checkInherited ? AnnotatedElementUtils.hasAnnotation(method, this.annotationType) :
				method.isAnnotationPresent(this.annotationType));
	}

	// ...

}
  • AnnotationClassFilter 对应的,AnnotationMethodMatcher 基于注解进行方法匹配
  • 同样的,属性 checkInherited 决定是否层级匹配

StaticMethodMatcherPointcut

/**
 * 首先是一个 MethodMatcher,同时是一个 Pointcut,这样就可以拓展出
 * 		ClassFilter 的能力了,值得学习
 */
public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut {

	private ClassFilter classFilter = ClassFilter.TRUE;

	// 允许指定一个 ClassFilter,拓展出了 ClassFilter 的能力
	public void setClassFilter(ClassFilter classFilter) {
		this.classFilter = classFilter;
	}

	@Override
	public ClassFilter getClassFilter() {
		return this.classFilter;
	}

	// 自己就是个 MethodMatcher
	@Override
	public final MethodMatcher getMethodMatcher() {
		return this;
	}

}
  • 挺值得学习的设计模式,类似一种组合,跟 MethodBeforeAdviceInterceptor 的设计类似,StaticMethodMatcherPointcut 本身是个 MethodMatcher,实现 Pointcut 又拓展出了 ClassFilter 的能力,这样子类只要专注实现 MethodMatcher 的方法就好
  • setClassFilter 方法支持设置指定的 ClassFilter
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;
	}

	// 静态匹配,equals 或者正则匹配
	@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;
	}

	// 支持形如 "xxx*", "*xxx" 的正则匹配
	protected boolean isMatch(String methodName, String mappedName) {
		return PatternMatchUtils.simpleMatch(mappedName, methodName);
	}

	// 略
}

StaticMethodMatcherPointcut 的基础上,支持基于方法名匹配,同时支持形如 xxx*, *xxx 的模糊匹配,比如*Service 匹配所有 Serivce 结尾的方法

demo
@My
public class A {

	public void aaa() {

	}

	public void bbb() {

	}
}

public class B {

	public void aaa() {

	}
} 

public class NameMatchMethodTest {
    public static void main(String[] args) {
        NameMatchMethodPointcut matchMethodPointcut
                = new NameMatchMethodPointcut();

        matchMethodPointcut.setClassFilter(
                new AnnotationClassFilter(My.class)
        );
        matchMethodPointcut.setMappedName("*a");

        if (matchMethodPointcut.getClassFilter().matches(A.class)) {
            for (Method m : A.class.getDeclaredMethods()) {
                boolean match = matchMethodPointcut.matches(m, A.class);
                System.out.println(
                        "A类的方法" + m.getName() + (match ? " 匹配" : " 不匹配")
                );
            }
        } else {
            System.out.println("A类不匹配");
        }

        if (matchMethodPointcut.getClassFilter().matches(B.class)) {
            for (Method m : B.class.getDeclaredMethods()) {
                boolean match = matchMethodPointcut.matches(m, B.class);
                System.out.println(
                        "B类的方法" + m.getName() + (match ? " 匹配" : " 不匹配")
                );
            }
        } else {
            System.out.println("B类不匹配");
        }
    }
}

// 结果
A类的方法aaa 匹配
A类的方法bbb 不匹配
B类不匹配
StaticMethodMatcherPointcutDemo
public class StaticMethodMatcherPointcutDemo extends StaticMethodMatcherPointcut {

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return "a".equals(method.getName());
    }

    public void a() {

    }

    public void b() {

    }

    @Test
    public void test() throws NoSuchMethodException {
        StaticMethodMatcherPointcutDemo demo = new StaticMethodMatcherPointcutDemo();
        demo.setClassFilter(clazz -> clazz == StaticMethodMatcherPointcutDemo.class);
        boolean a = demo.matches(StaticMethodMatcherPointcutDemo.class.getMethod("a")
                , StaticMethodMatcherPointcutDemo.class);
        System.out.println(a);  // true
        boolean b = demo.matches(StaticMethodMatcherPointcutDemo.class.getMethod("b")
                , StaticMethodMatcherPointcutDemo.class);
        System.out.println(b);  // false
    }
}

自己实现一个 StaticMethodMatcherPointcut 加深印象

ComposablePointcut

Composable(可组合的)的 Pointcut,提供了 union(并集) intersection(交集) 等方法,而且还支持链式调用,设计思想值得学习

demo

	@Deprecated
    public static class Sample {

        @Autowired
        public void a() {

        }

        // @After("")
        public void b() {

        }

    }

    @Test
    public void test() throws NoSuchMethodException {
        AnnotationMatchingPointcut annotationMatchingPointcut
                = new AnnotationMatchingPointcut(Deprecated.class, After.class, false);
        ComposablePointcut union = new ComposablePointcut(annotationMatchingPointcut)
                .union(new AnnotationMethodMatcher(Autowired.class));
        Method a = Sample.class.getMethod("a");
        Method b = Sample.class.getMethod("b");

        // true
        System.out.println(union.getMethodMatcher().matches(a, String.class));
        // false
        System.out.println(union.getMethodMatcher().matches(b, String.class));
    }

AspectJExpressionPointcut

这个类比较复杂也比较重要,它的主要功能可以根据类名理解:基于 AspectJ 表达式的 Pointcut

同时它也是 ClassFilter MethodMatcher,对于类和方法的匹配都是基于 org.aspectj.weaver.tools 下的原生类来实现的

写个 demo 演示一下

public class AService implements A {

	public void a() {

	}
}

public class BService implements B {

	public void b() {

	}
}

public class Test2 {

    @Test
    public void test1() {

        AspectJExpressionPointcut aspectJExpressionPointcut
                = new AspectJExpressionPointcut();
        aspectJExpressionPointcut.setExpression("execution(* a())");

        System.out.println("A类:" + aspectJExpressionPointcut.matches(AService.class));
        System.out.println("B类:" + aspectJExpressionPointcut.matches(BService.class));

        System.out.println("================================");

        for (Method m : AService.class.getDeclaredMethods()) {
            System.out.println(m.getName() + ": " +
                    aspectJExpressionPointcut.matches(m, m.getDeclaringClass()));
        }

        System.out.println("================================");

        for (Method m : BService.class.getDeclaredMethods()) {
            System.out.println(m.getName() + ": " +
                    aspectJExpressionPointcut.matches(m, m.getDeclaringClass()));
        }
    }
}

// 结果
A类:true
B类:true
================================
a: true
================================
b: false

类图

Pointcut

Pointcut

ClassFilter

ClassFilter

MethodMatcher

MethodMatcher

总结

这一章节涉及的类比较多一点,主要对 Pointcut 体系抽象做了整体的了解,它可以与 ClassFilterMethodMatcher 组合,来实现对类、方法的匹配

其中 AspectJExpressionPointcut 就是对基于注解的 AspectJ切点表达式 的匹配实现

下一章节,了解下 Spring 下的 Advisor 抽象

上一篇:【源码】Spring AOP 3 Joinpoint
下一篇:【源码】Spring AOP 5 Advisor

参考

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package com.spring.dao; import org.springframework.stereotype.Component; @Component("userDAO") public class UserDao { public void say() { System.out.println("say method is called"); } public void smile() { System.out.println("smile method is called"); } public void cry() { System.out.println("cry method is called"); } public void jump() { System.out.println("jump method is called"); } } 注意观察包名。@Component("userDAO")等价于在spring配置文件中定义一个<bean id="userDAO"/> 编写Service package com.spring.service; import javax.annotation.Resource; import org.springframework.stereotype.Component; import com.spring.dao.UserDao; @Component("userService") public class UserService { @Resource(name="userDAO") private UserDao dao; public UserDao getDao() { return dao; } public void setDao(UserDao dao) { this.dao = dao; } public void say() { dao.say(); } public void smile() { dao.smile(); } public void cry() { dao.cry(); } public void jump() { dao.jump(); } } 注意观察包名。@Component("userService")等价于在spring配置文件中定义一个<bean id="userService"/> @Resource(name="userDAO")将userDA注入进来 写一个拦截器的类 package com.spring.aop; import org.springframework.stereotype.Component; @Component("logIntercepter") public class LogIntercepter { public void before(){ System.out.println("----------before-------------"); } public void after(){ System.out.println("----------after-------------"); } public void exception(){ System.out.println("----------exception-------------"); } public void around(){ System.out.println("----------exception-------------"); } } 注意观察包名。@Component("logIntercepter")等价于在spring配置文件中定义一个<bean id="logIntercepter"/> applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <context:annotation-config/> <context:component-scan base-package="com.spring.*"/> <aop:config> <aop:aspect id="aspect" ref="logIntercepter"> <aop:pointcut expression="execution(* com.spring.service..*(..))" id="pointCut"/> <aop:before method="before" pointcut-ref="pointCut"/> <aop:after method="after" pointcut-ref="pointCut"/> <aop:after-throwing method="exception" pointcut-ref="pointCut"/> <!-- <aop:around method="around" pointcut-ref="pointCut"/> --> </aop:aspect> </aop:config> </beans><context:annotation-config/> <context:component-scan base-package="com.spring.*"/> 两行为开启spring的注解配置 <aop:aspect id="aspect" ref="logIntercepter"> 引入具体的AOP操作类 <aop:pointcut expression="execution(* com.spring.service..*(..))" id="pointCut"/>声明一个切入点,注意execution表达式的写法 <aop:before method="before" pointcut-ref="pointCut"/> aop前置通知 <aop:after method="after" pointcut-ref="pointCut"/> aop后置通知, <aop:after-throwing method="exception" pointcut-ref="pointCut"/> aop异常通知 以上结合起来意思就是在调用com.spring.service包或子包下的所有方法之前或之后或抛出异常时依次调用id为logIntercepter的类中的before after exception方法 测试用例 package com.spring.test; import javax.annotation.Resource; import org.junit.Test; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import com.spring.service.UserService; @ContextConfiguration(locations="classpath:applicationContext.xml") public class SpringTest extends AbstractJUnit4SpringContextTests { @Resource(name="userService") private UserService userService; @Test public void test1(){ userService.say(); System.out.println(); userService.smile(); System.out.println(); userService.cry(); } } 此单元测试基于spring的AbstractJUnit4SpringContextTests,你需要添加spring的关于单元测试的支持 在类上标注@ContextConfiguration(locations="classpath:applicationContext.xml")意思是去classpath路径下加载applicationContext.xml @Resource(name="userService")意思是把userService注入进来 最终输出结果为: ----------before------------- say method is called ----------after------------- ----------before------------- smile method is called ----------after------------- ----------before------------- cry method is called ----------after-------------

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值