前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring读源码系列之AOP--01---aop基本概念扫盲---上

Spring读源码系列之AOP--01---aop基本概念扫盲---上

作者头像
大忽悠爱学习
发布2022-05-10 16:38:35
7240
发布2022-05-10 16:38:35
举报
文章被收录于专栏:c++与qt学习

Spring读源码系列之AOP--01---aop基本概念扫盲--上


AOP相关概念

  • 切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。切面用spring的Advisor或拦截器实现。
  • 连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
  • 通知(Advice):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice:BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice
  • 切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解,MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上
  • 引入(Introduction): 添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。Spring中要使用Introduction,可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口(使用较少)
  • 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO
  • AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
  • 织入(Weaving):组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

这里有一个非常形象的比喻:

  • Aspect就是找茬(pointcut)揍你(target)一顿(advice),一群欠揍的的人(join point)

Pointcut

notice !!!

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

  • 一个是:org.aspectj.lang.reflect.Pointcut,它是aspectj内部使用的。它只有一个实现类PointcutImpl。是它内部的抽象
  • 另一个是:org.springframework.aop.Pointcut,这是Spring AOP体系中对切点的顶层抽象,贯穿整个AOP过程,非常重要。

Pointcut继承体系

ExpressionPointcut,它是用于解析String类型的切点表达式的接口,这个很重要,一会重点分析。


Pointcut分析

代码语言:javascript
复制
public interface Pointcut {

	/**
	 类过滤器
	 */
	ClassFilter getClassFilter();

	/**
   方法级别的过滤
	 */
	MethodMatcher getMethodMatcher();


	/**
	 始终匹配的规范切入点实例
	 */
	Pointcut TRUE = TruePointcut.INSTANCE;

}

主要负责对系统的相应的Joinpoint进行捕捉,对系统中所有的对象进行Joinpoint所定义的规则进行匹配。

提供了一个TruePointcut实例,当Pointcut为TruePointcut类型时,则会忽略所有的匹配条件,永远返回true。

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

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


ClassFilter—类过滤器
代码语言:javascript
复制
@FunctionalInterface
public interface ClassFilter {

	/**
	  pointcut是否应该应用于给定的接口或目标类?
	 */
	boolean matches(Class<?> clazz);


	/**
	 所有情况下都返回true
	 */
	ClassFilter TRUE = TrueClassFilter.INSTANCE;

}

我们可以来看一下它的继承体系:


RootClassFilter
代码语言:javascript
复制
public class RootClassFilter implements ClassFilter, Serializable {

	private final Class<?> clazz;


	public RootClassFilter(Class<?> clazz) {
		Assert.notNull(clazz, "Class must not be null");
		this.clazz = clazz;
	}
    
    //过滤条件: 当前类是clazz的子类或者接受clazz类,那么返回true
	@Override
	public boolean matches(Class<?> candidate) {
		return this.clazz.isAssignableFrom(candidate);
	}

...省略toString等方法
}

AnnotationClassFilter
代码语言:javascript
复制
public class AnnotationClassFilter implements ClassFilter {

	private final Class<? extends Annotation> annotationType;

	private final boolean checkInherited;

// 默认情况下checkInherited给的false:不去看它继承过来的注解
	public AnnotationClassFilter(Class<? extends Annotation> annotationType) {
		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 ? 
//继承的注解也会找出来
AnnotatedElementUtils.hasAnnotation(clazz, this.annotationType) :
//只会看自己本类的注解
				clazz.isAnnotationPresent(this.annotationType));
	}
	
...省略toString等方法
}

MethodMatcher—方法匹配器
代码语言:javascript
复制
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;

}

它有两个非常重要的抽象实现:StaticMethodMatcher和DynamicMethodMatcher


StaticMethodMatcher—静态匹配
代码语言:javascript
复制
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 动态匹配
代码语言:javascript
复制
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----基于正则过滤

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

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

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

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

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

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


我们来看一下match匹配过程的核心逻辑:

match方法在抽象父类AbstractRegexpMethodPointcut中实现:

代码语言:javascript
复制
@Override
	public boolean matches(Method method, Class<?> targetClass) {
		//先搞到当前方法的名字,然后传入matchesPattern方法中进行匹配,看是否符合切入的正则表达式
		return (matchesPattern(ClassUtils.getQualifiedMethodName(method, targetClass)) ||
				(targetClass != method.getDeclaringClass() &&
						matchesPattern(ClassUtils.getQualifiedMethodName(method, method.getDeclaringClass()))));
	}

