白话Spring源码(九):Spring AOP原理和相关概念

距上次写完spring bean相关源码的博客已经过了很长时间了,主要是最近自己工作比较忙,但这不是借口还是要继续坚持写下去。好了,下面进入主题。bean相关的已经介绍完了,下面的一大块就是AOP相关的,我们知道AOP是Spring框架的第二个核心。在分析源码以前还是先介绍一下它的原理和里面的一些抽象概念(源码越读越觉得老外的抽象能力就是厉害)。

AOP全程是 Aspect Oriented Programming,即面向切面的编程。通过 AOP,我们可以把一些非业务逻辑的代码,比如安全检查,监控等代码从业务方法中抽取出来,以非侵入的方式与原方法进行协同。这样可以使原方法更专注于业务逻辑,代码结构会更加清晰,便于维护。

但是AOP并非Spring原创的,在spring之前就有一个叫AOP联盟,AOP联盟规范了一套用于规范AOP实现的底层API,通过这些统一的底层API,可以使得各个AOP实现及工具产品之间实现相互移植。而spring只是的 IOC 的基础之上,又实现了一套 AOP 的框架。

其实AOP的原理并不难就是:无非是通过代理模式为目标对象生产代理对象,并将横切逻辑插入到目标方法执行的前后。不过原理归原理,在具体的实现上,很多事情并没想象的那么简单,更何况还要跟IOC结合。

在学习Spring AOP之前我们必须先要理解一些概念,也就是抽象,不然读源码也是一头雾水。

1.连接点 - Joinpoint

程序里每个方法的调用都是连接点。我们怎么理解这句话呢,这里我们可以抽象出几个概念:调用,方法调用。我们直接看代码。

Joinpoint(连接点):

public interface Joinpoint {

   Object proceed() throws Throwable;

   Object getThis();

   AccessibleObject getStaticPart();   

}

Invocation(调用)

public interface Invocation extends Joinpoint {
   
   Object[] getArguments();

}

MethodInvocation(方法调用)

public interface MethodInvocation extends Invocation
{

    Method getMethod();

}

看完这三个接口正好解释了连接点,而且这三个接口并不了Spring里的,是我上面说的AOP联盟定义的标准,在aopalliance.jar里。

有了这些连接点,接下来要做的事情是对我们感兴趣连接点进行一些横切操作。在操作之前,我们首先要把我们所感兴趣的连接点选中,怎么选中的呢?这就是切点 Pointcut 要做的事情了,继续往下看。

2.切点 - Pointcut

刚刚说到切点是用于选择连接点的,那么应该怎么选呢?在回答这个问题前,我们不妨先去看看 Pointcut 接口的定义。如下:

public interface Pointcut {

	ClassFilter getClassFilter();
	
	MethodMatcher getMethodMatcher();
	
	// could add getFieldMatcher() without breaking most existing code
	
	
	/**
	 * Canonical instance that matches everything.
	 */
	Pointcut TRUE = new Pointcut() {

		public ClassFilter getClassFilter() {
			return ClassFilter.TRUE;
		}

		public MethodMatcher getMethodMatcher() {
			return MethodMatcher.TRUE;
		}

		public String toString() {
			return "Pointcut.TRUE";
		}
	};
	

}

Pointcut 接口中定义了两个接口,分别用于返回类型过滤器和方法匹配器。下面我们再来看一下类型过滤器和方法匹配器接口的定义:

ClassFilter

public interface ClassFilter {
	
	/**
	 * Should the pointcut apply to the given interface or target class?
	 * @param clazz candidate target class
	 * @return whether the advice should apply to this candidate target class
	 */
	boolean matches(Class clazz);


	/**
	 * Canonical instance of a ClassFilter that matches all classes.
	 */
	ClassFilter TRUE = new ClassFilter() {
		public boolean matches(Class clazz) {
			return true;
		}
	};

}

 MethodMatcher 

public interface MethodMatcher {
	
	boolean matches(Method m, Class targetClass);
	
	boolean isRuntime();
	
	boolean matches(Method m, Class targetClass, Object[] args);
	

	MethodMatcher TRUE = new MethodMatcher() {

		public boolean isRuntime() {
			return false;
		}

		public boolean matches(Method m, Class targetClass) {
			return true;
		}

		public boolean matches(Method m, Class targetClass, Object[] args) {
			// should never be invoked as isRuntime returns false
			throw new UnsupportedOperationException();
		}
	};

}

 上面的两个接口均定义了 matches 方法,用户只要实现了 matches 方法,即可对连接点进行选择。

3.通知 - Advice

