搞懂AOP之二,引入增强

1,281 阅读7分钟

回顾

在上一篇搞懂AOP之一,拦截器链中,我们介绍了MethodInterceptor增强目标类的方式,了解到了AOP的拦截器是如何使用,以及多个拦截器组成的拦截器链是如何工作的,并且看到了如何使用ProxyFactory去创建代理类。本篇接上篇,接着介绍另外一种增强方式--引入增强。

了解引入增强

定义

spring中的引入增强对应IntroductionInterceptor类。看下spring对其的定义:

/**
 * Subinterface of AOP Alliance MethodInterceptor that allows additional interfaces
 * to be implemented by the interceptor, and available via a proxy using that
 * interceptor. This is a fundamental AOP concept called <b>introduction</b>.
 *
 * <p>Introductions are often <b>mixins</b>, enabling the building of composite
 * objects that can achieve many of the goals of multiple inheritance in Java.
 *
 * @author Rod Johnson
 * @see DynamicIntroductionAdvice
 */
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {

}

MethodInterceptor的子接口,允许添加由拦截器实现接口,并可以通过代理使用拦截器。这是AOP的基本概念。 从字面意思理解比较懵,下文将通过实例去理解。

我只是一个翻译搬运官,对于源码的英文注释,我会保留并贴出来。因为我觉得编程这玩意,有时候翻译过来味道就变了,作者原本的思想可能就失真了,所以还是建议多读注释,spring的注释字字珍贵,没什么废话。

AOP是一种思想,而非实现。AOP是基于OOP,而又远远高于OOP,主要是将主要核心业务和交叉业务分离,交叉业务就是切面。introduction是AOP中的一个基本概念。不要把AOP和spring绑定起来,也不要把introduction和spring绑定起来。

相关类

在spring AOP中,与引入增强有关的类主要有:IntroductionInterceptor、DynamicIntroductionAdvice、DelegatingIntroductionInterceptor、IntroductionInfoSupport和IntroductionInfo。先总览下他们之间的关系,后文将一一介绍。

IntroductionInterceptor和MethodInterceptor

IntroductionInterceptor和MethodInterceptor相比有什么区别呢? 我的理解是,MethodInterceptor是对目标类方法的增强,它是基于目标类方法,在目标类方法基础上添加额外的逻辑。而IntroductionInterceptor是在不修改目标类的情况下,可以让没有实现A接口的目标类,具备A接口的功能,是凭空赋予目标类新的能力。这就好比,MethodInterceptor可以让一个篮球水平如周琦的人变得像姚明一样(非黑),而IntroductionInterceptor可以赋予一个不会打篮球的人运球、投篮的能力。这就是“0到1(从无到有)”和“1到2(从有到富)”的区别。

引用增强的原理

使用

学会一个东西之前,要先知道怎么用它。

还是以搞懂AOP之一,拦截器链中的例子,我们新建一个目标类Cat,实现了Animal接口。

public class Cat implements Animal {
    @Override
    public void bark() {
        System.out.println("miao miao miao...");
    }
}
###################
public interface Animal {
    void bark();
}

接下来我们新建一个接口Flyable,定义了“飞行”的行为。

public interface Flyable {
    void fly();
}

同时,定义引入飞行的增强行为,FlyableIntroductionInterceptor:

public class FlyableIntroductionInterceptor extends DelegatingIntroductionInterceptor implements Flyable {
    @Override
    public void fly() {
        System.out.println("fly fly fly...");
    }
}

FlyableIntroductionInterceptor继承了DelegatingIntroductionInterceptor,实现了Flyable接口,定义了fly的具体实现,这里仅仅打印一些东西。 接下来我们就在不修改Cat的前提下,让Cat也具有Flyable的行为。

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

        proxyFactory.addAdvice(new LogInterceptor());
        proxyFactory.addAdvice(new TimeInterceptor());
        proxyFactory.addAdvice(new AOPAfterReturningAdvice());
        proxyFactory.addAdvice(new AOPMethodBeforeAdvice());
        proxyFactory.addAdvice(new FlyableIntroductionInterceptor());

        proxyFactory.setTarget(new Cat());
        for (Class<?> ifc : Cat.class.getInterfaces()) {
            proxyFactory.addInterface(ifc);
        }
        proxyFactory.setProxyTargetClass(false);

        Object proxy = proxyFactory.getProxy();

        if (proxy instanceof Animal) {
            ((Animal) proxy).bark();
        }
        System.out.println("============");
        if (proxy instanceof Flyable) {
            ((Flyable) proxy).fly();
        }
    }