方法名必须满足正则数组中所有的正则表达式并且当前方法名不满足排除某个方法的正则数组里面所有正则表达式的匹配,才会返回true

代码语言:javascript
复制
	protected boolean matchesPattern(String signatureString) {
		for (int i = 0; i < this.patterns.length; i++) {
		//match方法由子类实现
			boolean matched = matches(signatureString, i);
			if (matched) {
				for (int j = 0; j < this.excludedPatterns.length; j++) {
					//matchesExclusion方法也由子类实现
					boolean excluded = matchesExclusion(signatureString, j);
					if (excluded) {
						return false;
					}
				}
				return true;
			}
		}
		return false;
	}

JdkRegexpMethodPointcut中对match和matchesExclusion方法的实现

代码语言:javascript
复制
	@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();
	}

使用演示
代码语言:javascript
复制
<!-- 自己书写的日志切面 -->
<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,这样配置起来非常的方便

代码语言:javascript
复制
	<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代码来实现一遍:

代码语言:javascript
复制
    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—基于切点表达式过滤

切点表达式依赖于AspectJ的jar包去解析的 Spring在使用@Aspect注解时,会大量的用到它

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

代码语言:javascript
复制
    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方式

代码语言:javascript
复制
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配置文件:

代码语言:javascript
复制
<?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配置类,让它生效

代码语言:javascript
复制
...
@Configuration
@ImportResource(locations = "classpath:spring.xml")
public class RootConfig { ... }

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

代码语言:javascript
复制
@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

AspectJExpressionPointcut源码分析
代码语言:javascript
复制
// 很容易发现,自己即是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);
		}
	}

}

这里没有贴出所有源码,而是贴出了重要的一些代码,主要就是先拿到外部传入的切点表达式,然后创建切点表达式解析器,然后通过aspectJ内置的这个解析器进行解析,判断是否符合切入条件


NameMatchMethodPointcut—基于方法名过滤

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

代码语言:javascript
复制
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

NameMatchMethodPointcut源码分析
代码语言:javascript
复制
public class NameMatchMethodPointcut extends StaticMethodMatcherPointcut implements Serializable {
   //mappedNames是当前Pointcut选定切入的方法集合
	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;
	}

	/**
如果给定的方法名称与映射的名称匹配,则返回。
默认实现检查“xxx”、“*xxx”和“*xxx*”匹配,以及直接相等。可以在子类中覆盖。
	 */
	protected boolean isMatch(String methodName, String mappedName) {
		return PatternMatchUtils.simpleMatch(mappedName, methodName);
	}
....
}

ControlFlowPointCut—流程切入点

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

代码语言:javascript
复制
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倍,追求高性能的要慎重使用


ControlFlowPointCut源码分析
代码语言:javascript
复制
public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher, Serializable {

	private final Class<?> clazz;

	@Nullable
	private final String methodName;

	private final AtomicInteger evaluations = new AtomicInteger();


	/**
	当执行到该类下时,才有可能会被成为切入时机
	 */
	public ControlFlowPointcut(Class<?> clazz) {
		this(clazz, null);
	}

	/**
当目标对象的方法在指定类指定的方法中被调用时,才符合切入时机
	 */
	public ControlFlowPointcut(Class<?> clazz, @Nullable String methodName) {
		Assert.notNull(clazz, "Class must not be null");
		this.clazz = clazz;
		this.methodName = methodName;
	}


	@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();
        //StackTraceElement可以拿到堆栈信息  
		for (StackTraceElement element : new Throwable().getStackTrace()) {
			//如果当前方法执行到了clazz类下的method方法时,才会选择进行切入
			if (element.getClassName().equals(this.clazz.getName()) &&
					(this.methodName == null || element.getMethodName().equals(this.methodName))) {
				return true;
			}
		}
		return false;
	}
	
	public int getEvaluations() {
		return this.evaluations.get();
	}


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

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

ComposablePointcut 组合切入点

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

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

代码语言:javascript
复制
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 源码分析
代码语言:javascript
复制
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

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

代码语言:javascript
复制
//仅指定类级别的注解, 标注了 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);  

Advice

Spring的advice的大体分类如下:

Advice实现了将被织入到Pointcut规定的JointPoint处的横切逻辑,在Spring中,Advice按照其自身实例能否在目标对象类的所有实例中共享这一标准,可以划分为两大类: per-class类型和per-instance类型

per-class类型是指,该类型的Advice的实例可以在目标对象类的所有实例之间共享,这种类型的Advice通常只是提供方法拦截功能,不会为目标对象类保存任何状态或者添加新的特性。

