Spring 揭秘之Spring AOP一世(1)概念实现

Spring AOP一世

在上篇文章Spring AOP概述及其实现机制中,我们知道了Spring AOP建立在动态代理和CGLIB的支持之上;在这篇文章里,我们将介绍Spring AOP的底层实现机制;或许Spring AOP的概念实体会进化,但是其低层级制却是稳定的;

Spring AOP中的Joinpoint

AOP中的Joinpoint有很多类型,比如字段的获取与设置、构造方法、类初始化、方法调用、方法执行等;而Spring AOP秉承KISS原则,仅支持方法级别的Joinpoint;这已经可以满足80%的开发需求啦;Spring这么选择有以下几个原因:

  1. KISS原则:Keep It Simple,Stupid;当然可以选择100%实现AOP中的概念,但是这样就会导致框架过于臃肿;事倍功半并不是我们想要的结果;
  2. 字段级别的Joinpoint完全可以通过方法级别的Joinpoint来代替,同时对OOP封装的破坏性也较小;
  3. Spring 提供了对其他AOP实现的支持,比如AspectJ。如果Spring AOP无法满足业务需求,不妨使用AspectJ;

Spring AOP中的Pointcut

Pointcut就是Spring AOP中所有Pointcut的最顶层抽象啦,它定义两个方法用于捕捉系统中的Joinpoint;并且提供了TruePointcut类型的实例,该实例会对系统中所有的对象上的所有被支持的Joinpoint进行匹配;

public interface Pointcut {

	/**
	 * Return the ClassFilter for this pointcut.
	 * @return the ClassFilter (never {@code null})
	 */
	ClassFilter getClassFilter();

	/**
	 * Return the MethodMatcher for this pointcut.
	 * @return the MethodMatcher (never {@code null})
	 */
	MethodMatcher getMethodMatcher();

	/**
	 * Canonical Pointcut instance that always matches.
	 */
	Pointcut TRUE = TruePointcut.INSTANCE;

}

和Pointcut关系紧密的有ClassFilter和MethodMatcher:这两个接口分别负责在类层面和方法层面对系统对象进行筛选,从而确定Joinpoint;

ClassFilter进行类级别的筛选;MethodMatcher则进行方法级别的筛选;将其分开有利于筛选逻辑的重用;

ClassFilter和MethodMatcher和Pointcut相同的一点是,都提供了一个True实例,表示都ok;

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


	/**
	 * Canonical instance of a ClassFilter that matches all classes.
	 */
	ClassFilter TRUE = TrueClassFilter.INSTANCE;

}
public interface MethodMatcher {

	/**
	 * Perform static checking whether the given method matches.
	 * <p>If this returns {@code false} or if the {@link #isRuntime()}
	 * method returns {@code false}, no runtime check (i.e. no
	 * {@link #matches(java.lang.reflect.Method, Class, Object[])} call)
	 * will be made.
	 * @param method the candidate method
	 * @param targetClass the target class
	 * @return whether or not this method matches statically
	 */
	boolean matches(Method method, Class<?> targetClass);

	/**
	 * Is this MethodMatcher dynamic, that is, must a final call be made on the
	 * {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
	 * runtime even if the 2-arg matches method returns {@code true}?
	 * <p>Can be invoked when an AOP proxy is created, and need not be invoked
	 * again before each method invocation,
	 * @return whether or not a runtime match via the 3-arg
	 * {@link #matches(java.lang.reflect.Method, Class, Object[])} method
	 * is required if static matching passed
	 */
	boolean isRuntime();

	/**
	 * Check whether there a runtime (dynamic) match for this method,
	 * which must have matched statically.
	 * <p>This method is invoked only if the 2-arg matches method returns
	 * {@code true} for the given method and target class, and if the
	 * {@link #isRuntime()} method returns {@code true}. Invoked
	 * immediately before potential running of the advice, after any
	 * advice earlier in the advice chain has run.
	 * @param method the candidate method
	 * @param targetClass the target class
	 * @param args arguments to the method
	 * @return whether there's a runtime match
	 * @see MethodMatcher#matches(Method, Class)
	 */
	boolean matches(Method method, Class<?> targetClass, Object... args);