输出结果

开始执行
计时开始
Before...
miao miao miao...
AfterReturning ....
计时结束,耗时:0
执行完毕
============
开始执行
计时开始
Before...
fly fly fly...
AfterReturning ....
计时结束,耗时:0
执行完毕

可以看到,通过proxyFactory.addAdvice(new FlyableIntroductionInterceptor()),我们让Cat拥有了Flyable行为,一个猫具备了飞行的能力!并且fly方法与bark方法一样,被LogInterceptor、TimeInterceptor、AOPAfterReturningAdvice和AOPMethodBeforeAdvice进行了增强。看起来就跟Cat自己实现了fly接口一样。这就是引入增强,在不修改目标类(Cat)的情况下,可以让没有实现A接口(Flyable)的目标类,具备A接口的功能。

剖析

按照惯例,我们还是看一下代理增强后的Cat对应的拦截器链:

JdkDynamicAopProxy#invoke

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

可以看到,FlyableIntroductionInterceptor作为一个MethodInterceptor加入到了拦截器链中,按照搞懂AOP之一,拦截器链的分析,真相就在FlyableIntroductionInterceptor的invoke方法中! 最终进入到其父类DelegatingIntroductionInterceptor内。

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);
	}

逻辑比较简单,先判断当前执行的方法是不是属于引入的接口的方法。 如果是,则直接调用目标方法,AopUtils.invokeJoinpointUsingReflection。也就是直接执行fly方法。

public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
			throws Throwable {

		// Use reflection to invoke the method.
		try {
		    // 反射调用目标方法
			ReflectionUtils.makeAccessible(method);
			return method.invoke(target, args);
		}
		catch (InvocationTargetException ex) {
			// Invoked method threw a checked exception.
			// We must rethrow it. The client won't see the interceptor.
			throw ex.getTargetException();
		}
		catch (IllegalArgumentException ex) {
			throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
					method + "] on target [" + target + "]", ex);
		}
		catch (IllegalAccessException ex) {
			throw new AopInvocationException("Could not access method [" + method + "]", ex);
		}
	}

如果不是,则doProceed,继续执行拦截器链后续部分。

protected Object doProceed(MethodInvocation mi) throws Throwable {
		// If we get here, just pass the invocation on.
		return mi.proceed();
	}

DelegatingIntroductionInterceptor本身就是一个MethodInterceptor,所以跟之前拦截器链执行的逻辑是一样,只不过是多了一层判断,即当前被调用方法是不是被引入增强的方法。 画了个逻辑图帮助全局把控:

小细节

addAdvice的顺序问题

不知道大家注意到了没有,当DelegatingIntroductionInterceptor#invoke执行时,如果当前被执行方法是被引入增强的,则会直接调用目标方法,然后返回!返回会有什么问题呢?返回就意味着拦截器链执行到头了,如果后续还有MethodInterceptor,那么是执行不到的,拦截器链就断了。 现在我把之前addAdvice的顺序调换一下,变成

        proxyFactory.addAdvice(new LogInterceptor());
        // 提升FlyableIntroductionInterceptor的add顺序
        proxyFactory.addAdvice(new FlyableIntroductionInterceptor());
        proxyFactory.addAdvice(new TimeInterceptor());
        proxyFactory.addAdvice(new AOPAfterReturningAdvice());
        proxyFactory.addAdvice(new AOPMethodBeforeAdvice());

再来看一下输出

开始执行
计时开始
Before...
miao miao miao...
AfterReturning ....
计时结束,耗时:0
执行完毕
============
开始执行
fly fly fly...
执行完毕