上图没有列出Introuction类型的Advice不属于per-class类型的Advice之外:


MethodBeforeAdvice----前置逻辑增强

代码语言:javascript
复制
public interface MethodBeforeAdvice extends BeforeAdvice {

	/**
	 方法被调用前执行
	 */
	void before(Method method, Object[] args, @Nullable Object target) throws Throwable;

}

AspectJMethodBeforeAdvice----@Before

该advice针对是@Before注解的方法:

代码语言:javascript
复制
@SuppressWarnings("serial")
public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice, Serializable {

	public AspectJMethodBeforeAdvice(
			Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

		super(aspectJBeforeAdviceMethod, pointcut, aif);
	}

    //当前正在执行的方法,方法参数和目标对象
	@Override
	public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
	//触发before advice方法增强逻辑
		invokeAdviceMethod(getJoinPointMatch(), null, null);
	}

	@Override
	public boolean isBeforeAdvice() {
		return true;
	}

	@Override
	public boolean isAfterAdvice() {
		return false;
	}

}

MethodBeforeAdviceInterceptor—拦截方法,织入前置增强逻辑

代码语言:javascript
复制
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
    //前置增强逻辑
	private final MethodBeforeAdvice advice;

	public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}

   //MethodInvocation维护一组MethodInterceptor,然后依次递归调用
	@Override
	@Nullable
	public Object invoke(MethodInvocation mi) throws Throwable {
	//调用内部维护的advice,并触发其前置增强方法
		this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
		//调用链继续往后执行
		return mi.proceed();
	}

}

spring会把所有的Advice最终都转变为Interceptor,Interceptor内部维护一个转换前的advice对象


AspectJAroundAdvice

代码语言:javascript
复制
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {

	public AspectJAroundAdvice(
			Method aspectJAroundAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

		super(aspectJAroundAdviceMethod, pointcut, aif);
	}


	@Override
	public boolean isBeforeAdvice() {
		return false;
	}

	@Override
	public boolean isAfterAdvice() {
		return false;
	}

	@Override
	protected boolean supportsProceedingJoinPoint() {
		return true;
	}

	@Override
	@Nullable
	public Object invoke(MethodInvocation mi) throws Throwable {
		if (!(mi instanceof ProxyMethodInvocation)) {
			throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
		}
		ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
		//获取到ProceedingJoinPoint---当前连接点的信息封装
		ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
		//要求 ShadowMatch 在给定连接点匹配的结果。
		JoinPointMatch jpm = getJoinPointMatch(pmi);
		//触发advice增强逻辑---相当于把方法的执行权移交到了用户手上
		return invokeAdviceMethod(pjp, jpm, null, null);
	}

	/**
返回当前调用的 ProceedingJoinPoint,如果它还没有绑定到线程,则延迟实例化它。
	 */
	protected ProceedingJoinPoint lazyGetProceedingJoinPoint(ProxyMethodInvocation rmi) {
		return new MethodInvocationProceedingJoinPoint(rmi);
	}

}

AfterAdvice----后置增强逻辑

代码语言:javascript
复制
public interface AfterAdvice extends Advice {

}

AspectJAfterAdvice—后置增强逻辑加方法拦截器

AspectJAfterAdvice 同时实现了AfterAdvice和MethodInterceptor,那么可以认为MethodInterceptor内部拥有的Advice实例就是他自己。

代码语言:javascript
复制
public class AspectJAfterAdvice extends AbstractAspectJAdvice
		implements MethodInterceptor, AfterAdvice, Serializable {

	public AspectJAfterAdvice(
			Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

		super(aspectJBeforeAdviceMethod, pointcut, aif);
	}


	@Override
	@Nullable
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
		//调用链继续往后执行
			return mi.proceed();
		}
		finally {
		//方法执行结束后,调用afterAdvice方法
			invokeAdviceMethod(getJoinPointMatch(), null, null);
		}
	}

	@Override
	public boolean isBeforeAdvice() {
		return false;
	}

	@Override
	public boolean isAfterAdvice() {
		return true;
	}

}

AspectJAfterReturningAdvice

代码语言:javascript
复制
public class AspectJAfterReturningAdvice extends AbstractAspectJAdvice
		implements AfterReturningAdvice, AfterAdvice, Serializable {

	public AspectJAfterReturningAdvice(
			Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

		super(aspectJBeforeAdviceMethod, pointcut, aif);
	}


	@Override
	public boolean isBeforeAdvice() {
		return false;
	}

	@Override
	public boolean isAfterAdvice() {
		return true;
	}

	@Override
	public void setReturningName(String name) {
		setReturningNameNoCheck(name);
	}

	@Override
	public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
	//是否应该执行afterReturning的织入逻辑
		if (shouldInvokeOnReturnValueOf(method, returnValue)) {
		//执行advice
			invokeAdviceMethod(getJoinPointMatch(), returnValue, null);
		}
	}
	....
}