	/**
	 * Canonical instance that matches all methods.
	 */
	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

ClassFilter逻辑较为简单,MethodMatcher相对来说就复杂一些;其提供了两个检测函数——两个参数和三个参数;第三个不同的参数为方法调用的参数数组;只有在isRuntime()方法返回true时,才会调用三个参数的方法进行运行时的判断;

前面我们提到的MethodMatcher的True实例中,isRuntime()返回false,而二个参数的matches方法则返回true;

按照是否进行三参数matches方法调用,Spring中的MethodMatcher分为两类:StaticMethodMatcher和DynamicMethodMatcher:
在这里插入图片描述

常见的Pointcut

  1. NameMatchMethodPointcut:使用静态MethodMatcher,通过方法名进行筛选,可使用通配符*进行简单的模糊匹配;最简单实现;

  2. JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut:使用静态MethodMatcher,通过正则表达式进行筛选;

  3. AnnotationMatchingPointcut:根据待检测对象中是否有指定类型的注解来匹配Joinpoint;该Pointcut根据传入的注解类型不同,有着不同的匹配规则:

    1. 只传入类级别的注解:拥有该注解的类的所有方法均匹配该Pointcut;忽略方法签名;
    2. 只传入方法级别的注解:拥有该注解的方法均匹配该Pointcut,忽略对象的类型;
    3. 同时传入:精确匹配,首先待检测对象所属的类拥有对应的类注解,同时该类中只有拥有对应方法注解的方法才匹配该Pointcut
  4. ComposablePointcut:之前文章里提到过Pointcut可以进行运算;而ComposablePointcut就是Spring AOP对其的实现;它可以进行交和并运算;该类不仅可以进行Pointcut之间的运算,还可以进行ClassFilter和MethodMatcher的运算;

  5. ControlFlowPointcut:Spring AOP中最复杂的Pointcut类型,它不是对方法进行匹配,而是对方法调用的流程进行匹配;