可以看到,对于fly方法的执行,TimeInterceptor、AOPAfterReturningAdvice和AOPMethodBeforeAdvice都不会被执行到,拦截器链戛然而止。 spring是如何解决这种情况的呢? 这里先大概说一下,后续解读Advisor时会再详说。 首先获取到的拦截器链是List结构,有序。然后就是spring会保证IntroductionInterceptor会处在拦截器的最后(通过设置最低优先级来实现),而不是我们测试时候自己按照随意顺序去add的。这样就可以保证我们调用引入增强方法之前,所有的MethodInterceptor都已执行完毕。

ProxyFactory的addInterface和setInterfaces

不知道大家主要本篇测试类的写法没有:

for (Class<?> ifc : Cat.class.getInterfaces()) {
       proxyFactory.addInterface(ifc);
}

搞懂AOP之一,拦截器链中的测试类我们是这样写的:

proxyFactory.setInterfaces(Dog.class.getInterfaces());

这两种写法有什么区别呢?

AdvisedSupport(ProxyFactory的父类,后续会详细分析)

	public void setInterfaces(Class<?>... interfaces) {
		Assert.notNull(interfaces, "Interfaces must not be null");
		// 先清空当前的interfaces
		this.interfaces.clear();
		for (Class<?> ifc : interfaces) {
			addInterface(ifc);
		}
	}
	###################
	public void addInterface(Class<?> intf) {
		Assert.notNull(intf, "Interface must not be null");
		if (!intf.isInterface()) {
			throw new IllegalArgumentException("[" + intf.getName() + "] is not an interface");
		}
		if (!this.interfaces.contains(intf)) {
			this.interfaces.add(intf);
			adviceChanged();
		}
	}

总结来说,ProxyFactory里维护了一个List,即List<Class<?>> interfaces = new ArrayList<>();,这里面存放了代理类需要实现的接口,供创建代理类时使用。setInterfaces方法会以入参值为最终interfaces,因为有一步clear操作。addInterface方法会将入参值add进当前interfaces中。 如果我们先执行了addInterface操作,再执行setInterfaces操作,那么之前addInterface就被冲掉了。 再回到我们的测试类中,主要关注这一步:

proxyFactory.addAdvice(new FlyableIntroductionInterceptor());

这里最终会调用到

	public void addAdvisor(int pos, Advisor advisor) throws AopConfigException {
		if (advisor instanceof IntroductionAdvisor) {
			validateIntroductionAdvisor((IntroductionAdvisor) advisor);
		}
		addAdvisorInternal(pos, advisor);
	}

对于FlyableIntroductionInterceptor,会进入到if内部,也就是执行validateIntroductionAdvisor

	private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
		advisor.validateInterfaces();
		// If the advisor passed validation, we can make the change.
		Class<?>[] ifcs = advisor.getInterfaces();
		for (Class<?> ifc : ifcs) {
			addInterface(ifc);
		}
	}

可以看到,这里会把引入增强的接口fly添加到ProxyFactory所维护的代理需要实现的接口中,也就是List<Class<?>> interfaces = new ArrayList<>();。这也是为什么不采用proxyFactory.setInterfaces(Cat.class.getInterfaces());写法的原因,因为会把fly冲掉嘛。这样生成的代理类就不会实现Flyable接口了。

我们这里先不管Advisor是个啥东西,可以先简单理解成就是切面的一种实现,也可以当MethodInterceptor去看,后续会专门探讨下Advisor、Advice和MethodInterceptor。

后续详细解析源码的过程中,我们将会看看spring是怎么处理这种情况的。

总结

本篇介绍了spring对AOP中的引入增强的实现,并举例说明如何使用IntroductionInterceptor。现在,我们应该已经知道了怎么定义增强,并把增强应用到代理对象。以及被增强过的代理对象在执行被切面切到的方法时,拦截器链是怎么工作的。 接下来,我们将继续分析: 1.proxyFactory.addAdvice做了什么事情?从而引出Advice和Advisor的牵连。 2.proxyFactory.getProxy做了什么事情?jdk代理对象和cglib代理对象是如何生成的。