引言
??spring-aop作為Spring生態(tài)中的基礎(chǔ)模塊,發(fā)揮著舉足輕重的作用。spring-framework內(nèi)部大量使用它來(lái)提供聲明式的企業(yè)級(jí)服務(wù),其中最為開(kāi)發(fā)者熟知的莫過(guò)于spring-cache和spring-tx了。
??學(xué)習(xí)spring-aop的關(guān)鍵在于掌握它的設(shè)計(jì)與實(shí)現(xiàn)。然而AOP相關(guān)概念甚多,抽象也頗為復(fù)雜,如果不建立一個(gè)全局視角就直接鉆到源碼里去的話,很容易被勸退。本文作為spring-aop源碼解析系列的開(kāi)篇(5.2.6.RELEASE),就和大家一起好好聊聊spring-aop的抽象體系。我們的目標(biāo)是媽媽再也不用擔(dān)心我不理解AOP了,話不多說(shuō),我們開(kāi)始吧~~
Why Spring AOP
??首先,讓我們思考一下為什么要使用spring-aop?為了回答這個(gè)問(wèn)題,假設(shè)我們?cè)陂_(kāi)發(fā)一個(gè)計(jì)算器程序:
public interface Calculator {
int calculate(int x, int y);
}
public class AddCalculator implements Calculator {
@Override
public int calculate(int x, int y) {
return x + y;
}
}
public class SubCalculator implements Calculator {
@Override
public int calculate(int x, int y) {
return x - y;
}
}
現(xiàn)在,如果需要做性能統(tǒng)計(jì),該怎么辦呢?樸素的思路是為每個(gè)實(shí)現(xiàn)都單獨(dú)添加一段統(tǒng)計(jì)耗時(shí)的邏輯,但這樣不僅違背了開(kāi)閉原則并且不可避免的會(huì)產(chǎn)生重復(fù)代碼,更重要的是后續(xù)在引入MultipleCalculator等其它計(jì)算類型時(shí),同樣需要拷貝粘貼這一段邏輯。好一點(diǎn)的思路是使用裝飾器模式:
public class TimingCalculator implements Calculator {
private final Calculator calculator;
public TimingCalculator(Calculator calculator) {
this.calculator = calculator;
}
@Override
public int calculate(int x, int y) {
long start = System.currentTimeMillis();
int result = calculator.calculate(x, y);
long end = System.currentTimeMillis();
System.out.println("time in millis: " + (end - start));
return result;
}
public static void main(String[] args) {
Calculator calculator = new TimingCalculator(new AddCalculator());
// 控制臺(tái)輸出耗時(shí)
calculator.calculate(3, 5);
}
}
Better!但這種思路同樣有一些缺陷:
- 需要進(jìn)行包裝,不那么直觀,無(wú)形中增加了API使用者的心智負(fù)擔(dān)
- 耦合度高,適用范圍窄,只能用于
Calculator,其它類型需要添加對(duì)應(yīng)的裝飾器
可這種模式又是如此通用,以至于JDK為我們提供了一種優(yōu)化思路——?jiǎng)討B(tài)代理。
public class TimingProxyFactory {
public static Object getProxy(Object target, Class<?>[] ifcs) {
ClassLoader cl = target.getClass().getClassLoader();
return Proxy.newProxyInstance(cl, ifcs, new TimingInvocationHandler(target));
}
private static class TimingInvocationHandler implements InvocationHandler {
private final Object target;
public TimingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// before
long start = System.currentTimeMillis();
try {
Object result = method.invoke(target, args);
// after returning
return result;
} catch (Throwable throwable) {
// after throwing
throw throwable;
} finally {
// after
long end = System.currentTimeMillis();
System.out.println("time in millis: " + (end - start));
}
}
}
public static void main(String[] args) {
Calculator calculator = (Calculator) TimingProxyFactory.getProxy(new AddCalculator(),
new Class[]{Calculator.class});
// 控制臺(tái)輸出耗時(shí)
calculator.calculate(3, 5);
}
}
Better!JDK動(dòng)態(tài)代理的出現(xiàn),解決了代碼耦合的問(wèn)題,即使Calculator中新增了方法,也可以在不附加任何修改的前提下就獲得統(tǒng)計(jì)耗時(shí)的功能。更令人興奮的是適用性得到了極大的增強(qiáng),TimingProxyFactory不再只服務(wù)于Calculator,而可以是任意類型的接口。凡事都有兩面性,JDK動(dòng)態(tài)代理固然有諸多利好,卻也不是沒(méi)有缺點(diǎn),它
- 沒(méi)有解決調(diào)用時(shí)的包裝問(wèn)題,使用方不能無(wú)感地使用代理功能
- 只能針對(duì)接口進(jìn)行代理,不能對(duì)類進(jìn)行代理
如果使用spring-aop呢?
@Aspect
@Component
public class TimingAspect {
// 對(duì)com.anyoptional.aop包下的所有類的所有方法進(jìn)行增強(qiáng),
// 統(tǒng)一為它們?cè)黾有阅芙y(tǒng)計(jì)功能,而不再局限于某些接口,
// 適用性得到進(jìn)一步提升
@Around("execution(* com.anyoptional.aop.*.*(*, ..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("time in millis: " + (end - start));
return result;
}
}
@SpringBootTest
@RunWith(SpringRunner.class)
public class TimingAspectTest {
@Autowired
Calculator addCalculator;
@Test
public void contextLoad() {
// 控制臺(tái)輸出耗時(shí)
addCalculator.calculate(3, 5);
}
}
Best!spring-aop與Spring IoC的結(jié)合讓業(yè)務(wù)邏輯和性能統(tǒng)計(jì)徹底解耦,使用者可以無(wú)感地使用增強(qiáng)后的Calculator實(shí)例,并且Calculator也不再局限為接口,spring-aop會(huì)根據(jù)Calculator的類型智能地選擇生成JDK動(dòng)態(tài)代理或使用CGLIB動(dòng)態(tài)生成子類。同時(shí),切面不僅僅只對(duì)某些類型生效,而可以是com.anyoptional.aop包下所有類的所有方法。通過(guò)使用AspectJ切面表達(dá)式,可以輕松橫跨多種類型和多個(gè)對(duì)象,適用性得到進(jìn)一步提升。
AOP Concepts
??相信通過(guò)上面的小例子,我們能夠感受到傳統(tǒng)的OOP范式不能很好地封裝一些貫穿全局的輔助邏輯,比如示例中的性能統(tǒng)計(jì),又或者權(quán)限校驗(yàn)、事務(wù)管理等等。而使用spring-aop,能夠在不侵入業(yè)務(wù)邏輯的基礎(chǔ)上對(duì)原業(yè)務(wù)進(jìn)行增強(qiáng),并且可以很方便的在所有業(yè)務(wù)邏輯中生效。
??說(shuō)了這么多,那什么是AOP呢?Spring官網(wǎng)給出了解釋:
Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing
another way of thinking about program structure. The key unit of modularity in OOP is the
class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization
of concerns (such as transaction management) that cut across multiple types and objects.
(Such concerns are often termed “crosscutting” concerns in AOP literature.)
面向切面編程(Aspect-oriented Programming)通過(guò)提供對(duì)程序結(jié)構(gòu)的另一種思維方式來(lái)補(bǔ)充面向?qū)ο缶幊蹋?code>OOP)。在OOP編程范式中,模塊化的核心單元是類,而在AOP編程范式中,模塊化的核心單元是切面。切面,使得跨越多種類型和對(duì)象的問(wèn)題得到模塊化。
??一臉懵逼?沒(méi)關(guān)系,讓我們用人話來(lái)解讀一下AOP相關(guān)術(shù)語(yǔ)吧。
關(guān)注點(diǎn)(
Concern):指系統(tǒng)中基于功能劃分出的一部分,比如示例中的計(jì)算器和對(duì)它的性能統(tǒng)計(jì)橫切關(guān)注點(diǎn)(
Crosscutting concerns):部分關(guān)注點(diǎn)橫切程序中的多個(gè)模塊,即施加在多個(gè)需要的模塊上,它們就被稱作橫切關(guān)注點(diǎn),比如示例中的性能統(tǒng)計(jì)連接點(diǎn)(
Join point):程序運(yùn)行過(guò)程中的某個(gè)點(diǎn),比如方法調(diào)用或處理異常。在spring-aop中,連接點(diǎn)始終代表方法調(diào)用,換句話說(shuō)spring-aop是基于方法攔截的,比如示例中攔截了對(duì)位于com.anyoptional.aop包下所有類、所有方法的調(diào)用,這里,方法調(diào)用就是連接點(diǎn)增強(qiáng)(
Advice):在特定連接點(diǎn)上由某個(gè)切面持有的動(dòng)作,比如示例中的doAround(...),它為連接點(diǎn)添加了性能統(tǒng)計(jì)功能,我們就說(shuō)它對(duì)連接點(diǎn)進(jìn)行了增強(qiáng)。Advice的類型包括Around、Before和After,spring-aop將Advice建模為攔截器,并在連接點(diǎn)上維護(hù)了一個(gè)攔截器鏈引介(
Introduction):動(dòng)態(tài)地給類添加方法或字段。spring-aop允許我們動(dòng)態(tài)地給對(duì)象引入新的接口及其實(shí)現(xiàn)。這個(gè)示例中就沒(méi)有啦,它有點(diǎn)像Swift中的extension,只不過(guò)extension發(fā)生在編譯期,更確切地說(shuō)像ObjectiveC runtime中的class_addMethod(...)和objc_setAssociatedObject(...)切點(diǎn)(
Pointcut):匹配連接點(diǎn)的謂詞。Advice與切點(diǎn)表達(dá)式相關(guān)聯(lián),并對(duì)任何匹配的連接點(diǎn)進(jìn)行增強(qiáng),比如示例中的切點(diǎn)選取了位于com.anyoptional.aop包下的所有類的所有方法切面(
Aspect):對(duì)跨越多個(gè)類型的關(guān)注點(diǎn)的模塊化。比如示例中的性能統(tǒng)計(jì)切面, 它聚合了切點(diǎn)和增強(qiáng),可以對(duì)匹配的任何連接點(diǎn)應(yīng)用增強(qiáng)目標(biāo)對(duì)象(
Target object):被一個(gè)或多個(gè)切面增強(qiáng)的對(duì)象。由于spring-aop是基于運(yùn)行時(shí)動(dòng)態(tài)代理的,因此目標(biāo)對(duì)象就是被代理對(duì)象,比如示例中的AddCalculator實(shí)例
現(xiàn)在是不是稍微清晰一點(diǎn)了?
Spring AOP Abstraction
??AOP的核心概念并不是Spring獨(dú)有的,而是一個(gè)廣泛使用的概念。在實(shí)現(xiàn)上,spring-aop確實(shí)也引入了一些特有的概念,我們繼續(xù)說(shuō)道說(shuō)道。
MethodInvocation
??spring-aop目前只支持方法調(diào)用(MethodInvocation)這一種Join point,MethodInvocation是對(duì)方法調(diào)用上下文的封裝。
public interface Joinpoint {
/**
* 執(zhí)行攔截器鏈中的下一個(gè)攔截器,類似于Servlet中FilterChain的doFilter(...)
*/
Object proceed() throws Throwable;
/**
* 持有static part的對(duì)象,比如對(duì)方法調(diào)用來(lái)說(shuō)就是這個(gè)方法所屬的實(shí)例,也就是被代理對(duì)象
*/
Object getThis();
/**
* static part 是攔截器鏈作用的某個(gè)AccessibleObject,比如對(duì)方法調(diào)用來(lái)說(shuō)就是那個(gè)方法
*/
AccessibleObject getStaticPart();
}
public interface Invocation extends Joinpoint {
/**
* 獲取參數(shù)信息,比如方法的參數(shù)
* 對(duì)返回?cái)?shù)組中元素的修改會(huì)反應(yīng)到實(shí)際的參數(shù)上去,持有引用嘛,懂的都懂
*/
Object[] getArguments();
}
public interface MethodInvocation extends Invocation {
/**
* 獲取即將被調(diào)用的方法,類型更加具體的#getStaticPart()版本
*/
Method getMethod();
}
Pointcut
??前文提到,Pointcut是用來(lái)匹配Join point的謂詞,因此spring-aop對(duì)Pointcut的建模是圍繞如何匹配方法來(lái)展開(kāi)的。我們知道,Java中方法是不能脫離類而單獨(dú)存在的,所以Pointcut也自然地分為了ClassFilter和MethodMatcher兩部分。
public interface Pointcut {
/**
* 類型過(guò)濾器,首先過(guò)濾類型,因?yàn)镴ava中方法是附加在類上的,不能單獨(dú)存在。
*/
ClassFilter getClassFilter();
/**
* 方法匹配器,可以根據(jù)方法名、方法上的注解等信息來(lái)確定該方法
* 是不是一個(gè)需要被增強(qiáng)的連接點(diǎn)
*/
MethodMatcher getMethodMatcher();
}
public interface ClassFilter {
/**
* 判斷給定的類或接口能否被切點(diǎn)所匹配
*/
boolean matches(Class<?> clazz);
}
public interface MethodMatcher {
/**
* 判斷給定的方法能否被切點(diǎn)所匹配
* 這里的判定是靜態(tài)判定,也就是不涉及方法參數(shù),因?yàn)閰?shù)只有在觸發(fā)調(diào)用的那一刻才能真正確定下來(lái)
*/
boolean matches(Method method, Class<?> targetClass);
/**
* 返回true,表示需要進(jìn)行進(jìn)一步的運(yùn)行時(shí)判定,具體來(lái)說(shuō),就是額外檢查方法參數(shù)
*/
boolean isRuntime();
/**
* 判斷給定的方法能否被切點(diǎn)所匹配
* 這里的判定是動(dòng)態(tài)判定,只在靜態(tài)判定成功和isRuntime()返回true的基礎(chǔ)上才會(huì)觸發(fā)
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
}
spring-aop提供了很多開(kāi)箱即用的Pointcut實(shí)現(xiàn),其中最常用的是AnnotationMatchingPointcut和AspectJExpressionPointcut。前者根據(jù)類和方法上的注解信息執(zhí)行匹配,比如@Async開(kāi)啟異步執(zhí)行、@Transactional開(kāi)啟本地事務(wù);后者通過(guò)AspectJ切點(diǎn)表達(dá)式來(lái)執(zhí)行匹配,比如示例中對(duì)com.anyoptional.aop包下的所有類、所有方法的匹配。
Hierarchy of Advice
??Advice表示在連接點(diǎn)上發(fā)生的動(dòng)作。由于spring-aop只支持MethodInvocation,因此將Advice建模為方法攔截器(MethodInterceptor)就再合適不過(guò)了。
// 標(biāo)記接口,表示這是一個(gè)Advice
public interface Advice {
}
// 同樣是一個(gè)標(biāo)記接口,表示這是一個(gè)攔截器
public interface Interceptor extends Advice {
}
// 方法攔截器,攔截具體的方法
public interface MethodInterceptor extends Interceptor {
/**
* 攔截方法調(diào)用,可以在方法調(diào)用前、后執(zhí)行自定義邏輯,通過(guò)調(diào)用Joinpoint#proceed()轉(zhuǎn)到攔截器鏈上的下一個(gè)實(shí)例
*/
Object invoke(MethodInvocation invocation) throws Throwable;
}
MethodInterceptor的功能有點(diǎn)過(guò)于強(qiáng)大,它可以在方法執(zhí)行前、執(zhí)行后甚至是拋出異常時(shí)執(zhí)行自定義邏輯,相當(dāng)于AspectJ中的Around Advice(也就是所謂的環(huán)繞增強(qiáng))。為了避免濫用,spring-aop設(shè)計(jì)了幾種窄化的Advice,窄化后的Advice只能在方法執(zhí)行的特定時(shí)機(jī)對(duì)它進(jìn)行增強(qiáng)。
// 目標(biāo)方法執(zhí)行前進(jìn)行增強(qiáng)
public interface BeforeAdvice extends Advice {
}
// 目標(biāo)方法執(zhí)行后進(jìn)行增強(qiáng)
// 方法執(zhí)行完有兩種可能的結(jié)果
// 1. 正常結(jié)束
// 2. 異常退出
public interface AfterAdvice extends Advice {
}
// 對(duì)應(yīng)方法正常結(jié)束
public interface AfterReturningAdvice extends AfterAdvice {
// 方法正常結(jié)束后進(jìn)行增強(qiáng),此時(shí)可以獲取到方法返回值,如果有的話
void afterReturning(@Nullable Object returnValue, Method method,
Object[] args, @Nullable Object target) throws Throwable;
}
// 對(duì)應(yīng)方法異常退出
public interface ThrowsAdvice extends AfterAdvice {
// ThrowsAdvice沒(méi)有定義任何方法,它的方法是根據(jù)約定通過(guò)反射調(diào)用的,可能的形式是如下兩種
// 1. public void afterThrowing(Exception ex),ex可以是任意類型的異常
// 2. public void afterThrowing(Method method, Object[] args, Object target, Exception ex),前三個(gè)參數(shù)分別是方法、方法參數(shù)和目標(biāo)對(duì)象,最后一個(gè)參數(shù)是異常信息,同樣可以是任意類型的異常
}
TargetSource
??spring-aop使用TargetSouece來(lái)獲取目標(biāo)對(duì)象(target object)。TargetSource作為工廠接口可以屏蔽目標(biāo)對(duì)象的獲取細(xì)節(jié),真實(shí)的目標(biāo)對(duì)象可能來(lái)自對(duì)象池也可能就是一個(gè)單例對(duì)象。
public interface TargetClassAware {
/**
* 返回目標(biāo)對(duì)象的真實(shí)類型
*/
@Nullable
Class<?> getTargetClass();
}
public interface TargetSource extends TargetClassAware {
/**
* 返回true,表示多次調(diào)用#getTarget()獲取的是同一個(gè)對(duì)象,因此也就沒(méi)必要調(diào)用#releaseTarget(...)了
*/
boolean isStatic();
/**
* 返回目標(biāo)對(duì)象,目標(biāo)對(duì)象是持有連接點(diǎn)的對(duì)象,也是被代理的對(duì)象
*/
@Nullable
Object getTarget() throws Exception;
/**
* 釋放通過(guò)#getTarget()獲取的目標(biāo)對(duì)象
*/
void releaseTarget(Object target) throws Exception;
}
Advisor
??Advisor作為Advice的持有者,通常來(lái)說(shuō)會(huì)額外攜帶一個(gè)過(guò)濾器來(lái)判定Advice的適用性,比如Pointcut(Introduction使用的很少,我們就不講它了)。換言之,Advisor表達(dá)的是可以對(duì)哪些類型的哪些方法進(jìn)行增強(qiáng)。
public interface Advisor {
/**
* 持有的Advice,可能是MethodInterceptor、BeforeAdvice,、ThrowsAdvice等等
*/
Advice getAdvice();
/**
* 在spring-aop中未被使用,可忽略
*/
boolean isPerInstance();
}
// PointcutAdvisor幾乎涵蓋了spring-aop中所有的Advisor類型
public interface PointcutAdvisor extends Advisor {
/**
* 獲取驅(qū)動(dòng)此Advisor的切點(diǎn)
*/
Pointcut getPointcut();
}
Advised
??Advised被設(shè)計(jì)來(lái)保存AOP配置信息,比如目標(biāo)對(duì)象是誰(shuí)?是使用JDK動(dòng)態(tài)代理還是CGLIB?如果使用JDK動(dòng)態(tài)代理,需要被代理的接口有哪些?圍繞目標(biāo)對(duì)象的Advisor有哪些?相信你也看出來(lái)了,這個(gè)接口是留給創(chuàng)建代理的工廠實(shí)現(xiàn)的,只有在掌握了這些信息,創(chuàng)建出的代理才可能是符合需求的。
public interface Advised extends TargetClassAware {
/**
* AOP配置是否已凍結(jié),如果是,后續(xù)就不能修改Advice了
* isFrozen()的主要作用是給spring-aop一個(gè)hint,框架可以借此執(zhí)行一些優(yōu)化
*/
boolean isFrozen();
/**
* 使用jdk動(dòng)態(tài)代理還是cglib
*/
boolean isProxyTargetClass();
/**
* 返回所有需要被代理的接口
*/
Class<?>[] getProxiedInterfaces();
/**
* 檢測(cè)某個(gè)接口是否被代理
*/
boolean isInterfaceProxied(Class<?> intf);
/**
* 設(shè)置獲取目標(biāo)對(duì)象的工廠,也就是當(dāng)前Advised作用的對(duì)象
*/
void setTargetSource(TargetSource targetSource);
/**
* Return the {@code TargetSource} used by this {@code Advised} object.
*/
TargetSource getTargetSource();
/**
* 設(shè)置是否暴露代理對(duì)象到ThreadLocal中,如果是,后續(xù)可通過(guò)AopContext獲取
*/
void setExposeProxy(boolean exposeProxy);
/**
* 是否暴露代理對(duì)象到ThreadLocal中
*/
boolean isExposeProxy();
/**
* 設(shè)置當(dāng)前AOP配置信息是否已被超前過(guò)濾過(guò)了,如果是的話,可以不執(zhí)行Pointcut持有的ClassFilter
*/
void setPreFiltered(boolean preFiltered);
/**
* 返回當(dāng)前AOP配置信息是否已被超前過(guò)濾過(guò)
*/
boolean isPreFiltered();
/**
* 返回所有配置給目標(biāo)對(duì)象的Advisor,這些Advisor將組成攔截器鏈作用在連接點(diǎn)上
*/
Advisor[] getAdvisors();
/**
* Advisor的增刪改查
*/
void addAdvisor(Advisor advisor) throws AopConfigException;
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
boolean removeAdvisor(Advisor advisor);
void removeAdvisor(int index) throws AopConfigException;
int indexOf(Advisor advisor);
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
void addAdvice(Advice advice) throws AopConfigException;
void addAdvice(int pos, Advice advice) throws AopConfigException;
boolean removeAdvice(Advice advice);
int indexOf(Advice advice);
/**
* 因?yàn)閠oString()最終會(huì)作用在目標(biāo)對(duì)象上,因此提供了toProxyConfigString()來(lái)返回配置信息的文本描述
*/
String toProxyConfigString();
}
結(jié)語(yǔ)
??以上就是對(duì)spring-aop相關(guān)概念的解讀了,相信耐心看完的你一定有所收貨。下一篇的話我們一起研究研究Spring到底是如何執(zhí)行織入的吧~~
??喔,對(duì)了,織入(Weaving)是把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程。spring-aop的織入依賴于BeanPostProcessor,理解了spring-aop是如何執(zhí)行織入的,也就從根上理解了spring-aop。好啦,我們下篇再見(jiàn)。