spring AOP原始碼深度解析
因為springAOP會使用springIOC來管理Bean,所以對springIOC不太瞭解的同學可以參考我前篇springIOC原始碼深度解析。
本文采用的原始碼版本是5.2.x。為了我們更好地理解springAOP,我們使用的是xml的方式,實際開發中大部分都是是用註解的方式,經驗告訴我,從理解原始碼的角度上來講,xml配置是最好不過了。
閱讀建議:把spring官網的原始碼給拉下來,對照著我的解析看,這樣學習效率是最高的,而不是走馬觀花。
本文分為兩大部分,第一部分對spring AOP 的一些前置知識進行介紹,這部分的目的是讓對springAOP不太瞭解的讀者能瞭解springAOP的一些基本知識。第二部分就是我們本文的重點,這部分會對springAOP原始碼進行深入的解析。
spring AOP的相關術語介紹
-
Joinpoint(連線點)
所謂的連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點
對應的類:
public interface Joinpoint { // 執行攔截器鏈中的下一個攔截器邏輯 Object proceed() throws Throwable; Object getThis(); AccessibleObject getStaticPart(); } 複製程式碼
-
Pointcut(切入點)
所謂的切入點,是指我們要對哪些Joinpoint進行攔截的定義
對應的類:
public interface Pointcut { //返回一個型別過濾器 ClassFilter getClassFilter(); // 返回一個方法匹配器 MethodMatcher getMethodMatcher(); Pointcut TRUE = TruePointcut.INSTANCE; } 複製程式碼
對應的ClassFilter和MethodMatcher介面:
public interface ClassFilter { boolean matches(Class<?> clazz); ClassFilter TRUE = TrueClassFilter.INSTANCE; } public
我們寫AOP程式碼的時候,一般是同切入點表示式進行對連線點進行選擇,切入點表示式處理類: AspectJExpressionPointcut,這個類的繼承關係圖如下:
可以看到AspectJExpressionPointcut繼承了ClassFilter和MethodMatcher介面,擁有了matches方法,所以它可以對連線點進行選擇。
-
Advice(通知/增強)
所謂的通知就是指攔截到Jointpoint之後所要做的事情就是通知,通知分為前置通知(AspectJMethodBeforeAdvice),後置通知(AspectJAfterReturningAdvice),異常通知(AspectJAfterThrowingAdvice),最終通知(AspectAfterAdvice),環繞通知(AspectJAroundAdvice)
-
Introduction(引介)
引介是一種特殊的通知在不修改類程式碼的前提下,Introduction可以在執行期為類動態地新增一些方法或Field。
-
Target(目標物件)
代理的目標物件,比如UserServiceImpl類
-
Weaving(織入)
是指把增強應用到目標物件來建立新的代理物件的過程。spring是通過實現後置處理器BeanPostProcessor介面來實現織入的。也就是在bean完成初始化之後,通過給目標物件生成代理物件,並交由springIOC容器來接管,這樣再去容器中獲取到的目標物件就是已經增強過的代理物件。
-
Proxy(代理)
一個類被AOP織入增強後,就產生一個結果代理類
-
Advisor(通知器、顧問)
和Aspect很相似
-
Aspect(切面)
是切入點和通知的結合,對應於spring中的PointcutAdvisor,既可以獲取到切入點又可以獲取到通知。如下面類所示:
public interface Advisor { Advice getAdvice(); boolean isPerInstance(); } public interface PointcutAdvisor extends Advisor { Pointcut getPointcut(); } 複製程式碼
對上面的知識點,通過下面的圖來總結一下,希望能夠幫助讀者記憶。
瞭解AOP、Spring AOP、AspectJ這幾個概念
什麼是AOP
AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計。具體的請讀者查資料看相關定義
Spring AOP
- 基於動態代理實現,如果使用介面,則用JDK的動態代理實現;如果沒有實現介面,則使用CGLIB來實現。
- 而動態代理是基於反射設計的。
- Spring提供了AspectJ的支援,實現了一套純的Spring AOP
本文解析原始碼時基於springAOP進行解析的。
原理如下圖所示:
可能這樣講還是有些抽象,下面寫個例子輔助理解:
建立一個Person介面:
public interface Person {
public void eat();
}
複製程式碼
建立介面實現類:PersonImpl
public class PersonImpl implements Person {
@Override
public void eat() {
System.out.println("我要吃飯");
}
}
複製程式碼
小時候媽媽告訴我們吃飯前要洗手,怎麼使用程式碼進行實現呢?
定義一個EatHandler,實現 InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class EatHandler implements InvocationHandler {
// 要代理的目標物件
private Object target;
public EatHandler(Object target) {
this.target = target;
}
public Object proxyInstance() {
// 第一個引數就是要載入代理物件的類載入器,
// 第二個引數就是要代理類實現的介面
// 第三個引數就是EatHandler
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
System.out.println("吃飯前要洗手");
Object result = method.invoke(this.target,args);
System.out.println("吃飯後要洗碗");
return result;
}
}
複製程式碼
來個測試類:
public class ProxyMain {
public static void main(String[] args) {
EatHandler eatHandler = new EatHandler(new PersonImpl());
Person person = (Person) eatHandler.proxyInstance();
person.eat();
}
}
複製程式碼
輸出:
吃飯前要洗手
我要吃飯
吃飯後要洗碗
複製程式碼
可以看到,在“我要吃飯”前後我們已經插入了“吃飯前要洗手”,“吃飯後要洗碗”的功能
是怎麼插入的呢?
原理就是JDK幫我們建立了目標物件(EatHandler)的代理類,這個代理類插入了我們想要的功能,當我們呼叫person.eat();時,操作的是代理類而不是原物件,從而實現了插入我們想要功能的需求。這樣應該可以理解了。下面貼下生成的代理物件:
import java.lang.reflect.UndeclaredThrowableException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//
// Decompiled by Procyon v0.5.36
//
public final class PersonImpl$Proxy1 extends Proxy implements Person
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public PersonImpl$Proxy1(final InvocationHandler h) {
super(h);
}
public final boolean equals(final Object o) {
try {
return (boolean)super.h.invoke(this,PersonImpl$Proxy1.m1,new Object[] { o });
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable undeclaredThrowable) {
throw new UndeclaredThrowableException(undeclaredThrowable);
}
}
public final void eat() {
try {
super.h.invoke(this,PersonImpl$Proxy1.m3,null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable undeclaredThrowable) {
throw new UndeclaredThrowableException(undeclaredThrowable);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this,PersonImpl$Proxy1.m2,null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable undeclaredThrowable) {
throw new UndeclaredThrowableException(undeclaredThrowable);
}
}
public final int hashCode() {
try {
return (int)super.h.invoke(this,PersonImpl$Proxy1.m0,null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable undeclaredThrowable) {
throw new UndeclaredThrowableException(undeclaredThrowable);
}
}
static {
try {
PersonImpl$Proxy1.m1 = Class.forName("java.lang.Object").getMethod("equals",Class.forName("java.lang.Object"));
// 利用反射獲取目標物件的Method方法
PersonImpl$Proxy1.m3 = Class.forName("Person").getMethod("eat",(Class<?>[])new Class[0]);
PersonImpl$Proxy1.m2 = Class.forName("java.lang.Object").getMethod("toString",(Class<?>[])new Class[0]);
PersonImpl$Proxy1.m0 = Class.forName("java.lang.Object").getMethod("hashCode",(Class<?>[])new Class[0]);
}
catch (NoSuchMethodException ex) {
throw new NoSuchMethodError(ex.getMessage());
}
catch (ClassNotFoundException ex2) {
throw new NoClassDefFoundError(ex2.getMessage());
}
}
}
複製程式碼
我們看到JDK確實是為我們生成了一個代理物件,這個代理物件繼承了Proxy物件,實現了Person介面。我們看到代理物件中生成了equals()、toString()、hashCode()以及我們的eat()方法。看到eat方法中有這樣的程式碼super.h.invoke(this,null); 其中,h就是我們定義的EatHandle。我們看到當我們呼叫person.eat()的時候,實際上是呼叫這個代理物件的eat()方法,先是呼叫EatHandle中的invoke方法,這裡有我們加入的增強程式碼,這就實現了代理的作用了。這樣應該懂了吧。
AspectJ
- AspectJ是一個Java實現的AOP框架,它能夠對java程式碼進行AOP編譯(一般在編譯期進行),讓 java程式碼具有AspectJ的AOP功能(當然需要特殊的編譯器)
- 可以這樣說AspectJ是目前實現AOP框架中最成熟,功能最豐富的語言。更幸運的是,AspectJ與 java程式完全相容,幾乎是無縫關聯,因此對於有java程式設計基礎的工程師,上手和使用都非常容易。
- ApectJ採用的就是靜態織入的方式。ApectJ主要採用的是編譯期織入,在這個期間使用AspectJ的 acj編譯器(類似javac)把aspect類編譯成class位元組碼後,在java目標類編譯時織入,即先編譯 aspect類再編譯目標類。
如下圖所示:
原始碼解析
有了前面基礎知識的鋪墊,現在終於可以進入到原始碼解析階段了。讓我們來驗證一下我們的想法吧。請讀者開啟spring原始碼。
這個階段分為三個步驟:
- AOP配置檔案的解析流程分析
- 代理物件的建立分析
- 代理物件執行流程分析
AOP配置檔案的解析流程分析
我們要完成下面配置檔案的解析:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置目標物件 -->
<bean class="com.sjc.spring.aop.target.UserServiceImpl"></bean>
<!-- 配置通知類 -->
<bean id="myAdvice" class="com.sjc.spring.aop.advice.MyAdvice"></bean>
<!-- AOP配置 -->
<aop:config>
<!-- <aop:advisor advice-ref="" /> -->
<aop:pointcut expression="" id="" />
<aop:aspect ref="myAdvice">
<aop:before method="before"
pointcut="execution(* *..*.*ServiceImpl.*(..))" />
<aop:after method="after"
pointcut="execution(* *..*.*ServiceImpl.*(..))" />
</aop:aspect>
</aop:config>
</beans>
複製程式碼
入口:DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
我們在上一篇解析SpringIOC原始碼的時候也也分析過這一步,讓我們進入到
delegate.parseCustomElement(ele);
protected void parseBeanDefinitions(Element root,BeanDefinitionParserDelegate delegate) {
// 載入的Document物件是否使用了Spring預設的XML名稱空間(beans名稱空間)
if (delegate.isDefaultNamespace(root)) {
// 獲取Document物件根元素的所有子節點(bean標籤、import標籤、alias標籤和其他自定義標籤context、aop等)
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
// bean標籤、import標籤、alias標籤,則使用預設解析規則
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele,delegate);
}
else { //像context標籤、aop標籤、tx標籤,則使用使用者自定義的解析規則解析元素節點
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
複製程式碼
我們進入到:BeanDefinitionParserDelegate#parseCustomElement
這個方法主要做兩件事:
- 獲取名稱空間URI(就是獲取beans標籤的xmlns:aop或者xmlns:context屬性的值)
- 根據namespaceUri獲取對應的處理類handler
public BeanDefinition parseCustomElement(Element ele,@Nullable BeanDefinition containingBd) {
// 獲取名稱空間URI(就是獲取beans標籤的xmlns:aop或者xmlns:context屬性的值)
// http://www.springframework.org/schema/aop
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 根據不同的名稱空間URI,去匹配不同的NamespaceHandler(一個名稱空間對應一個NamespaceHandler)
// 此處會呼叫DefaultNamespaceHandlerResolver類的resolve方法
// 兩步操作:查詢NamespaceHandler 、呼叫NamespaceHandler的init方法進行初始化(針對不同自定義標籤註冊相應的BeanDefinitionParser)
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]",ele);
return null;
}
return handler.parse(ele,new ParserContext(this.readerContext,this,containingBd));
}
複製程式碼
我們進入到NamespaceHandlerSupport#parse
這個方法我們主要關心的是parser.parse()方法,findParserForElement可以作為一個分支去了解,如果讀者感興趣的話。
public BeanDefinition parse(Element element,ParserContext parserContext) {
// NamespaceHandler裡面初始化了大量的BeanDefinitionParser來分別處理不同的自定義標籤
// 從指定的NamespaceHandler中,匹配到指定的BeanDefinitionParser
BeanDefinitionParser parser = findParserForElement(element,parserContext);
// 呼叫指定自定義標籤的解析器,完成具體解析工作
return (parser != null ? parser.parse(element,parserContext) : null);
}
複製程式碼
BeanDefinitionParser有很多的實現類,這裡我們配置檔案是由ConfigBeanDefinitionParser來實現的。
我們進入到ConfigBeanDefinitionParser#parse();
這個方法主要產生三個我們比較關心的分支:
-
configureAutoProxyCreator(parserContext,element);
主要是生成AspectJAwareAdvisorAutoProxyCreator類的BeanDefinition,並註冊到IOC容器,這個類用來建立AOP代理物件
-
parsePointcut(elt,parserContext);
產生一個AspectJExpressionPointcut的BeanDefinition物件,並註冊。這個AspectJExpressionPointcut用來解析我們的切入點表示式,讀者可以看開頭我們介紹的AspectJExpressionPointcut類關係圖。
-
parseAspect(elt,parserContext);
這一步主要是將解析的aop:aspect標籤進行進行封裝。我們主要來看看這個分支。
public BeanDefinition parse(Element element,ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(),parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
// 向IoC容器中註冊 AspectJAwareAdvisorAutoProxyCreator 類的BeanDefinition:(用於建立AOP代理物件的)
// BeanPostProcessor可以對例項化之後的bean進行一些操作
// AspectJAwareAdvisorAutoProxyCreator 實現了BeanPostProcessor介面,可以對目標物件例項化之後,建立對應的代理物件
configureAutoProxyCreator(parserContext,element);
// 獲取<aop:config>標籤的子標籤<aop:aspect>、<aop:advisor> 、<aop:pointcut>
List<Element> childElts = DomUtils.getChildElements(element);
for (Element elt: childElts) {
// 獲取子標籤的節點名稱或者叫元素名稱
String localName = parserContext.getDelegate().getLocalName(elt);
if (POINTCUT.equals(localName)) {
// 解析<aop:pointcut>標籤
// 產生一個AspectJExpressionPointcut的BeanDefinition物件,並註冊
parsePointcut(elt,parserContext);
}
else if (ADVISOR.equals(localName)) {
// 解析<aop:advisor>標籤
// 產生一個DefaultBeanFactoryPointcutAdvisor的BeanDefinition物件,並註冊
parseAdvisor(elt,parserContext);
}
else if (ASPECT.equals(localName)) {
// 解析<aop:aspect>標籤
// 產生了很多BeanDefinition物件
// aop:after等標籤對應5個BeanDefinition物件
// aop:after標籤的method屬性對應1個BeanDefinition物件
// 最終的AspectJPointcutAdvisor BeanDefinition類
parseAspect(elt,parserContext);
}
}
parserContext.popAndRegisterContainingComponent();
return null;
}
複製程式碼
我們進入到ConfigBeanDefinitionParser#parseAspect
這個方法開始解析aop:aspect標籤,
我們主要關心parseAdvice方法
private void parseAspect(Element aspectElement,ParserContext parserContext) {
// 獲取<aop:aspect>標籤的id屬性值
String aspectId = aspectElement.getAttribute(ID);
// 獲取<aop:aspect>標籤的ref屬性值,也就是增強類的引用名稱
String aspectName = aspectElement.getAttribute(REF);
try {
this.parseState.push(new AspectEntry(aspectId,aspectName));
List<BeanDefinition> beanDefinitions = new ArrayList<>();
List<BeanReference> beanReferences = new ArrayList<>();
// 處理<aop:aspect>標籤的<aop:declare-parents>子標籤
List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement,DECLARE_PARENTS);
for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
Element declareParentsElement = declareParents.get(i);
beanDefinitions.add(parseDeclareParents(declareParentsElement,parserContext));
}
// We have to parse "advice" and all the advice kinds in one loop,to get the
// ordering semantics right.
// 獲取<aop:aspect>標籤的所有子標籤
NodeList nodeList = aspectElement.getChildNodes();
boolean adviceFoundAlready = false;
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
// 判斷是否是<aop:before>、<aop:after>、<aop:after-returning>、<aop:after-throwing method="">、<aop:around method="">這五個標籤
if (isAdviceNode(node,parserContext)) {
if (!adviceFoundAlready) {
adviceFoundAlready = true;
if (!StringUtils.hasText(aspectName)) {
parserContext.getReaderContext().error(
"<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",aspectElement,this.parseState.snapshot());
return;
}
beanReferences.add(new RuntimeBeanReference(aspectName));
}
// 解析<aop:before>等五個子標籤
// 方法主要做了三件事:
// 1、根據織入方式(before、after這些)建立RootBeanDefinition,名為adviceDef即advice定義
// 2、將上一步建立的RootBeanDefinition寫入一個新的RootBeanDefinition,構造一個新的物件,名為advisorDefinition,即advisor定義
// 3、將advisorDefinition註冊到DefaultListableBeanFactory中
AbstractBeanDefinition advisorDefinition = parseAdvice(
aspectName,i,(Element) node,parserContext,beanDefinitions,beanReferences);
beanDefinitions.add(advisorDefinition);
}
}
AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
aspectElement,aspectId,beanReferences,parserContext);
parserContext.pushContainingComponent(aspectComponentDefinition);
List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement,POINTCUT);
for (Element pointcutElement : pointcuts) {
parsePointcut(pointcutElement,parserContext);
}
parserContext.popAndRegisterContainingComponent();
}
finally {
this.parseState.pop();
}
}
複製程式碼
我們進入到ConfigBeanDefinitionParser#parseAdvice
這個方法主要做的是:解析aop:before等五個子標籤
1、根據織入方式(before、after這些)建立RootBeanDefinition,名為adviceDef即advice定義 2、將上一步建立的RootBeanDefinition寫入一個新的RootBeanDefinition,構造一個新的物件,名為advisorDefinition,即advisor定義 3、將advisorDefinition註冊到DefaultListableBeanFactory中
private AbstractBeanDefinition parseAdvice(
String aspectName,int order,Element aspectElement,Element adviceElement,ParserContext parserContext,List<BeanDefinition> beanDefinitions,List<BeanReference> beanReferences) {
try {
this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));
// create the method factory bean
// 建立方法工廠Bean的BeanDefinition物件:用於獲取Advice增強類的Method物件,<aop:brefore method="before">中的method
RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
// 設定MethodLocatingFactoryBean的targetBeanName為advice類的引用名稱,也就是<aop:aspect ref="myAdvice">中的myAdvice
methodDefinition.getPropertyValues().add("targetBeanName",aspectName);
// 設定MethodLocatingFactoryBean的methodName為<aop:after>標籤的method屬性值(也就是method="before"中的before,作為advice方法名稱)
methodDefinition.getPropertyValues().add("methodName",adviceElement.getAttribute("method"));
methodDefinition.setSynthetic(true);
// create instance factory definition
// 建立例項工廠BeanDefinition:用於建立增強類的例項,也就是<aop:aspect ref="myAdvice">中的myAdvice
RootBeanDefinition aspectFactoryDef =
new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
// 設定SimpleBeanFactoryAwareAspectInstanceFactory的aspectBeanName為advice類的引用名稱
aspectFactoryDef.getPropertyValues().add("aspectBeanName",aspectName);
aspectFactoryDef.setSynthetic(true);
//以上的兩個BeanDefinition的作用主要是通過反射呼叫Advice物件的指定方法
// method.invoke(obj,args)
// register the pointcut
// 通知增強類的BeanDefinition物件(核心),也就是一個<aop:before>等對應一個adviceDef
AbstractBeanDefinition adviceDef = createAdviceDefinition(
adviceElement,aspectName,order,methodDefinition,aspectFactoryDef,beanReferences);
// configure the advisor
// 通知器類的BeanDefinition物件,對應<aop:aspect>
RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
advisorDefinition.setSource(parserContext.extractSource(adviceElement));
// 給通知器類設定Advice物件屬性值
advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
advisorDefinition.getPropertyValues().add(
ORDER_PROPERTY,aspectElement.getAttribute(ORDER_PROPERTY));
}
// register the final advisor
// 將advisorDefinition註冊到IoC容器中
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
return advisorDefinition;
}
finally {
this.parseState.pop();
}
}
複製程式碼
至此對標籤的解析完畢
代理物件的建立分析
分析AOP代理物件建立之前,我們先來看下類的繼承結構圖:
我們來看AbstractAutoProxyCreator類,裡面有這些方法:
postProcessBeforeInitialization
postProcessAfterInitialization----AOP功能入口
postProcessBeforeInstantiation
postProcessAfterInstantiation
postProcessPropertyValues
複製程式碼
我們找到分析入口:AbstractAutoProxyCreator#postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(@Nullable Object bean,String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(),beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 使用動態代理技術,產生代理物件
// bean : 目標物件
// beanName :目標物件名稱
return wrapIfNecessary(bean,beanName,cacheKey);
}
}
return bean;
}
複製程式碼
進入AbstractAutoProxyCreator#wrapIfNecessary
這個方法主要做三件事:
- 查詢代理類相關的advisor物件集合,從IOC中查詢,因為上一步我們解析了配置檔案,並將相關資訊封裝了幾個物件,並儲存到了IOC容器中。
- 通過jdk動態代理或者cglib動態代理,產生代理物件
- 將代理型別放入快取中
protected Object wrapIfNecessary(Object bean,String beanName,Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
// Advice/Pointcut/Advisor/AopInfrastructureBean介面的beanClass不進行代理以及對beanName為aop內的切面名也不進行代理
// 此處可檢視子類複寫的shouldSkip()方法
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(),beanName)) {
this.advisedBeans.put(cacheKey,Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
// 查詢對代理類相關的advisor物件集合,此處就與point-cut表示式有關了
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(),null);
// 對相應的advisor不為空才採取代理
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey,Boolean.TRUE);
// 通過jdk動態代理或者cglib動態代理,產生代理物件
Object proxy = createProxy(
bean.getClass(),specificInterceptors,new SingletonTargetSource(bean));
// 放入代理型別快取
this.proxyTypes.put(cacheKey,proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey,Boolean.FALSE);
return bean;
}
複製程式碼
我們接著進入AbstractAutoProxyCreator#createProxy
protected Object createProxy(Class<?> beanClass,@Nullable String beanName,@Nullable Object[] specificInterceptors,TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory,beanClass);
}
// 建立代理工廠物件
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
//如果沒有使用CGLib代理
if (!proxyFactory.isProxyTargetClass()) {
// 是否可能使用CGLib代理
if (shouldProxyTargetClass(beanClass,beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
// 檢視beanClass對應的類是否含有InitializingBean.class/DisposableBean.class/Aware.class介面
// 無則採用JDK動態代理,有則採用CGLib動態代理
evaluateProxyInterfaces(beanClass,proxyFactory);
}
}
// 獲得所有關聯的Advisor集合(該分支待補充)
Advisor[] advisors = buildAdvisors(beanName,specificInterceptors);
proxyFactory.addAdvisors(advisors);
// 此處的targetSource一般為SingletonTargetSource
proxyFactory.setTargetSource(targetSource);
// 空的實現
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
// 是否設定預過濾模式,此處針對本文為true
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 獲取使用JDK動態代理或者cglib動態代理產生的物件
return proxyFactory.getProxy(getProxyClassLoader());
}
複製程式碼
我們進入ProxyFactory#getProxy
public Object getProxy(@Nullable ClassLoader classLoader) {
// 1、建立JDK方式的AOP代理或者CGLib方式的AOP代理
// 2、呼叫具體的AopProxy來建立Proxy代理物件
return createAopProxy().getProxy(classLoader);
}
複製程式碼
我們選擇jdk方式建立代理,因為我們是通過介面實現代理的。
我們進入:JdkDynamicAopProxy#getProxy
這個方法主要做兩件事:
- 獲取所有的代理介面
- 呼叫JDK動態代理
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
// 獲取完整的代理介面
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised,true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// 呼叫JDK動態代理方法
return Proxy.newProxyInstance(classLoader,proxiedInterfaces,this);
}
複製程式碼
至此生成代理類物件分析完畢
代理物件執行流程分析
我們來看一下JdkDynamicAopProxy,它實現了InvocationHandler,所以代理物件執行流程分析的入口我們已經找到了
入口:JdkDynamicAopProxy#invoke
這個方法主要做以下事情:
-
獲取針對該目標物件的所有增強器(advisor)
這些Advisor都是有順序的,他們會按照順序進行鏈式呼叫
-
如果呼叫鏈為空,則直接通過反射呼叫目標物件的方法,也就是不對方法進行任何的增強
-
建立ReflectiveMethodInvocation例項對呼叫鏈進行呼叫,開始執行AOP的攔截過程
public Object invoke(Object proxy,Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
//...省略若干程式碼
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
// 獲取目標物件
target = targetSource.getTarget();
// 獲取目標物件的型別
Class<?> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
// 獲取針對該目標物件的所有增強器(advisor),這些advisor都是有順序的,他們會按照順序進行鏈式呼叫
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);
// Check whether we have any advice. If we don't,we can fallback on direct
// reflective invocation of the target,and avoid creating a MethodInvocation.
// 檢查是否我們有一些通知。如果我們沒有,我們可以直接對目標類進行反射呼叫,避免建立MethodInvocation類
// 呼叫目標類的方法
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target,and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method,args);
// 通過反射呼叫目標物件的方法
retVal = AopUtils.invokeJoinpointUsingReflection(target,method,argsToUse);
}
else {
// We need to create a method invocation...
//我們需要建立一個方法呼叫
// proxy:生成的動態代理物件
// target:目標方法
// args: 目標方法引數
// targetClass:目標類物件
// chain: AOP攔截器執行鏈,是一個MethodInterceptor的集合
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy,target,args,targetClass,chain);
// Proceed to the joinpoint through the interceptor chain.
// 通過攔截器鏈進入連線點
// 開始執行AOP的攔截過程
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
複製程式碼
我們來看獲取呼叫鏈的過程:
進入到:AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method,@Nullable Class<?> targetClass) {
// 建立以方法為單位的快取key
MethodCacheKey cacheKey = new MethodCacheKey(method);
// 從快取中獲取指定方法的advisor集合
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
// 獲取目標類中指定方法的MethodInterceptor集合,該集合是由Advisor轉換而來
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this,targetClass);
this.methodCache.put(cacheKey,cached);
}
return cached;
}
複製程式碼
我們進入獲取MethodInterceptor集合:
DefaultAdviceChainFactory#getInterceptorsAndDynamicInterceptionAdvice
這裡主要做:
-
建立DefaultAdvisorAdapterRegistry例項,並建立MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter、ThrowsAdviceAdapter介面卡
這裡為啥需要介面卡呢?我們來看下面的類關係圖:
有些Advice和攔截器MethodInterceptor根本就沒有關係,此時就需要介面卡將他們之間產生關係。這裡用了介面卡模式,打個比方,我們買了好多型號的電腦,使用的電壓都不一樣,我們家的電壓是220v,那怎麼樣才能使我們的電腦能充上電呢,奈米就需要電源介面卡了。這樣講應該懂了吧
2.變數所有的Advisor集合,使用Pointcut類的ClassFilter().matches()進行匹配,還有使用Pointcut().getMethodMatcher()進行匹配,如果匹配上則將advisor轉成MethodInterceptor,
3.如果需要根據引數動態匹配(比如過載)則攔截器鏈中新增InterceptorAndDynamicMethodMatcher
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config,@Nullable Class<?> targetClass) {
// This is somewhat tricky... We have to process introductions first,
// but we need to preserve order in the ultimate list.
// advice介面卡註冊中心
// MethodBeforeAdviceAdapter:將Advisor適配成MethodBeforeAdvice
// AfterReturningAdviceAdapter:將Advisor適配成AfterReturningAdvice
// ThrowsAdviceAdapter: 將Advisor適配成ThrowsAdvice
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors();
// 返回值集合,裡面裝的都是Interceptor或者它的子類介面MethodInterceptor
List<Object> interceptorList = new ArrayList<>(advisors.length);
// 獲取目標類的型別
Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
// 是否有引介
Boolean hasIntroductions = null;
// 去產生代理物件的過程中,針對該目標方法獲取到的所有合適的Advisor集合
for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
// 如果該Advisor可以對目標類進行增強,則進行後續操作
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
// 獲取方法介面卡,該方法匹配器可以根據指定的切入點表示式進行方法匹配
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
boolean match;
if (mm instanceof IntroductionAwareMethodMatcher) {
if (hasIntroductions == null) {
hasIntroductions = hasMatchingIntroductions(advisors,actualClass);
}
match = ((IntroductionAwareMethodMatcher) mm).matches(method,actualClass,hasIntroductions);
}
else {
match = mm.matches(method,actualClass);
}
if (match) {
// 將advisor轉成MethodInterceptor
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
// MethodMatcher介面通過過載定義了兩個matches()方法
// 兩個引數的matches() 被稱為靜態匹配,在匹配條件不是太嚴格時使用,可以滿足大部分場景的使用
// 稱之為靜態的主要是區分為三個引數的matches()方法需要在執行時動態的對引數的型別進行匹配
// 兩個方法的分界線就是boolean isRuntime()方法
// 進行匹配時先用兩個引數的matches()方法進行匹配,若匹配成功,則檢查boolean isRuntime()的返回值若為
// true,則呼叫三個引數的matches()方法進行匹配(若兩個引數的都匹配不中,三個引數的必定匹配不中)
// 需要根據引數動態匹配(比如過載)
if (mm.isRuntime()) {
// Creating a new object instance in the getInterceptors() method
// isn't a problem as we normally cache created chains.
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor,mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
return interceptorList;
}
複製程式碼
我們返回到JdkDynamicAopProxy#invoke
進入到ReflectiveMethodInvocation#proceed()中:
這個是呼叫鏈的執行過程
-
如果執行到鏈條的末尾,則直接呼叫連線點,即直接呼叫目標方法
-
最終會呼叫MethodInterceptor的invoke方法,本文配置檔案會呼叫AspectJAfterAdvice、MethodBeforeAdviceInterceptor
我們來看AspectJAfterAdvice:裡面的方法呼叫是這樣,又繼續呼叫呼叫鏈
public Object invoke(MethodInvocation mi) throws Throwable { try { return mi.proceed(); } finally { invokeAdviceMethod(getJoinPointMatch(),null,null); } } 複製程式碼
我們看:MethodBeforeAdviceInterceptor(前置通知攔截器)
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(),mi.getArguments(),mi.getThis());
return mi.proceed();
}
複製程式碼
是先執行目標方法然後再呼叫下一個呼叫鏈
這就保證了呼叫的順序符號前置通知在目標方法前呼叫,最終通知是在目標方法執行後通知,同時也說明瞭配置檔案中<aop:before />
、<aop:after method="after"/>的順序不會影響最終執行的順序,順序是通過呼叫鏈來保證的。
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
// 如果執行到鏈條的末尾, 則直接呼叫連線點方法 即直接呼叫目標方法
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 獲取集合中的MethodInterceptor
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 如果是InterceptorAndDynamicMethodMatcher型別(動態匹配)
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
// 這裡每一次都去匹配是否適用於這個目標方法
if (dm.methodMatcher.matches(this.method,this.arguments)) {
// 如果匹配則直接呼叫MethodInterceptor的invoke方法
// 注意這裡傳入的引數是this,我們下面看一下ReflectiveMethodInvocation的型別
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
// 如果不適用於此目標方法,則繼續執行下一鏈條
// 遞迴呼叫
return proceed();
}
}
else {
// It's an interceptor,so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
// 說明是適用於此目標方法的,直接呼叫MethodInterceptor的invoke方法
// 傳入this即ReflectiveMethodInvocation例項
// 傳入this進入 這樣就可以形成一個呼叫的鏈條了
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
複製程式碼
經過長久的書寫,終於成文,由於水平有限,不能詳盡各個方面,忘讀者見諒,可以和我交流。