AfterReturningAdviceInterceptor

代码语言:javascript
复制
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
   //内部维护一个增强器
	private final AfterReturningAdvice advice;
	
	public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
		Assert.notNull(advice, "Advice must not be null");
		this.advice = advice;
	}


	@Override
	@Nullable
	public Object invoke(MethodInvocation mi) throws Throwable {
	//调用链继续往后执行
		Object retVal = mi.proceed();
	//再执行	afterReturning增强逻辑
		this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
	//返回结果
		return retVal;
	}

}

AspectJAfterThrowingAdvice

代码语言:javascript
复制
public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice
		implements MethodInterceptor, AfterAdvice, Serializable {

	public AspectJAfterThrowingAdvice(
			Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

		super(aspectJBeforeAdviceMethod, pointcut, aif);
	}


	@Override
	public boolean isBeforeAdvice() {
		return false;
	}

	@Override
	public boolean isAfterAdvice() {
		return true;
	}

	@Override
	public void setThrowingName(String name) {
		setThrowingNameNoCheck(name);
	}

	@Override
	@Nullable
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
		//调用链继续往后执行
			return mi.proceed();
		}
		catch (Throwable ex) {
		//如果出现异常,决定是否要执行出现异常时的增强逻辑
			if (shouldInvokeOnThrowing(ex)) {
				invokeAdviceMethod(getJoinPointMatch(), null, ex);
			}
			throw ex;
		}
	}

	/**
在 AspectJ 语义中,仅当抛出的异常是给定抛出类型的子类型时,才会调用指定抛出子句的抛出后通知。
	 */
	private boolean shouldInvokeOnThrowing(Throwable ex) {
		return getDiscoveredThrowingType().isAssignableFrom(ex.getClass());
	}

}

invokeAdviceMethod方法源码简单剖析

所有的通知类最终通知方法被调用都是走的invokeAdviceMethod函数,下面简单看一下这函数的执行过程

代码语言:javascript
复制
    //JoinPoint  :封装当前连接点的信息
	// JoinPointMatch :当前连接点的匹配结果
	//returnValue :方法返回值
	//Throwable : 连接点处方法抛出的异常
	protected Object invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch,
			@Nullable Object returnValue, @Nullable Throwable t) throws Throwable {
         //真正调用增强方法的地方,会传入参数数组
		return invokeAdviceMethodWithGivenArgs(argBinding(jp, jpMatch, returnValue, t));
	}

	protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
		Object[] actualArgs = args;
		if (this.aspectJAdviceMethod.getParameterCount() == 0) {
			actualArgs = null;
		}
		try {
			ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
			//getAspectInstance()获取到当前增强方法对应切面类的对象实例
			return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
		}
		catch (IllegalArgumentException ex) {
			throw new AopInvocationException("Mismatch on arguments to advice method [" +
					this.aspectJAdviceMethod + "]; pointcut expression [" +
					this.pointcut.getPointcutExpression() + "]", ex);
		}
		catch (InvocationTargetException ex) {
			throw ex.getTargetException();
		}
	}

JointPoint----连接点

org.aspectj.lang.JoinPoint

首先需要注意的是,一般我们会接触到两个Joinpoint

org.aspectj.lang.JoinPoint:该对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,可以很方便的获得更多信息。(一般用于@Aspect标注的切面的方法入参里),它的API很多,常用的有下面几个:

  1. Signature getSignature(); :封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
  2. Object[] getArgs();:传入目标方法的参数们
  3. Object getTarget();:被代理的对象(目标对象)
  4. Object getThis();:该代理对象

备注:ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中

ProceedingJoinPoint本类中只有两个公开方法:

代码语言:javascript
复制
   //继续下一个advice或目标方法调用
    public Object proceed() throws Throwable;
    public Object proceed(Object[] args) throws Throwable;

org.aopalliance.intercept.Joinpoint

org.aopalliance.intercept.Joinpoint是本文的重点,下面主要看看它的解释和相关方法:

代码语言:javascript
复制
// 此接口表示运行时的连接点(AOP术语)  (和aspectj里的连接点意思有点像)
public interface Joinpoint {