通知 Advice 即我们定义的横切逻辑,比如我们可以定义一个用于监控方法性能的通知,也可以定义一个安全检查的通知等。如果说切点解决了通知在哪里调用的问题,那么现在还需要考虑了一个问题,即通知在何时被调用?是在目标方法前被调用,还是在目标方法返回后被调用,还在两者兼备呢?Spring 帮我们解答了这个问题,Spring 中定义了以下几种通知类型:

  • 前置通知(Before advice)- 在目标方便调用前执行通知
  • 后置通知(After advice)- 在目标方法完成后执行通知
  • 返回通知(After returning advice)- 在目标方法执行成功后,调用通知
  • 异常通知(After throwing advice)- 在目标方法抛出异常后,执行通知
  • 环绕通知(Around advice)- 在目标方法调用前后均可执行自定义逻辑

下面我们来看一下通知相关源码

Advice(通知)

public interface Advice {

}

 BeforeAdvice 

public interface BeforeAdvice extends Advice {

}

AfterReturningAdvice

public interface AfterReturningAdvice extends Advice {
	
	void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable;

}

Interceptor

public interface Interceptor extends Advice {
}

现在我们有了切点 Pointcut 和通知 Advice,由于这两个模块目前还是分离的,我们需要把它们整合在一起。这样切点就可以为通知进行导航,然后由通知逻辑实施精确打击。那怎么整合两个模块呢?答案是,切面。好的,是时候来介绍切面 Aspect 这个概念了

4.切面 - Aspect

切面 Aspect 整合了切点和通知两个模块,切点解决了 where 问题,通知解决了 when 和 how 问题。切面把两者整合起来,就可以解决 对什么方法(where)在何时(when - 前置还是后置,或者环绕)执行什么样的横切逻辑(how)的三连发问题。在 AOP 中,切面只是一个概念,并没有一个具体的接口或类与此对应。不过 Spring 中倒是有一个接口的用途和切面很像,我们不妨了解一下,这个接口就是切点通知器 PointcutAdvisor。我们先来看看这个接口的定义,如下:

Advisor 

public interface Advisor {
	
	boolean isPerInstance();
	
	Advice getAdvice();

}

PointcutAdvisor

public interface PointcutAdvisor extends Advisor {
	
	Pointcut getPointcut();

}

简单来说一下 PointcutAdvisor 及其父接口 Advisor,Advisor 中有一个 getAdvice 方法,用于返回通知。PointcutAdvisor 在 Advisor 基础上,新增了 getPointcut 方法,用于返回切点对象。因此 PointcutAdvisor 的实现类即可以返回切点,也可以返回通知,所以说 PointcutAdvisor 和切面的功能相似。不过他们之间还是有一些差异的,比如看下面的配置:

<bean id="aopCode" class="xyz.coolblog.aop.AopCode"/>
    
<aop:config expose-proxy="true">
    <aop:aspect ref="aopCode">
    	<!-- pointcut -->
        <aop:pointcut id="helloPointcut" expression="execution(* xyz.coolblog.aop.*.hello*(..))" />

        <!-- advoce -->
        <aop:before method="before" pointcut-ref="helloPointcut"/>
        <aop:after method="after" pointcut-ref="helloPointcut"/>
    </aop:aspect>
</aop:config>

如上,一个切面中配置了一个切点和两个通知,两个通知均引用了同一个切点,即 pointcut-ref=“helloPointcut”。这里在一个切面中,一个切点对应多个通知,是一对多的关系(可以配置多个 pointcut,形成多对多的关系)。而在 PointcutAdvisor 的实现类中,切点和通知是一一对应的关系。

5.织入 - Weaving

现在我们有了连接点、切点、通知,以及切面等,可谓万事俱备,但是还差了一股东风。这股东风是什么呢?没错,就是织入。所谓织入就是在切点的引导下,将通知逻辑插入到方法调用上,使得我们的通知逻辑在方法调用时得以执行。说完织入的概念,现在来说说 Spring 是通过何种方式将通知织入到目标方法上的。先来说说以何种方式进行织入,这个方式就是通过实现后置处理器 BeanPostProcessor 接口。该接口是 Spring 提供的一个拓展接口,通过实现该接口,用户可在 bean 初始化前后做一些自定义操作。那 Spring 是在何时进行织入操作的呢?答案是在 bean 初始化完成后,即 bean 执行完初始化方法(init-method)。Spring通过切点对 bean 类中的方法进行匹配。若匹配成功,则会为该 bean 生成代理对象,并将代理对象返回给容器。容器向后置处理器输入 bean 对象,得到 bean 对象的代理,这样就完成了织入过程。

好了,到这里介绍了AOP原理和相关概念,下面可以开始分析源码了。

猜你喜欢

转载自blog.csdn.net/haoxin963/article/details/88836828