    它只在类C中对方法M进行调用时,对M进行匹配;其他上下文中对M的调用将不会使M匹配该Pointcut;当然,我们在指定C的同时,也可以传入方法名N,于是只有C中的方法N调用M时,才会命中M,即匹配Pointcut;

扩展Pointcut

Pointcut根据MethodMatcher的类别(静态还是动态),也可以分为两类,这在上面的图中是可以看到的;

扩展StaticMethodMatcherPointcut

该类别的Pointcut默认使用ClassFilter.TRUT作为其获取ClassFilter的返回值;如果需要对目标对象的类别进行限制,也可以通过Setter方法指定ClassFilter;通常只需要实现两参数的matches方法即可;

扩展DynamicMethodMatcherPointcut

该类别的Pointcut的getClassFilter方法默认返回ClassFilter.TRUE;如果要对目标对象的类别进行约束,可以重写该方法即可;

同时isRuntime和两个参数的matches方法均返回true;通常只需要实现三参数的matches方法即可;

Spring AOP中的Advice

Spring AOP加入了开源组织AOP Alliance,目的在于标准化AOP的使用,促进各种AOP实现之间的可交互性;所以Spring中的Advice实现全部遵循AOP Alliance规定的接口;

Advice封装了横切逻辑;Spring AOP中,Advice按照其能否在目标对象类的所有实例中共享,分为两大类:per-class和per-instance;
在这里插入图片描述

per-class类型的Advice

per-class类型的Advice一般不会为目标对象保存任何状态或者添加新的属性,因为它要在所有目标对象之间共享;

上图中的所有Advice均属于per-class类型;上图中未出现的Introduction则不属于该类型;

Before Advice

Before Advice所表示的横切逻辑将在Joinpoint表示的方法之前执行,通常不会打断Joinpoint的执行,当然,如果有必要,也可以通过抛出异常来中断程序执行流程;

要实现Before Advice,在Spring中只需要实现MethodBeforeAdvice接口即可;

public interface MethodBeforeAdvice extends BeforeAdvice {
	void before(Method method, Object[] args, Object target) throws Throwable;
}
public interface BeforeAdvice extends Advice {
}
public interface Advice {
}

BeforeAdvice和Advice都是标记接口;该类型的Advice常用来做初始化或者其他准备工作;

ThrowsAdvice

对应AOP中AfterThrowingAdvice,虽然该接口没有定义任何方法,但是在实现该接口时,方法需要满足以下规则:

void afterThrowing([Method, args, target], ThrowableSubclass);

如:

public void afterThrowing(Exception ex){}
public void afterThrowing(RemoteException){}
public void afterThrowing(Method method, Object[] args, Object target, Exception ex){}
public void afterThrowing(Method method, Object[] args, Object target, ServletException ex){}

框架通过反射调用这些方法;该类型的Advice通常用于系统中特定异常的监控,并提供统一的异常处理方式;

AfterReturningAdvice

方法在正常返回的情况下,AfterReturningAdvice才会执行;Spring的AfterReturningAdvice可以访问到返回值,但是无法更改返回值;这点需要特别注意;

Around Advice

Spring AOP没有提供After Finally Advice,并且AfterReturningAdvice对于返回值无法做过多的干涉,所以类似的功能就要交给Around Advice来实现了;Spring AOP没有直接定义Around Advice,而是直接采用AOP Alliance标准接口即:MethodInterceptor。

public interface MethodInterceptor extends Interceptor {
	Object invoke(MethodInvocation invocation) throws Throwable;
}

通过MethodInvocation类,我们可以控制对Joinpoint的拦截行为——调用proceed()方法则继续执行该Joinpoint上的其他MethodInterceptor,否则中断执行;

per-instance类型的Advice

per-instance类型的Advice将为每个目标对象保存各自的状态和相关逻辑;Spring AOP中唯一的per-instance类型的Advice就是Introduction;

Spring中,为目标对象添加新的行为和属性,就必须声明相应的接口及其实现,然后通过特定的AroundAdvice——IntroductionInterceptor将新的逻辑附加到目标对象上,完成对目标对象的增强;
在这里插入图片描述

Introduction总体上分为两支,一支是以DynamicIntroductionAdvice为首的动态分支,一支是以IntroductionInfo为首的静态可配置分支;区别在于判断Introduction是否可应用到目标接口的时机——编译时或者运行时;

来看看两个实现类吧~

DelegatingIntroductionInterceptor
public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport
		implements IntroductionInterceptor {
	@Nullable
	private Object delegate;

	public DelegatingIntroductionInterceptor(Object delegate) {
		init(delegate);
	}

	protected DelegatingIntroductionInterceptor() {
		init(this);
	}


	/**
	 * Both constructors use this init method, as it is impossible to pass
	 * a "this" reference from one constructor to another.
	 * @param delegate the delegate object
	 */
	private void init(Object delegate) {
		Assert.notNull(delegate, "Delegate must not be null");
		this.delegate = delegate;
		implementInterfacesOnObject(delegate);

		// We don't want to expose the control interface
		suppressInterface(IntroductionInterceptor.class);
		suppressInterface(DynamicIntroductionAdvice.class);
	}


	/**
	 * Subclasses may need to override this if they want to perform custom
	 * behaviour in around advice. However, subclasses should invoke this
	 * method, which handles introduced interfaces and forwarding to the target.
	 */
	@Override
	@Nullable
	public Object invoke(MethodInvocation mi) throws Throwable {
		if (isMethodOnIntroducedInterface(mi)) {
			// Using the following method rather than direct reflection, we
			// get correct handling of InvocationTargetException
			// if the introduced method throws an exception.
			Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());

			// Massage return value if possible: if the delegate returned itself,
			// we really want to return the proxy.
			if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
				Object proxy = ((ProxyMethodInvocation) mi).getProxy();
				if (mi.getMethod().getReturnType().isInstance(proxy)) {
					retVal = proxy;
				}
			}
			return retVal;
		}