	// 执行此拦截点,并进入到下一个连接点
	Object proceed() throws Throwable;
	// 返回保存当前连接点静态部分【的对象】。  这里一般指的target
	Object getThis();
	// 返回此静态连接点  一般就为当前的Method(至少目前的唯一实现是MethodInvocation,所以连接点得静态部分肯定就是本方法喽)
	AccessibleObject getStaticPart();
}

org.aopalliance.intercept.Invocation

代码语言:javascript
复制
// 此接口表示程序中的调用~
// 该调用是一个可以被拦截器拦截的连接点
public interface Invocation extends Joinpoint {
	// 获得参数们。比如方法的入参们
	Object[] getArguments();
}

org.aopalliance.intercept.MethodInvocation

接口到了这一层,就比较具象了。它表示方法的执行器,显然就是和Method方法有关

代码语言:javascript
复制
// 方法调用时,对这部分进行描述
public interface MethodInvocation extends Invocation {
	// 返回正在被调用得方法~~~  返回的是当前Method对象。
	// 此时,效果同父类的AccessibleObject getStaticPart() 这个方法
	Method getMethod();
}

MethodInvocation作为aopalliance里提供的最底层接口了。Spring提供了相关的实现,如下图:

Spring自己也定义了一个接口,来进行扩展和统一管理:ProxyMethodInvocation


org.springframework.aop.ProxyMethodInvocation

这个接口是Spring提供的对aopalliance里MethodInvocation的继承扩展接口

代码语言:javascript
复制
// 这是Spring提供的对MethodInvocation 的一个扩展。
// 它允许访问  方法被调用的代理对象以及其它相关信息
public interface ProxyMethodInvocation extends MethodInvocation {
	
	// 返回代理对象
	Object getProxy();
	
	// 克隆一个,使用的Object得clone方法
	MethodInvocation invocableClone();
	MethodInvocation invocableClone(Object... arguments);

	// 设置参数  增强器、通知们执行的时候可能会用到
	void setArguments(Object... arguments);
	// 添加一些属性kv。这些kv并不会用于AOP框架内,而是保存下来给特殊的一些拦截器实用
	void setUserAttribute(String key, @Nullable Object value);
	@Nullable
	Object getUserAttribute(String key);
}

下面我们就是主菜了,Spring给我们提供的唯一(其实算唯二吧)的实现类,它执行着拦截的核心逻辑。会让所有的通知器都执行~~


ReflectiveMethodInvocation

显然它作为实现类,需要实现包括父接口在内的所有的方法们。

它也是JdkDynamicAopProxy最终执行时候new出来的执行对象,话不多说,下面看看具体的逻辑吧~~

代码语言:javascript
复制
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {

	protected final Object proxy; // 代理对象
	@Nullable
	protected final Object target; // 目标对象
	protected final Method method; // 被拦截的方法
    //方法参数
	protected Object[] arguments = new Object[0];
	@Nullable
	private final Class<?> targetClass;
    
	/**
	 * 懒加载的属性集合--因为拦截器链执行过程中,拦截器链只有一个实例,
	 * 但是如果我们需要在各个拦截器之间传递信息,就可以利用这个唯一实例提供的属性集合来完成
	 */
	@Nullable
	private Map<String, Object> userAttributes;
		/**
	 getInterceptorsAndDynamicInterceptionAdvice方法在为当前方法构建拦截器链的时候
	 如果PointcutAdvisor的methodMatcher的isRunTime返回true表示需要进行动态匹配
	 那么会将当前MethodInterceptor包装为一个InterceptorAndDynamicMethodMatcher加入要返回的拦截器集合中
     
     interceptorsAndDynamicMethodMatchers的赋值就是getInterceptorsAndDynamicInterceptionAdvice方法返回值
	 */
	protected final List<?> interceptorsAndDynamicMethodMatchers;
	
	// currentInterceptorIndex初始值为 -1 
	//当前拦截器的索引
	private int currentInterceptorIndex = -1;

	// 唯一的构造函数。注意是protected  相当于只能本包内、以及子类可以调用。外部是不能直接初始化的此对象的(显然就是Spring内部使用的类了嘛)
	//invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
	// proxy:代理对象
	// target:目标对象
	// method:被代理的方法
	// args:方法的参数们
	// targetClass:目标方法的Class (target != null ? target.getClass() : null)
	// interceptorsAndDynamicMethodMatchers:拦截链。  this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)这个方法找出来的
	protected ReflectiveMethodInvocation(
			Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments,
			@Nullable Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {

		this.proxy = proxy;
		this.target = target;
		this.targetClass = targetClass;
		// 找到桥接方法,作为最后执行的方法。至于什么是桥接方法,自行百度关键字:bridge method
		// 桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法(子类实现父类的泛型方法时会生成桥接方法)
		this.method = BridgeMethodResolver.findBridgedMethod(method);
		// 对参数进行适配
		this.arguments = AopProxyUtils.adaptArgumentsIfNecessary(method, arguments);
		this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
	}

	@Override
	public final Object getProxy() {
		return this.proxy;
	}
	@Override
	@Nullable
	public final Object getThis() {
		return this.target;
	}
	// 此处:getStaticPart返回的就是当前得method
	@Override
	public final AccessibleObject getStaticPart() {
		return this.method;
	}
	// 注意:这里返回的可能是桥接方法哦
	@Override
	public final Method getMethod() {
		return this.method;
	}
	@Override
	public final Object[] getArguments() {
		return this.arguments;
	}
	@Override
	public void setArguments(Object... arguments) {
		this.arguments = arguments;
	}


	// 这里就是核心了,要执行方法、执行通知、都是在此处搞定的
	// 这里面运用 递归调用 的方式,非常具有技巧性
	@Override
	@Nullable
	public Object proceed() throws Throwable {
		//	currentInterceptorIndex初始值为 -1  如果执行到链条的末尾 则直接调用连接点方法 即 直接调用目标方法
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			// 这个方法相当于调用了目标方法~~~下面会分析
			return invokeJoinpoint();
		}

		// 获取集合中的 MethodInterceptor(并且currentInterceptorIndex + 1了哦)
		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        //如果某个methodInterceptor被包装为了InterceptorAndDynamicMethodMatcher
        //说明它的methodMatcher的isRunTime返回值为true
		//InterceptorAndDynamicMethodMatcher它是Spring内部使用的一个类。很简单,就是把MethodInterceptor实例和MethodMatcher放在了一起。看看在advisor chain里面是否能够匹配上
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			
			// 去匹配这个拦截器是否适用于这个目标方法  试用就执行拦截器得invoke方法
			if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// 如果不匹配。就跳过此拦截器,而继续执行下一个拦截器
				// 注意:这里是递归调用  并不是循环调用
				return proceed();
			}
		}
		else {
			// 直接执行此拦截器。说明之前已经匹配好了,只有匹配上的方法才会被拦截进来的
			// 这里传入this就是传入了ReflectiveMethodInvocation,从而形成了一个链条了
			//我们会在拦截器内部,通过传入的拦截器对象,继续调用其proceed方法,完成递归调用
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}
	

	// 其实就是简单的一个:method.invoke(target, args);
	// 子类可以复写此方法,去执行。比如它的唯一子类CglibAopProxy内部类  CglibMethodInvocation就复写了这个方法  它对public的方法做了一个处理(public方法调用MethodProxy.invoke)
	@Nullable
	protected Object invokeJoinpoint() throws Throwable {
		// 此处传入的是target,而不能是proxy,否则进入死循环
		//invokeJoinpointUsingReflection直接返回调用目标方法
		return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
	}
...非重要方法省略
}

从这里我们需要注意到的是:ProxyMethodInvocation(ReflectiveMethodInvocation)是代理执行的入口。然后内部会把所有的 增强器 都拿出来 递归执行(比如前置通知,就在目标方法之前执行)这就实现了指定次序的链式调用


CglibMethodInvocation

它是继承自ReflectiveMethodInvocation,是CglibAopProxy自己使用的执行器。

代码语言:javascript
复制
	private static class CglibMethodInvocation extends ReflectiveMethodInvocation {

		private final MethodProxy methodProxy;

		private final boolean publicMethod;

		public CglibMethodInvocation(Object proxy, @Nullable Object target, Method method,
				Object[] arguments, @Nullable Class<?> targetClass,
				List<Object> interceptorsAndDynamicMethodMatchers, MethodProxy methodProxy) {
			
			// 调用父类的构造  完成基本参数得初始化
			super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
			
			// 自己的个性化参数:
			
			// 这个参数是子类多传的,表示:它是CGLIb拦截的时候的类MethodProxy
			//MethodProxy为生成的代理类对方法的代理引用。cglib生成用来代替Method对象的一个对象,使用MethodProxy比调用JDK自身的Method直接执行方法效率会有提升
			// 它有两个重要的方法:invoke和invokeSuper
			this.methodProxy = methodProxy;
			// 方法是否是public的  对应下面的invoke方法的处理 见下面
			this.publicMethod = Modifier.isPublic(method.getModifiers());
		}

		@Override
		protected Object invokeJoinpoint() throws Throwable {
			// 如果是public的方法,调用methodProxy去执行目标方法
			// 否则直接执行method即可
			if (this.publicMethod) {
				// 此处务必注意的是,传入的是target,而不能是proxy,否则进入死循环
				return this.methodProxy.invoke(this.target, this.arguments);
			} else {
				return super.invokeJoinpoint();
			}
		}
	}