		return doProceed(mi);
	}

	/**
	 * Proceed with the supplied {@link org.aopalliance.intercept.MethodInterceptor}.
	 * Subclasses can override this method to intercept method invocations on the
	 * target object which is useful when an introduction needs to monitor the object
	 * that it is introduced into. This method is <strong>never</strong> called for
	 * {@link MethodInvocation MethodInvocations} on the introduced interfaces.
	 */
	protected Object doProceed(MethodInvocation mi) throws Throwable {
		// If we get here, just pass the invocation on.
		return mi.proceed();
	}

}

从其Invoke方法中,就可以看出DelegatingIntroductionInterceptor实际上是一个伪军:因为对于所有目标对象,它都使用一个delegate实例;如果真正要实现per-instance的诺言,就要使用它的兄弟:

DelegatePerTargetObjectIntroductionInterceptor

该类内部持有一个目标对象和相应Introduction逻辑实现类之间的映射关系。当目标对象上的接口方法被调用时,该类就会拦截这些方法,然后找到对应的横切逻辑,之后就没有什么区别啦;

最后,Spring AOP中的Introduction采用动态代理的方式实现,所以在性能上同AspectJ通过编译器将Introduction织入目标对象要逊色不少;

Spring AOP中的Aspect

有了Pointcut和Advice,就可以组装Aspect啦;然鹅,Spring中并没有完全明确的Aspect的概念;但是它还是有相同功能的概念的:Advisor;

Advisor与Aspect不同,因为Spring AOP中,Advisor通常只持有一个Pointcut和一个Advice;而理论上,一个Aspect可以有多个Pointcut和多个Advice

首先来看Advisor的体系结构:
在这里插入图片描述

注意!这里,名词组合出现的情况变得更为普遍!请时刻注意:Spring AOP中,IntroductionInterceptor是Introduction(Advice的一种)的实现,;Interceptor是Around Advice的实现;Advisor是Aspect的实现;

PointcutAdvisor家族

首先上图:
在这里插入图片描述

PointcutAdvisor才是真正的一个Pointcut和一个Advice的Advisor;以下简单介绍各个Advisor:

  1. DefaultPointcutAdvisor:PointcutAdvisor最为常用的实现;不接受Introduction类型的Advice,其余任何类型的Pointcut和Advice均可接受;
  2. NameMatchMethodPointcutAdvisor:限定使用NameMatchMethodPointcut,并且外部不可更改;不接受Introduction类型的Advice,其余均可;
  3. RegexpMethodPointcutAdvisor:同上,限定使用正则表达式的Pointcut。默认使用JdkRegexpMethodPoincut;可以通过setPerl5(boolean)进行转换;
  4. DefaultBeanFactoryPointcutAdvisor:将自身绑定到BeanFactory之上,通过beanName的方式寻找关联的Advice;只有Pointcut匹配成功后才去实例化对应的Advice,减少容器启动初期Advisor和Advice之间的耦合;

IntroductionAdvisor分支

该类别与PointcutAdvisor最大的区别在于,IntroductionAdvisor只能用于类级别的拦截,只能使用Introduction类型的Advisor;其类层次比较简单:
在这里插入图片描述

只有DefaultIntroductionAdvisor这一默认实现;

我们需要关注的也就不多啦;

Ordered的作用

系统中存在多个横切关注点,也会存在多个Advisor;当一个Joinpoint命中多个Advisor时,就需要留意或者控制这些Advisor的执行顺序;特别是这些Advisor需要按照一定顺序执行时,否则系统就会出现不可控的情况;

Spring在处理同一Joinpoint处的多个Advisor时,会按照指定的顺序来执行它们;顺序号越小,优先级越高;优先级越高,将越早执行;

问题来了,顺序号如何指定呢?默认情况下,配置文件中先声明的Advisor顺序号比较小;

当然,最为彻底的解决方法是为每个Advisor指定顺序号(一般从0或者1开始,因为小于0的编号一般是Spring内部使用)——即实现Ordered接口;

小结

这一节中,我们了解了Spring AOP对AOP的各种概念的实现;它们是整个框架的基础;体现了Spring框架的稳定性和灵活性;

接下来,我们将看看Spring AOP的织入过程;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值