Cglib如何通过methodProxy调用目标方法来节省资源消耗


org.aopalliance.intercept.MethodInterceptor

MethodInterceptor在上面Advice部分已经简单提过了,之所以没有单独放到Advice里面重点讲解是因为我想先讲MethodInvocation做知识铺垫,在来MethodInterceptor的讲解会更加简单一些

需要说明的cglib包里也存在一个MethodInterceptor,它的主要作用是CGLIB内部使用,一般是和Enhancer一起来使用而创建一个动态代理对象。

而本处我们讲到的 org.aopalliance.intercept.MethodInterceptor,那些@AspectJ定义的通知们(增强器们),或者是自己实现的MethodBeforeAdvice、AfterReturningAdvice…(总是都是org.aopalliance.aop.Advice一个通知器),最终都会被包装成一个org.aopalliance.intercept.MethodInterceptor,最终交给MethodInvocation(其子类ReflectiveMethodInvocation)去执行,它会把你所有的增强器都给执行了,这就是我们面向切面编程的核心思路过程。

这里面其实有两个标记接口(没有任何方法):

  • 顶层接口:Advice 中文意思:建议,忠告。 实现通知的方式可议是任何方式,比如Interceptors拦截器的方式
  • 中间层接口:Interceptor 继承自Advice接口。它就是以拦截器方式去实现通知的效果

此处需要说明的是,Interceptor得子接口有两个:MethodInterceptor和ConstructorInterceptor,但是ConstructorInterceptor连Spring都没有提供实现类,因此本文不会讲述本接口。

代码语言:javascript
复制
// 从名字里都能看出来,它是通过拦截方法的执行来实现通知得效果的~~~~
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
	// 可议在此方法里  在方法执行之前、之后做对应的处理。
	// 需要执行的时候,调用invocation.proceed()方法即可
	Object invoke(MethodInvocation invocation) throws Throwable;
}

Spring给我们提供的MethodInterceptor实现非常非常的多:

圈出来的上面都讲过了,不清楚的回看上面内容,但是我下面还想提一嘴拦截器的适配器


DefaultAdvisorAdapterRegistry ----advisor的适配器

代码语言:javascript
复制
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {

	private final List<AdvisorAdapter> adapters = new ArrayList<>(3);


	/**
	 * Create a new DefaultAdvisorAdapterRegistry, registering well-known adapters.
	 */
	public DefaultAdvisorAdapterRegistry() {
	//注册三个默认的适配器
		registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
		registerAdvisorAdapter(new AfterReturningAdviceAdapter());
		registerAdvisorAdapter(new ThrowsAdviceAdapter());
	}

//将传入的对象包装为Advisor返回
	@Override
	public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
		if (adviceObject instanceof Advisor) {
			return (Advisor) adviceObject;
		}
		if (!(adviceObject instanceof Advice)) {
			throw new UnknownAdviceTypeException(adviceObject);
		}
		Advice advice = (Advice) adviceObject;
		if (advice instanceof MethodInterceptor) {
			// So well-known it doesn't even need an adapter.
			return new DefaultPointcutAdvisor(advice);
		}
		for (AdvisorAdapter adapter : this.adapters) {
			// Check that it is supported.
			if (adapter.supportsAdvice(advice)) {
				return new DefaultPointcutAdvisor(advice);
			}
		}
		throw new UnknownAdviceTypeException(advice);
	}

//将当前Advisor转换为一组MethodInterceptor[]
	@Override
	public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
		List<MethodInterceptor> interceptors = new ArrayList<>(3);
		Advice advice = advisor.getAdvice();
		if (advice instanceof MethodInterceptor) {
			interceptors.add((MethodInterceptor) advice);
		}
		for (AdvisorAdapter adapter : this.adapters) {
			if (adapter.supportsAdvice(advice)) {
				interceptors.add(adapter.getInterceptor(advisor));
			}
		}
		if (interceptors.isEmpty()) {
			throw new UnknownAdviceTypeException(advisor.getAdvice());
		}
		return interceptors.toArray(new MethodInterceptor[0]);
	}

//注册一个AdvisorAdapter 
	@Override
	public void registerAdvisorAdapter(AdvisorAdapter adapter) {
		this.adapters.add(adapter);
	}

}

小结

aopalliance属于AOP联盟,定义了一些标准。一共只几个接口,总结如下:

org.aopalliance.aop包

  • Advice:通知的标记接口。实现可以是任意类型,比如下面的Interceptor
  • AspectException:所有的AOP框架产生异常的父类。它是个RuntimeException

org.aopalliance.intercept包

  • Interceptor:它继承自Advice,它通过拦截器得方式实现通知的效果(也属于标记接口)
  • MethodInterceptor:具体的接口。拦截方法 (Spring提供了非常多的具体实现类)
  • ConstructorInterceptor:具体接口。拦截构造器 (Spring并没有提供实现类)
  • Joinpoint:AOP运行时的连接点(顶层接口)
  • Invocation:继承自Joinpoint。 表示执行,提供了Object[] getArguments()来获取执行所需的参数
  • MethodInvocation:(和MethodInterceptor对应,它的invoke方法入参就是它)表示一个和方法有关的执行器。提供方法Method getMethod() (Spring提供了唯一(唯二)实现类:ProxyMethodInvocation)
  • ConstructorInvocation:和构造器有关。Constructor<?> getConstructor();(Spring没有提供任何实现类)

这就是AOP联盟为我们提供的所有的类,它里面全部是接口(那个异常类除外),相当于它定义了一套AOP的标准类。Spring对核心的Method相关的拦截、执行器做了对应的实现。

注意,Spring的AOP实现并不依赖于AspectJ任何类,它自己实现了一套AOP的。比如它Spring自己提供的BeforeAdvice和AfterAdvice都是对AOP联盟规范的标准实现。以及Spring自己抽象出来的对Advice的包装:org.springframework.aop.Advisor贯穿Spring AOP的始终

但是在当前注解驱动的流行下,基于POJO(xml方式)以及编程的方式去书写AOP代理,显得非常的繁琐。因此Spring提供了另外一种实现:基于AspectJ,到这才使用到了AspectJ的相关注解、以及类。

但是还需要说明一点:哪怕使用到了AspectJ的相关注解和类,但核心的AOP织入的逻辑,还都是Spring自己用动态代理去实现的,没用AspectJ它那种特殊的语法和特有的编译器

最后说一句,若在Spring AOP中想使用AspectJ的方式去实现(也是当下最流行的方式),必须导入Jar包:aspectjweaver-1.9.2.jar,而Spring的这个包org.springframework.aop.aspectj下面的所有类,都是专门为了使用@Aspect的方式去服务的,毕竟AOP功能是Spring自己实现的,而不是依赖于AspectJ这个组件的

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring读源码系列之AOP--01---aop基本概念扫盲--上
  • AOP相关概念
    • Pointcut
      • notice !!!
      • Pointcut继承体系
      • Pointcut分析
      • JdkRegexpMethodPointcut----基于正则过滤
      • AspectJExpressionPointcut—基于切点表达式过滤
      • NameMatchMethodPointcut—基于方法名过滤
      • ControlFlowPointCut—流程切入点
      • ComposablePointcut 组合切入点
      • AnnotationMatchingPointcut 注解切入点
  • Advice
    • MethodBeforeAdvice----前置逻辑增强
      • AspectJMethodBeforeAdvice----@Before
      • MethodBeforeAdviceInterceptor—拦截方法,织入前置增强逻辑
      • AspectJAroundAdvice
    • AfterAdvice----后置增强逻辑
      • AspectJAfterAdvice—后置增强逻辑加方法拦截器
      • AspectJAfterReturningAdvice
      • AfterReturningAdviceInterceptor
      • AspectJAfterThrowingAdvice
    • invokeAdviceMethod方法源码简单剖析
    • JointPoint----连接点
      • org.aspectj.lang.JoinPoint
        • org.aopalliance.intercept.Joinpoint
          • org.aopalliance.intercept.Invocation
            • org.aopalliance.intercept.MethodInvocation
        • org.aopalliance.intercept.MethodInterceptor
        • DefaultAdvisorAdapterRegistry ----advisor的适配器
        • 小结
        相关产品与服务
        云顾问
        云顾问(Tencent Cloud Smart Advisor)是一款提供可视化云架构IDE和多个ITOM领域垂直应用的云上治理平台,以“一个平台,多个应用”为产品理念,依托腾讯云海量运维专家经验,助您打造卓越架构,实现便捷、灵活的一站式云上治理。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档