原創作品,可以轉載,但是請標注出處地址:https://www.cnblogs.com/V1haoge/p/9560803.html
一、概述
Spring的兩大特性:IOC和AOP。
AOP是面向切面編程,Spring內置了自己實現的基於動態代理技術的AOP,同時還支持成熟的AspectJ框架,我們這里主要講述的還是內置的基於動態代理的AOP實現。因為面對一些普通的需求,Spring內置的AOP已經綽綽有余。
AOP一般用於增強原來的代碼的功能,這種增強體現在輔助方面,比如安全、日志、事務等。
二、術語
1、連接點(JoinPoint)
連接點就是具體的代碼中的切入點,指的是具體的一處代碼位置。
2、切點(PointCut)
切點是對一系列代表同種功能(目的)的切入點(連接點)的統稱,切點不是一個點,而是代表某一功能的一系列連接點的集合。
3、通知(Advice)
通知就是我們要在切點執行的操作,就是我們要實現的目的,是要實現的功能的代碼實現。一般通知又稱為增強,所謂增強就是對原來功能的基礎上添加新功能,進行功能增強。
4、切面(Aspect)
切面是通知和切點的綜合體,定義好了一個切面,那么我們就知道,這個AOP要實現的功能是什么,需要切入到程序中的那些地方。抽象層次中,切面是代碼功能的橫切面,這個橫切面的位置就是切點、這個切面的功能就是通知。
5、織入(Weaving)
織入就是切面作用到目標代碼中的方式,Spring內置的AOP采用的是動態代理的方式來織入,還可以采用編譯器織入和加載期織入,后兩者分別需要特定的編譯器和類加載器來完成,后兩種方式是AspectJ所支持的織入方式。
6、引介(Introduction)
引介是另一種類型的增強,它可以為類添加一些屬性和方法。它和通知是並列的兩種不同的增強。
7、目標對象(Target)
目標對象就是我們想要織入的對象,一般不會是一個,通常是一批符合條件的對象。
8、代理(Proxy)
代理就好理解了,Spring內置的AOP就是通過動態代理的方式實現織入的,創建目標對象的代理類,在代理類中執行通知的內容,然后在合適的位置調用目標對象的方法,來達到織入的目的。
三、Spring AOP概述
Spring AOP是基於動態代理技術實現的切面編程,代理技術包括JDK動態代理和CGLIB動態代理,前者基於接口實現,后者基於類實現。
1、Spring中使用AOP技術的方式:
1)定義切面類,使用@Aspect注解
2)在切面類中定義切點方法,使用@PointCut注解
3)在切面類中定義通知方法,使用@Before、@After、@Around等注解
4)在通知方法的注解中使用切點方法
5)在切面類上加設注解@Component
6)啟動AOP功能,兩種方式:原始的XML配置方式和注解方式
XMl方式:<aop:aspectj-autoproxy/>
注解方式:@EnableAspectJAutoProxy
配置方式:spring.auto.proxy=true
2、Spring AOP支持的增強類型
通知增強:Advice
前置通知:MethodBeforeAdvice-在連接點之前執行
后置通知:AfterReturningAdvice-在連接點正常執行完后執行,如果還未到連接點就異常,不會執行
環繞通知:AroundAdvice-在連接點前后執行
異常通知:AfterThrowingAdvice-在連接點拋出異常后執行
finally通知:AfterAdvice-最終執行,無論是否異常,方法執行結束都會執行
引介增強:IntroductionInterceptor->DelegatingIntroductionInterceptor
前五種很好理解,重點是最后一種引介增強,這種增強並不是針對方法的增強,而是針對類的增強,它會為目標添加屬性和方法,比如它可以為目標類添加一個接口的實現。目標類原本沒有實現某個接口,但是我們可以使用引介增強的方式為目標類創建一個實現該接口的代理,在這個代理中可以使用接口的功能來作用於目標對象。
3、原理圖:(右鍵打開看大圖)
四、源碼分析
1、入口
1.1 入口一:<aop:aspectj-autoproxy/>
源碼1-來自:AopNamespaceHandler
1 @Override 2 public void init() { 3 // In 2.0 XSD as well as in 2.1 XSD. 4 registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser()); 5 registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser()); 6 registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); 7 8 // Only in 2.0 XSD: moved to context namespace as of 2.1 9 registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); 10 }
上面源碼就是針對XML配置中<aop:XXX/>配置的解析,紅色部分正是針對<aop:aspectj-autoproxy/>的解析,如果配置了aspectj-autoproxy,則注冊Bean定義解析器:AspectJAutoProxyBeanDefinitionParser。
AspectJAutoProxyBeanDefinitionParser是一個實現了BeanDefinitionParser接口的類,專門用於解析切面自動代理的Bean定義的解析工作,重點在其parse方法。
源碼2-來自:AspectJAutoProxyBeanDefinitionParser
1 class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser { 2 3 @Override 4 @Nullable 5 public BeanDefinition parse(Element element, ParserContext parserContext) { 6 // 1-注冊AnnotationAwareAspectJAutoProxyCreator 7 AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element); 8 // 2-擴展BeanDefinition 9 extendBeanDefinition(element, parserContext); 10 return null; 11 } 12 13 private void extendBeanDefinition(Element element, ParserContext parserContext) { 14 // 獲取BeanName為internalAutoProxyCreator的BeanDefinition,其實就是之前注冊的自動代理構建器 15 BeanDefinition beanDef = 16 parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME); 17 if (element.hasChildNodes()) { 18 // 如果當前元素有子節點,則給上面獲取的Bean定義添加子節點中明確定義的類型值(填充BeanDefinition) 19 addIncludePatterns(element, parserContext, beanDef); 20 } 21 } 22 23 private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) { 24 ManagedList<TypedStringValue> includePatterns = new ManagedList<>(); 25 NodeList childNodes = element.getChildNodes();//獲取子節點 26 for (int i = 0; i < childNodes.getLength(); i++) { 27 // 遍歷子節點,獲取子節點中name屬性值,封裝到TypeStringValue中 28 // 在上下文中提取子節點includeElement的元數據保存到TypedStringValue的source屬性中 29 // 最后封裝好的TypeStringValue保存到includePatterns列表中 30 Node node = childNodes.item(i); 31 if (node instanceof Element) { 32 Element includeElement = (Element) node; 33 TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name")); 34 valueHolder.setSource(parserContext.extractSource(includeElement)); 35 includePatterns.add(valueHolder); 36 } 37 } 38 if (!includePatterns.isEmpty()) { 39 // 從解析上下文parserContext中提取指定節點element的元數據保存到includePatterns的source屬性中, 40 // 然后將includePatterns保存到BeanDefinition的propertyValues屬性中 41 includePatterns.setSource(parserContext.extractSource(element)); 42 beanDef.getPropertyValues().add("includePatterns", includePatterns); 43 } 44 } 45 46 }
上面代碼中有兩個重點,首先就是registerAspectJAnnotationAutoProxyCreatorIfNecessary方法調用,用於注冊AnnotationAwareAspectJAutoProxyCreator構建器;另一點就是在構建器注冊完成后,為其填充一些必要內容,這些內容為XML配置中子節點的配置內容,具體內容參照源碼,這里重點看看第一步,注冊構建器的源碼:
源碼3-來自:AopNamespaceUtils
1 public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( 2 ParserContext parserContext, Element sourceElement) { 3 // 1-注冊或升級AnnotationAwareAspectJAutoProxyCreator 4 // parserContext.getRegistry()獲取到的是BeanDefinitionRegistry注冊器,第二個參數是提取的指定元素的元數據 5 BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary( 6 parserContext.getRegistry(), parserContext.extractSource(sourceElement)); 7 // 2-校驗並設置是否適用基於CGLIB的動態代理實現AOP,和是否要暴露代理類 8 useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); 9 // 3-注冊成組件 10 registerComponentIfNecessary(beanDefinition, parserContext); 11 } 12 13 private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) { 14 if (sourceElement != null) { 15 boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));//獲取XML中設置的proxy-target-class屬性的值,解析為Boolean值 16 if (proxyTargetClass) { 17 // 如果為true,則強制自動代理構建器使用基於類的動態代理CGLIB,需要將屬性設置到自動代理構建器的BeanDefinition中 18 AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); 19 } 20 boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));//獲取XML中配置的expose-proxy屬性的值,同樣解析為Boolean值 21 if (exposeProxy) { 22 // 如果為true,強制自動代理構建器暴露代理類,需要將屬性設置到自動代理構建器的BeanDefinition中 23 AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); 24 } 25 } 26 } 27 28 private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) { 29 if (beanDefinition != null) { 30 // 將自動代理構建器包裝成為一個Bean組件定義。 31 // Bean組件定義是將一個BeanDefinition中包含的所有的屬性的值(可能為一個BeanDefinition或者BeanReference)全部封裝起來成為一個組件包,然后將其注冊到解析上下文中 32 BeanComponentDefinition componentDefinition = 33 new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME); 34 parserContext.registerComponent(componentDefinition); 35 } 36 }
registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中主要做了三件事情:
1-注冊構建器
2-配置屬性
3-組件注冊
針對第2和第3,在源碼注釋中解釋的很清楚啦,主要看看第一步,繼續進行構建器的注冊:
源碼4-來自:AopConfigUtils
1 public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { 2 if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { 3 //如果構建器已經加載,獲取其BeanDefinition,添加屬性proxyTargetClass,值為true 4 BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); 5 definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE); 6 } 7 } 8 9 public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) { 10 if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { 11 //如果構建器已經加載,獲取其BeanDefinition,添加屬性exposeProxy,值為true 12 BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); 13 definition.getPropertyValues().add("exposeProxy", Boolean.TRUE); 14 } 15 } 16 17 @Nullable 18 private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, 19 @Nullable Object source) { 20 21 Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); 22 23 if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { 24 // 1-如果internalAutoProxyCreator已經被注冊那么比較新舊自動代理構建器類在列表中的優先級,如果已注冊的構建器優先級低,則替換為給定的新構建器 25 BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); 26 if (!cls.getName().equals(apcDefinition.getBeanClassName())) { 27 int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); 28 int requiredPriority = findPriorityForClass(cls); 29 if (currentPriority < requiredPriority) { 30 apcDefinition.setBeanClassName(cls.getName()); 31 } 32 } 33 return null; 34 } 35 // 尚未注冊internalAutoProxyCreator的情況下,將給定的構建器包裝成RootBeanDefinition,然后注冊這個BeanDefinition 36 RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); 37 // 把元數據保存到BeanDefinition中 38 beanDefinition.setSource(source); 39 // 設置為最高優先值 40 beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); 41 // 設置為基礎角色 42 beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); 43 // 2-以internalAutoProxyCreator為beanName注冊當前BeanDefinition(AnnotationAwareAspectJAutoProxyCreator類) 44 registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); 45 return beanDefinition; 46 }
源碼4中前兩個方法時源碼3中第二步里面配置屬性時調用的方法,在此給出。
源碼4的1-中是在已存在一個自動代理構建器的情況下,將其與新的給定的AnnotationAwareAspectJAutoProxyCreator構建器的優先級進行比對,取優先極高的。
最后的registerBeanDefinition方法用於注冊BeanDefinition。至此,自動代理構建器加載完畢。
1.2 入口二:@EnableAspectJAutoProxy
源碼5-來自:EnableAspectJAutoProxy
1 @Target(ElementType.TYPE) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Import(AspectJAutoProxyRegistrar.class) 5 public @interface EnableAspectJAutoProxy { 6 7 /** 8 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed 9 * to standard Java interface-based proxies. The default is {@code false}. 10 */ 11 boolean proxyTargetClass() default false; 12 13 /** 14 * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal} 15 * for retrieval via the {@link org.springframework.aop.framework.AopContext} class. 16 * Off by default, i.e. no guarantees that {@code AopContext} access will work. 17 * @since 4.3.1 18 */ 19 boolean exposeProxy() default false; 20 21 }
源碼中重點關注@Import(AspectJAutoProxyRegistrar.class),很明顯這個注解導入了一個新類:AspectJAutoProxyRegistrar。
使用@Import注解導入的方式可以將一個類注冊到BeanFactory中。
源碼6-來自AspectJAutoProxyRegistrar
1 class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { 2 3 @Override 4 public void registerBeanDefinitions( 5 AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 6 // 1-注冊或升級自動代理構建器 7 AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); 8 // 2-封裝注解屬性,並根據屬性進行配置 9 AnnotationAttributes enableAspectJAutoProxy = 10 AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); 11 if (enableAspectJAutoProxy != null) { 12 if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { 13 AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); 14 } 15 if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { 16 AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); 17 } 18 } 19 } 20 21 }
我們可以從源碼5中看到注解@EnableAspectJAutoProxy內部有兩個屬性設置proxyTargetClass和exposeProxy,這個之前的入口一中源碼3里面的2-中的設置是一樣的,即我們在XML啟動AOP的時候也可以設置這兩個值。
proxyTargetClass屬性表示是否適用基於類的的動態代理CGLIB來創建代理類。true表示使用,false表示不使用,默認是false。
exposeProxy屬性表示是否暴露生成的代理類,暴露就是可手動調用,最常見的情況如,在一個類中使用this調用帶有@Transactional注解的方法,你會發現事務是不生效的,這時候我們就可以將生成的代理暴露出來手動調用代理類來保證事務生效:
如下例子中:
1 public interface TestService { 2 void testMethod1(); 3 void testMethod2(); 4 }
1 public class TestServiceImpl { 2 3 @Transactional 4 public void testMethod1(){ 5 //some transaction operate 6 } 7 8 public void testMethod2(){ 9 this.testMethod1(); 10 } 11 }
在testMethod2中以this.testMethod1()方式調用帶事務注解的testMethod1方法時,其事務是不生效的。修改方式就是將exposeProxy設置為true,然后修改調用方式為:
1 ((TestService)AopContext.currnetProxy()).testMethod1();
源碼6中1-調用的是AopConfigUtils中的方法:
源碼7-來自:AopConfigUtils
1 @Nullable 2 public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) { 3 return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null); 4 }
然后它再調用的就是源碼4中的源碼了,到這里調用的就是公共部分啦。那么這一部分也就到此為止了,其余見源碼4之后的部分。
到此兩個主要的入口都分析完畢,入口的主要作用就是注冊或者升級自動代理構建器,因為之后AOP的操作基本都要依靠這個構建器來完成。
2、創建AOP代理
我們的重點就是構建器AnnotationAwareAspectJAutoProxyCreator,分析下其繼承結構,你會發現它實現了BeanPostProcessor接口。
BeanPostProcessor接口有兩個方法:
postProcessBeforeInitialization:Bean實例初始化前調用
postProcessAfterInitialization:Bean實例初始化之后調用
然后我們在其繼承體系中尋找實現這兩個方法的類,一番尋找后發現:AbstractAutoProxyCreator
源碼8-來自:AbstractAutoProxyCreator
1 @Override 2 public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException { 3 if (bean != null) { 4 // 1-生成緩存key 5 Object cacheKey = getCacheKey(bean.getClass(), beanName); 6 // 校驗該key是否已存在於earlyProxyReferences緩存 7 if (!this.earlyProxyReferences.contains(cacheKey)) { 8 // 2-執行創建代理對象 9 return wrapIfNecessary(bean, beanName, cacheKey); 10 } 11 } 12 return bean; 13 }
我們發現了這個方法的實現,這個方法會在構建器Bean初始化之后被調用。我們看看它做了啥?
重點就是wrapIfNecessary方法,這個方法用於代理對象的生成。
源碼9-來自:AbstractAutoProxyCreator
1 protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { 2 if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { 3 // 已經處理過,直接返回 4 return bean; 5 } 6 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { 7 // 如果不需要增強,直接返回 8 return bean; 9 } 10 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { 11 // 檢測目標類是否是AOP的基礎設施類,基礎設施類包括Advice、Pointcut、Advisor、AopInfrastructureBean,或者是否需要跳過代理,如果是則將其設置為無需增強 12 this.advisedBeans.put(cacheKey, Boolean.FALSE); 13 return bean; 14 } 15 16 // Create proxy if we have advice. 17 // 1-獲取針對當前Bean的增強 18 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); 19 if (specificInterceptors != DO_NOT_PROXY) { 20 // 如果獲取到增強則執行下面的創建代理 21 this.advisedBeans.put(cacheKey, Boolean.TRUE); 22 // 2-創建代理 23 Object proxy = createProxy( 24 bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); 25 // 緩存代理類 26 this.proxyTypes.put(cacheKey, proxy.getClass()); 27 return proxy; 28 } 29 // 如果未獲取到增強,則設置跳過代理 30 this.advisedBeans.put(cacheKey, Boolean.FALSE); 31 return bean; 32 }
上面的源碼中除了進行一些必要的校驗之外,主要的邏輯是獲取針對當前Bean的增強和創建代理這兩步。
首先來看下如何獲取增強
源碼10-來自:AbstractAdvisorAutoProxyCreator
1 //獲取通知(增強) 2 @Override 3 @Nullable 4 protected Object[] getAdvicesAndAdvisorsForBean( 5 Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) { 6 7 List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName); 8 if (advisors.isEmpty()) { 9 return DO_NOT_PROXY; 10 } 11 return advisors.toArray(); 12 }
源碼11-來自:AbstractAdvisorAutoProxyCreator
1 protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { 2 // 1-先獲取所有的增強器列表 3 List<Advisor> candidateAdvisors = findCandidateAdvisors(); 4 // 2-獲取應用到當前目標類的增強器列表 5 List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); 6 //在這個方法中進行了一個操作,那就是在通知鏈的首位添加了一個ExposeInvocationInteceptor,這個普通攔截器的作用是為了暴露方法調用,將會第一個被調用,並將MethodInvocation添加到ThreadLocal中 7 extendAdvisors(eligibleAdvisors); 8 if (!eligibleAdvisors.isEmpty()) { 9 eligibleAdvisors = sortAdvisors(eligibleAdvisors);//排序 10 } 11 return eligibleAdvisors; 12 }
上面的源碼我們重點關注findCandidateAdvisors方法和findAdvisorsThatCanApply方法,分別用於找出BeanFactory中所有的可用的增強器,和從可用增強器中找出作用於目標類的增強器。
另外,此處還有一個排序方法,那么這個排序的作用何在呢?此處排序是為了后面通知鏈的執行順序而進行的,具體內容,容后詳述。
至於extendAdvistors方法,這個方法的目的就是為了將ExposeInvocationInterceptor攔截器添加到通知連中,並放在首位,在通知鏈被調用的時候會被優先執行,將MethodInvocation放到ThreadLocal中。
最后返回的eligibleAdvisors其實就已經是完整的通知鏈了,然后一步步返回,進行代理的創建。
源碼12-來自:AbstractAdvisorAutoProxyCreator
1 protected List<Advisor> findCandidateAdvisors() { 2 Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available"); 3 return this.advisorRetrievalHelper.findAdvisorBeans(); 4 }
源碼13-來自:BeanFactoryAdvisorRetrievalHelper
1 public List<Advisor> findAdvisorBeans() { 2 // Determine list of advisor bean names, if not cached already. 3 String[] advisorNames = null; 4 synchronized (this) { 5 advisorNames = this.cachedAdvisorBeanNames; 6 if (advisorNames == null) { 7 // Do not initialize FactoryBeans here: We need to leave all regular beans 8 // uninitialized to let the auto-proxy creator apply to them! 9 // 1-獲取當前BeanFactory及其繼承體系中的容器中所有的Advisor類型的Bean的BeanName數組,排除掉FactoryBeans 10 advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( 11 this.beanFactory, Advisor.class, true, false); 12 this.cachedAdvisorBeanNames = advisorNames; 13 } 14 } 15 if (advisorNames.length == 0) { 16 return new LinkedList<>(); 17 } 18 19 List<Advisor> advisors = new LinkedList<>(); 20 for (String name : advisorNames) { 21 if (isEligibleBean(name)) { 22 // 2-再排除創建中的Bean 23 if (this.beanFactory.isCurrentlyInCreation(name)) { 24 if (logger.isDebugEnabled()) { 25 logger.debug("Skipping currently created advisor '" + name + "'"); 26 } 27 } 28 else { 29 try { 30 // 3-把剩下的Bean添加到通知(增強器)列表中 31 advisors.add(this.beanFactory.getBean(name, Advisor.class)); 32 } 33 catch (BeanCreationException ex) { 34 Throwable rootCause = ex.getMostSpecificCause(); 35 if (rootCause instanceof BeanCurrentlyInCreationException) { 36 BeanCreationException bce = (BeanCreationException) rootCause; 37 String bceBeanName = bce.getBeanName(); 38 if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) { 39 if (logger.isDebugEnabled()) { 40 logger.debug("Skipping advisor '" + name + 41 "' with dependency on currently created bean: " + ex.getMessage()); 42 } 43 // Ignore: indicates a reference back to the bean we're trying to advise. 44 // We want to find advisors other than the currently created bean itself. 45 continue; 46 } 47 } 48 throw ex; 49 } 50 } 51 } 52 } 53 return advisors; 54 }
這段源碼看起來挺復雜,我們去掉一些不必要的內容,總結起來,總共三件事:
1-獲取容器中所有的Advisor,排除FactoryBeans
2-再排除創建中的Bean
3-將剩余的Advisor通過getBean創建Bean實例並添加到列表中返回
至於再下一步的邏輯就不再探尋啦。
然后是源碼11中的2-findAdvisorsThatCanApply方法
源碼14-來自:AbstractAdvisorAutoProxyCreator
1 protected List<Advisor> findAdvisorsThatCanApply( 2 List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) { 3 4 // 將代理的目標Bean的beanName設置到ThreadLocal中 5 ProxyCreationContext.setCurrentProxiedBeanName(beanName); 6 try { 7 // 1-在候選增強器中找出可作用於目標Bean的增強器 8 return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); 9 } 10 finally { 11 ProxyCreationContext.setCurrentProxiedBeanName(null); 12 } 13 }
重點操作就是AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);用於找出能作用於目標Bean的增強器列表
源碼15-來自:AopUtils
1 public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) { 2 if (candidateAdvisors.isEmpty()) { 3 return candidateAdvisors; 4 } 5 List<Advisor> eligibleAdvisors = new LinkedList<>(); 6 for (Advisor candidate : candidateAdvisors) { 7 if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { 8 // 1-首先添加引介增強 9 eligibleAdvisors.add(candidate); 10 } 11 } 12 boolean hasIntroductions = !eligibleAdvisors.isEmpty(); 13 for (Advisor candidate : candidateAdvisors) { 14 if (candidate instanceof IntroductionAdvisor) { 15 // already processed 16 //跳過引介增強 17 continue; 18 } 19 //校驗其余增強器是否能應用到目標Bean 20 if (canApply(candidate, clazz, hasIntroductions)) { 21 // 2-添加通知增強 22 eligibleAdvisors.add(candidate); 23 } 24 } 25 return eligibleAdvisors; 26 }
源碼中可以看出來,這里針對了兩種類型的增強器(引介增強和通知增強)分別進行了處理,首先處理了引介增強,然后是通知增強。
這里有兩個canApply方法,第一個用於判斷引介增強是否能作用到目標類,第二個用於判斷通知增強是否能作用到目標類。
其實第一個是通過調用第二個實現的。
我覺得我們可以簡單看看其實現邏輯:
源碼16-來源:AopUtils
//用於校驗引介增強 public static boolean canApply(Advisor advisor, Class<?> targetClass) { return canApply(advisor, targetClass, false); } //用於校驗通知增強 public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { if (advisor instanceof IntroductionAdvisor) { return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);// 針對引介增強的類級匹配校驗 } else if (advisor instanceof PointcutAdvisor) { PointcutAdvisor pca = (PointcutAdvisor) advisor; return canApply(pca.getPointcut(), targetClass, hasIntroductions);// 針對通知增強的匹配校驗 } else { // It doesn't have a pointcut so we assume it applies. return true; } }
// 針對通知增強的匹配校驗 public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) { Assert.notNull(pc, "Pointcut must not be null"); if (!pc.getClassFilter().matches(targetClass)) { return false;// 針對通知增強的類級匹配校驗,如果類級校驗不通過,直接駁回 } MethodMatcher methodMatcher = pc.getMethodMatcher();// 獲取方法匹配器 if (methodMatcher == MethodMatcher.TRUE) {// 獲取方法匹配器匹配的是所有方法,這里直接返回true,不必要做校驗 // No need to iterate the methods if we're matching any method anyway... return true; } IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; if (methodMatcher instanceof IntroductionAwareMethodMatcher) { introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } Set<Class<?>> classes = new LinkedHashSet<>(); if (!Proxy.isProxyClass(targetClass)) { classes.add(ClassUtils.getUserClass(targetClass)); } classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); for (Class<?> clazz : classes) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); for (Method method : methods) { if (introductionAwareMethodMatcher != null ? introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) : methodMatcher.matches(method, targetClass)) {
// 方法匹配校驗 return true; } } } return false; }
上面的源碼中用於判斷增強是否與給定的類匹配,用了多個matches方法,包括ClassFilter的matches方法,IntroductionAwareMethodMatcher的matches方法和MethodMatcher中的matches方法。
第一個matches匹配的是類,第二、三個matches匹配的是方法。其中引介增強針對的是類,所以其校驗僅僅使用類級匹配進行校驗,但是通知增強針對的是類中的方法,需要進行類和方法的雙重匹配校驗。
然后返回到源碼9中,我們開始生成代理:
源碼17-來自:AbstractAutoProxyCreator
1 protected Object createProxy(Class<?> beanClass, @Nullable String beanName, 2 @Nullable Object[] specificInterceptors, TargetSource targetSource) { 3 4 if (this.beanFactory instanceof ConfigurableListableBeanFactory) { 5 //暴露目標類,將其保存到BeanDefinition中 6 AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); 7 } 8 9 //創建一個新的代理工廠,並為其拷貝當前類中的相關配置屬性 10 ProxyFactory proxyFactory = new ProxyFactory(); 11 proxyFactory.copyFrom(this); 12 13 if (!proxyFactory.isProxyTargetClass()) { 14 //校驗proxyTargetClass設置,如果設置不是直接代理目標類,則采用默認的JDK動態代理指定接口 15 if (shouldProxyTargetClass(beanClass, beanName)) { 16 //校驗該Bean的BeanDefinition中的preserveTargetClass屬性,是否被代理工廠設置為true,如果設置為true,則表示代理工廠希望代理類可以強轉為目標類 17 proxyFactory.setProxyTargetClass(true); 18 } 19 else { 20 //否則表示基於接口創建代理 21 evaluateProxyInterfaces(beanClass, proxyFactory); 22 } 23 } 24 25 //將攔截器封裝成通知 26 Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); 27 proxyFactory.addAdvisors(advisors);//加入增強器 28 proxyFactory.setTargetSource(targetSource);//設置要代理的類 29 customizeProxyFactory(proxyFactory);//子類定制代理 30 //用於控制代理工廠被配置之后,是否還允許修改通知,默認為false(表示不允許修改) 31 proxyFactory.setFrozen(this.freezeProxy); 32 if (advisorsPreFiltered()) { 33 proxyFactory.setPreFiltered(true); 34 } 35 //創建代理 36 return proxyFactory.getProxy(getProxyClassLoader()); 37 }
源碼中執行了一大串的工作,都在為最后的創建代理做准備:
源碼18-來自:ProxyFactory
1 public Object getProxy(@Nullable ClassLoader classLoader) { 2 //創建AOP代理,並獲取代理對象 3 return createAopProxy().getProxy(classLoader); 4 }
源碼19-來自:ProxyCreatorSupport
1 protected final synchronized AopProxy createAopProxy() { 2 if (!this.active) { 3 activate();//激活開關 4 } 5 // 獲取AOP代理工廠,使用AOP代理工廠創建AOP代理 6 return getAopProxyFactory().createAopProxy(this); 7 } 8 9 private void activate() { 10 this.active = true; 11 // 回調監聽器的activated方法 12 for (AdvisedSupportListener listener : this.listeners) { 13 listener.activated(this); 14 } 15 } 16 17 public AopProxyFactory getAopProxyFactory() { 18 return this.aopProxyFactory; 19 }
重點在createAopProxy方法:
源碼20-來自:DefaultAopProxyFactory
1 @Override 2 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { 3 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { 4 // 如果代理需要執行優化或者proxyTargetClass=true或者不存在代理接口 5 Class<?> targetClass = config.getTargetClass(); 6 if (targetClass == null) { 7 throw new AopConfigException("TargetSource cannot determine target class: " + 8 "Either an interface or a target is required for proxy creation."); 9 } 10 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { 11 //如果目標類是接口或者是動態生成的代理類,則使用JDK動態代理 12 return new JdkDynamicAopProxy(config); 13 } 14 //創建CGLIB動態AOP代理對象 15 return new ObjenesisCglibAopProxy(config); 16 } 17 else { 18 //創建JDK動態AOP代理對象 19 return new JdkDynamicAopProxy(config); 20 } 21 }
至此,代理對象生成,至於是使用JDK動態代理,還是Cglib動態代理,機理如下:
如果目標類實現了接口,默認使用JDK動態代理
如果目標類實現了接口,可以強制使用Cglib動態代理
如果目標沒有實現接口,必須采用Cglib動態代理
至於如何強制使用Cglib動態代理:
首先需要添加CGLIB庫,然后設置proxyTargetClass置為true,進行強制使用基於類的CGLIB動態代理。
JDK動態代理和Cglib動態代理的區別:
JDK動態代理只能基於接口生成代理,方式是通過實現JDK提供的InvocationHandler接口中的invoke方法來實現針對目標類指定方法的代理調用。
CGLIB可以基於類生成代理,方式是通過對目標類生成一個子類,覆蓋其中的方法。
返回到源碼18中,創建了AOP代理之后,執行其getProxy方法:(我們看下JDK動態代理的實現)
源碼21-來自:JdkDynamicAopProxy
1 @Override 2 public Object getProxy(@Nullable ClassLoader classLoader) { 3 if (logger.isDebugEnabled()) { 4 logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); 5 } 6 // 1-獲取用於代理的全部接口的集合(數組) 7 Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); 8 // 2-查找接口集合中可能定義的equals方法獲取hashCode方法 9 findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); 10 // 3-創建指定接口的代理實例 11 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); 12 }
上面的代理主要做了三件事情:
1-首先獲取用於代理的全部接口集;
2-然后查找該接口集中有無定義equals和hashCode方法;
3-最后執行代理實例的創建。
首先看下第一件事情:completeProxiedInterfaces
源碼22-來自:AopProxyUtils
1 // 獲取用於代理的接口的集合 2 static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) { 3 // 獲取配置中所有的接口 4 Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces(); 5 if (specifiedInterfaces.length == 0) { 6 // No user-specified interfaces: check whether target class is an interface. 7 Class<?> targetClass = advised.getTargetClass(); 8 if (targetClass != null) { 9 if (targetClass.isInterface()) { 10 advised.setInterfaces(targetClass); 11 } 12 else if (Proxy.isProxyClass(targetClass)) { 13 advised.setInterfaces(targetClass.getInterfaces()); 14 } 15 specifiedInterfaces = advised.getProxiedInterfaces(); 16 } 17 } 18 // 將SpringProxy、Advised、DecoratingProxy三個接口添加到接口集中 19 boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class); 20 boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class); 21 boolean addDecoratingProxy = (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)); 22 int nonUserIfcCount = 0; 23 if (addSpringProxy) { 24 nonUserIfcCount++; 25 } 26 if (addAdvised) { 27 nonUserIfcCount++; 28 } 29 if (addDecoratingProxy) { 30 nonUserIfcCount++; 31 } 32 Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount]; 33 System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length); 34 int index = specifiedInterfaces.length; 35 if (addSpringProxy) { 36 proxiedInterfaces[index] = SpringProxy.class; 37 index++; 38 } 39 if (addAdvised) { 40 proxiedInterfaces[index] = Advised.class; 41 index++; 42 } 43 if (addDecoratingProxy) { 44 proxiedInterfaces[index] = DecoratingProxy.class; 45 } 46 return proxiedInterfaces; 47 }
下面我們再看看之前的第二步:findDefinedEqualsAndHashCodeMethods
源碼23-來自:JdkDynamicAopProxy
1 private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) { 2 for (Class<?> proxiedInterface : proxiedInterfaces) { 3 Method[] methods = proxiedInterface.getDeclaredMethods(); 4 for (Method method : methods) { 5 // 遍歷每一個接口,再獲取其中的方法進行遍歷,逐個校驗其是否是equals方法,或者hashCode方法,只有當二者都被定義之后校驗才會結束,否則一直進行下去 6 if (AopUtils.isEqualsMethod(method)) { 7 this.equalsDefined = true; 8 } 9 if (AopUtils.isHashCodeMethod(method)) { 10 this.hashCodeDefined = true; 11 } 12 if (this.equalsDefined && this.hashCodeDefined) { 13 return; 14 } 15 } 16 } 17 }
最后就是我們的重點步驟:Proxy.newProxyInstance(classLoader, proxiedInterfaces, this),這個操作大家都不會陌生,這是JDK動態代理創建代理類的通用方式。這個方法的參數列表中最后一個是一個大家都很熟悉的家伙:InvocationHandler,我們都知道JDK動態代理的執行邏輯都是在一個實現了InvocationHandler接口中的invoke方法中,這里傳入this,表示當前實例,代表當前實例所屬類應該實現了InvocationHandler接口,之前已經成功創建了JDK動態代理對象,那么當我們發生對指定目標方法的調用時,就會觸發JdkDynamicAopProxy中的invoke方法:
源碼24-來自:JdkDynamicAopProxy
1 final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { 2 ... 3 }
這也就是說,這個類中必然實現了invoke方法:
源碼25-來自:JdkDynamicAopProxy
1 @Override 2 @Nullable 3 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 4 MethodInvocation invocation; 5 Object oldProxy = null; 6 boolean setProxyContext = false; 7 8 TargetSource targetSource = this.advised.targetSource; 9 Object target = null; 10 11 try { 12 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { 13 // The target does not implement the equals(Object) method itself. 14 // 目標類體系中未實現equals方法,但是代理的目標方法卻是equals方法 15 return equals(args[0]); 16 } 17 else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { 18 // The target does not implement the hashCode() method itself. 19 // 目標類體系中未實現hashCode方法,但是代理的目標方法卻是hashCode方法 20 return hashCode(); 21 } 22 else if (method.getDeclaringClass() == DecoratingProxy.class) { 23 // There is only getDecoratedClass() declared -> dispatch to proxy config. 24 return AopProxyUtils.ultimateTargetClass(this.advised); 25 } 26 // isAssignableFrom方法的意義:調用方如果是參數方的同類(接口)或者父類(接口),則返回true 27 else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && 28 method.getDeclaringClass().isAssignableFrom(Advised.class)) { 29 // Service invocations on ProxyConfig with the proxy config... 30 // 直接反射調用目標方法 31 return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); 32 } 33 34 Object retVal; 35 36 if (this.advised.exposeProxy) { 37 // Make invocation available if necessary. 38 // 如果設置了exposeProxy=true,那么就代理保存起來備用 39 oldProxy = AopContext.setCurrentProxy(proxy); 40 setProxyContext = true; 41 } 42 43 // Get as late as possible to minimize the time we "own" the target, 44 // in case it comes from a pool. 45 target = targetSource.getTarget(); 46 Class<?> targetClass = (target != null ? target.getClass() : null); 47 48 // Get the interception chain for this method. 49 // 1-獲取目標方法的攔截鏈 50 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 51 52 // Check whether we have any advice. If we don't, we can fallback on direct 53 // reflective invocation of the target, and avoid creating a MethodInvocation. 54 if (chain.isEmpty()) { 55 // We can skip creating a MethodInvocation: just invoke the target directly 56 // Note that the final invoker must be an InvokerInterceptor so we know it does 57 // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. 58 // 如果目標類沒有攔截器鏈,則直接反射調用目標方法 59 Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); 60 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); 61 } 62 else { 63 // We need to create a method invocation... 64 // 2-創建一個方法調用,並執行,ReflectiveMethodInvocation是Spring封裝的 65 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); 66 // Proceed to the joinpoint through the interceptor chain. 67 // 3-執行攔截器鏈,在ReflectiveMethodInvocation中維護了攔截器調用的計數器,保證攔截器的逐個調用,完成所有攔截器調用之后會反射調用目標方法。 68 retVal = invocation.proceed(); 69 } 70 71 // Massage return value if necessary. 72 Class<?> returnType = method.getReturnType(); 73 if (retVal != null && retVal == target && 74 returnType != Object.class && returnType.isInstance(proxy) && 75 !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { 76 // Special case: it returned "this" and the return type of the method 77 // is type-compatible. Note that we can't help if the target sets 78 // a reference to itself in another returned object. 79 retVal = proxy; 80 } 81 else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { 82 throw new AopInvocationException( 83 "Null return value from advice does not match primitive return type for: " + method); 84 } 85 return retVal; 86 } 87 finally { 88 if (target != null && !targetSource.isStatic()) { 89 // Must have come from TargetSource. 90 targetSource.releaseTarget(target); 91 } 92 if (setProxyContext) { 93 // Restore old proxy. 94 AopContext.setCurrentProxy(oldProxy); 95 } 96 } 97 }
上面的源碼並非發現織入的痕跡,讓我們接着看看ReflectiveMethodInvocation類的proceed方法:
源碼26-來自:ReflectiveMethodInvocation
1 @Override 2 @Nullable 3 public Object proceed() throws Throwable { 4 // We start with an index of -1 and increment early. 5 // 完成所有增強之后執行目標切點方法 6 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { 7 // 直接調用目標方法 8 return invokeJoinpoint(); 9 } 10 11 // 獲取下一個要執行的攔截器 12 Object interceptorOrInterceptionAdvice = 13 this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex); 14 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) { 15 // Evaluate dynamic method matcher here: static part will already have 16 // been evaluated and found to match. 17 InterceptorAndDynamicMethodMatcher dm = 18 (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice; 19 if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) { 20 // 動態匹配成功,則執行攔截器邏輯 21 return dm.interceptor.invoke(this); 22 } 23 else { 24 // Dynamic matching failed. 25 // Skip this interceptor and invoke the next in the chain. 26 // 動態匹配失敗,跳過當前攔截器,遞歸執行下一個攔截器 27 return proceed(); 28 } 29 } 30 else { 31 // It's an interceptor, so we just invoke it: The pointcut will have 32 // been evaluated statically before this object was constructed. 33 // 普通的攔截器,直接調用即可 34 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); 35 } 36 }
攔截器的調用執行invoke方法,並將this(當前實例)作為參數,來保證調用鏈的順利執行,具體的邏輯那就在每個攔截器的invoke方法之中了,執行完攔截器的邏輯之后,就可以執行目標方法的邏輯了。
這正是織入的實現。
我們從織入的邏輯中並未發現有對攔截器執行順序進行控制的邏輯,那么那些前置、后置、環繞、異常等的執行位置是怎么實現的呢?
3、織入實現原理
雖然在ReflectiveMethodInvocation的proceed方法中看到目標方法是最后才被執行,那么那些后置、環繞、異常的通知是怎么實現的呢,如果我們打開各種通知實現的invoke方法中,就會發現一些東西:
我們查看五種通知后發現
源碼27-來自:AspectJAfterReturningAdvice、AspectJAfterAdvice、AspectJAroundAdvice、AspectJMethodBeforeAdvice、AspectJAfterThrowingAdvice
1 // AspectJAfterReturningAdvice:后置通知 2 @Override 3 public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable { 4 if (shouldInvokeOnReturnValueOf(method, returnValue)) { 5 invokeAdviceMethod(getJoinPointMatch(), returnValue, null); 6 } 7 } 8 9 // AspectJAfterAdvice:后置終點通知 10 @Override 11 public Object invoke(MethodInvocation mi) throws Throwable { 12 try { 13 return mi.proceed(); 14 } 15 finally { 16 invokeAdviceMethod(getJoinPointMatch(), null, null); 17 } 18 } 19 20 // AspectJAroundAdvice:環繞通知 21 @Override 22 public Object invoke(MethodInvocation mi) throws Throwable { 23 if (!(mi instanceof ProxyMethodInvocation)) { 24 throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); 25 } 26 ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi; 27 ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi); 28 JoinPointMatch jpm = getJoinPointMatch(pmi); 29 return invokeAdviceMethod(pjp, jpm, null, null); 30 } 31 32 // AspectJMethodBeforeAdvice:前置通知 33 @Override 34 public void before(Method method, Object[] args, @Nullable Object target) throws Throwable { 35 invokeAdviceMethod(getJoinPointMatch(), null, null); 36 } 37 38 // AspectJAfterThrowingAdvice:異常通知 39 @Override 40 public Object invoke(MethodInvocation mi) throws Throwable { 41 try { 42 return mi.proceed(); 43 } 44 catch (Throwable ex) { 45 if (shouldInvokeOnThrowing(ex)) { 46 invokeAdviceMethod(getJoinPointMatch(), null, ex); 47 } 48 throw ex; 49 } 50 }
我們發現這五個通知里面的invoke方法中都調用了invokeAdviceMethod方法,這個方法是在AbstractAspectJAdvice抽象類中定義的。
源碼28-來自:AbstractAspectJAdvice
1 protected Object invokeAdviceMethod( 2 @Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable ex) 3 throws Throwable { 4 // 執行參數綁定,然后使用參數調用通知方法 5 return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex)); 6 }
此處有三個方法調用:
getJoinPoint方法,用戶獲取當前的連接點實例
argBinging方法,用於進行參數綁定操作
invokeAdviceMethodWithGivenArgs方法執行通知方法
首先看看getJointPoint方法:
源碼29-來自:AbstractAspectJAdvice
1 protected JoinPoint getJoinPoint() { 2 // 獲取當前的連接點實例 3 return currentJoinPoint(); 4 } 5 6 public static JoinPoint currentJoinPoint() { 7 // 首先嘗試從ExposeInvocationInterceptor攔截器中獲取當前的方法調用 8 MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation(); 9 if (!(mi instanceof ProxyMethodInvocation)) { 10 throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi); 11 } 12 ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi; 13 // 然后從方法調用之中獲取連接點實例jp 14 JoinPoint jp = (JoinPoint) pmi.getUserAttribute(JOIN_POINT_KEY); 15 if (jp == null) { 16 // 如果未獲取到連接點實例,並設置到方法調用之中 17 jp = new MethodInvocationProceedingJoinPoint(pmi); 18 pmi.setUserAttribute(JOIN_POINT_KEY, jp); 19 } 20 return jp; 21 }
上面邏輯很簡單,就是從方法調用上下文中獲取連接點,並返回,需要注意的是,此處一般情況是需要走if(jp == null)中的邏輯的,意思就是這里一般會是首次為方法調用設置連接點的地方,這也正是懶實例化的實現-在真正需要使用的時候才進行創建。
下面看看源碼28中第二個方法:參數綁定
源碼30-來自:AbstractAspectJAdvice
1 protected Object[] argBinding(JoinPoint jp, @Nullable JoinPointMatch jpMatch, 2 @Nullable Object returnValue, @Nullable Throwable ex) { 3 4 // 提前估測參數綁定 5 // 最后就是將所有通知中候選的參數的名稱和類型保存到了切點對應屬性之中備用 6 calculateArgumentBindings(); 7 8 // AMC start 9 Object[] adviceInvocationArgs = new Object[this.parameterTypes.length]; 10 int numBound = 0; 11 12 if (this.joinPointArgumentIndex != -1) { 13 adviceInvocationArgs[this.joinPointArgumentIndex] = jp; 14 numBound++; 15 } 16 else if (this.joinPointStaticPartArgumentIndex != -1) { 17 adviceInvocationArgs[this.joinPointStaticPartArgumentIndex] = jp.getStaticPart(); 18 numBound++; 19 } 20 21 if (!CollectionUtils.isEmpty(this.argumentBindings)) { 22 // binding from pointcut match 23 // 1-通過切點匹配進行參數綁定 24 if (jpMatch != null) { 25 PointcutParameter[] parameterBindings = jpMatch.getParameterBindings(); 26 for (PointcutParameter parameter : parameterBindings) { 27 String name = parameter.getName(); 28 Integer index = this.argumentBindings.get(name); 29 adviceInvocationArgs[index] = parameter.getBinding(); 30 numBound++; 31 } 32 } 33 // binding from returning clause 34 // 2-通過返回名稱進行參數綁定 35 if (this.returningName != null) { 36 Integer index = this.argumentBindings.get(this.returningName); 37 adviceInvocationArgs[index] = returnValue; 38 numBound++; 39 } 40 // binding from thrown exception 41 // 3-通過異常返回進行參數綁定 42 if (this.throwingName != null) { 43 Integer index = this.argumentBindings.get(this.throwingName); 44 adviceInvocationArgs[index] = ex; 45 numBound++; 46 } 47 } 48 49 if (numBound != this.parameterTypes.length) { 50 throw new IllegalStateException("Required to bind " + this.parameterTypes.length + 51 " arguments, but only bound " + numBound + " (JoinPointMatch " + 52 (jpMatch == null ? "was NOT" : "WAS") + " bound in invocation)"); 53 } 54 55 // 這里會將通知方法中的所有參數進行綁定,因為他們都是候選者,除了一些特殊的不需綁定的之外(只三種切點類型) 56 return adviceInvocationArgs; 57 }
參數綁定的重點在於第一步,估測參數綁定,這一步會將所有通知中候選的參數的名稱和類型保存到了切點對應屬性之中備用,我們來看看:
源碼31-來自AbstractAspectJAdvice
1 public final synchronized void calculateArgumentBindings() { 2 // The simple case... nothing to bind. 3 if (this.argumentsIntrospected || this.parameterTypes.length == 0) { 4 // 無參可綁的情況,直接返回 5 return; 6 } 7 8 // 獲取參數類型的數量numUnboundArgs(未綁參數數量) 9 int numUnboundArgs = this.parameterTypes.length; 10 Class<?>[] parameterTypes = this.aspectJAdviceMethod.getParameterTypes(); 11 // 排除parameterTypes中JoinPoint類型、ProceedingJoinPoint類型、JoinPoint.StaticPart類型的參數 12 // JoinPoint類型的參數可作為非環繞通知的首個參數 13 // ProceedingJoinPoint類型的參數可作為環繞通知的首個參數 14 // JoinPoint.StaticPart類型的參數也可以作為某些通知的首個參數 15 // 謹記,以上三種類型的參數只能作為對應通知的首個參數,當然也可以和其他參數共存,但位置必須位於首個,原因也很簡單,因為此處判斷的時候完全就是在拿首個參數類型來完成的。 16 // 這三個參數是可以直接使用的,無需進行參數綁定操作,所以在這里排除掉 17 if (maybeBindJoinPoint(parameterTypes[0]) || maybeBindProceedingJoinPoint(parameterTypes[0]) || 18 maybeBindJoinPointStaticPart(parameterTypes[0])) { 19 // 上述三種參數不需要綁定 20 numUnboundArgs--; 21 } 22 23 if (numUnboundArgs > 0) { 24 // need to bind arguments by name as returned from the pointcut match 25 // 排除以上類型之后,如果還有剩余,則需要根據從切入點匹配返回的名稱綁定參數 26 // 我們要明白:切點表達式完全就是用來匹配用的,哪怕其中有參數,也是為了匹配指定參數用的,他不帶有任何傳參功能,傳參功能只有通知方法才有 27 // 所以這里的剩余參數個數,其實就是通知方法剩余參數,這里是依據參數名稱來進行參數綁定 28 bindArgumentsByName(numUnboundArgs); 29 } 30 31 this.argumentsIntrospected = true; 32 }
這個方法中主要就是排除了三大類位於首位的切點參數類型,這三類型參數不需要進行綁定。然后對剩余的參數進行綁定操作:
源碼32-來自AbstractAspectJAdvice
1 // 通過name來綁定參數 2 private void bindArgumentsByName(int numArgumentsExpectingToBind) { 3 if (this.argumentNames == null) { 4 // 創建參數名稱發現器,並獲取指定通知方法的參數名稱 5 this.argumentNames = createParameterNameDiscoverer().getParameterNames(this.aspectJAdviceMethod); 6 } 7 if (this.argumentNames != null) { 8 // We have been able to determine the arg names. 9 // 只要確認通知使用有參數的就行 10 bindExplicitArguments(numArgumentsExpectingToBind); 11 } 12 else { 13 throw new IllegalStateException("Advice method [" + this.aspectJAdviceMethod.getName() + "] " + 14 "requires " + numArgumentsExpectingToBind + " arguments to be bound by name, but " + 15 "the argument names were not specified and could not be discovered."); 16 } 17 }
這一步,我們需要先創建參數名稱發現器,然后發現器來獲取當前通知方法的參數名的數組argumentNames。
這個數組的作用僅僅是用來判空,只要其有值,即通知方法有參數,那么就需要執行綁定操作。
首先來看看創建發現器的源碼:
源碼33-來自AbstractAspectJAdvice
1 protected ParameterNameDiscoverer createParameterNameDiscoverer() { 2 // We need to discover them, or if that fails, guess, 3 // and if we can't guess with 100% accuracy, fail. 4 // DefaultParameterNameDiscoverer是參數名稱發現器的默認實現,他其實是一個 5 DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); 6 AspectJAdviceParameterNameDiscoverer adviceParameterNameDiscoverer = 7 new AspectJAdviceParameterNameDiscoverer(this.pointcut.getExpression());// 切點的表達式 8 // 如果返回通知后綁定返回值,則returningName為非null 9 adviceParameterNameDiscoverer.setReturningName(this.returningName); 10 // 如果在拋出通知后綁定了拋出的值,則throwingName為非null 11 adviceParameterNameDiscoverer.setThrowingName(this.throwingName); 12 // Last in chain, so if we're called and we fail, that's bad... 13 // 設置在未能推導出通知參數名稱的情況下是否拋出IllegalArgumentException和AmbiguousBindingException異常 14 adviceParameterNameDiscoverer.setRaiseExceptions(true); 15 // 將配置好的發現器添加到DefaultParameterNameDiscoverer中並返回 16 discoverer.addDiscoverer(adviceParameterNameDiscoverer); 17 return discoverer; 18 }
源碼中AspectJAdviceParameterNameDiscoverer為真正執行發現操作的發現器。
源碼34-來自AspectJAdviceParameterNameDiscoverer
1 @Override 2 @Nullable 3 public String[] getParameterNames(Method method) { 4 // 參數類型 5 this.argumentTypes = method.getParameterTypes(); 6 // 初始化未綁定參數個數 7 this.numberOfRemainingUnboundArguments = this.argumentTypes.length; 8 // 初始化已綁定參數名稱數組 9 this.parameterNameBindings = new String[this.numberOfRemainingUnboundArguments]; 10 11 int minimumNumberUnboundArgs = 0;// 初始化最少未綁參數個數 12 // 針對后置通知和異常通知進行特殊處理,需要將返回值進行綁定 13 if (this.returningName != null) { 14 // 如果通知類型為后置通知 15 minimumNumberUnboundArgs++; 16 } 17 if (this.throwingName != null) { 18 // 如果通知類型為異常通知 19 minimumNumberUnboundArgs++; 20 } 21 if (this.numberOfRemainingUnboundArguments < minimumNumberUnboundArgs) { 22 throw new IllegalStateException( 23 "Not enough arguments in method to satisfy binding of returning and throwing variables"); 24 } 25 26 try { 27 //分成八步進行操作 28 int algorithmicStep = STEP_JOIN_POINT_BINDING; 29 while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) { 30 switch (algorithmicStep++) { 31 case STEP_JOIN_POINT_BINDING: 32 // 1-連接點參數綁定 33 // 連接點參數綁定格式為:thisJoinPoint -> 0 34 if (!maybeBindThisJoinPoint()) { 35 // 連接點參數綁定格式為:thisJoinPointStaticPart -> 0 36 maybeBindThisJoinPointStaticPart(); 37 } 38 break; 39 case STEP_THROWING_BINDING: 40 // 2-異常返回參數綁定 41 // 異常參數綁定格式為:throwingName -> throwableIndex 42 // throwableIndex為通知參數列表中接收異常的參數的位置 43 maybeBindThrowingVariable(); 44 break; 45 case STEP_ANNOTATION_BINDING: 46 // 3-注解參數綁定 47 // 格式:varName -> annotationIndex 48 maybeBindAnnotationsFromPointcutExpression(); 49 break; 50 case STEP_RETURNING_BINDING: 51 // 4-返回參數綁定 52 // 綁定返回值時,只有在未綁定參數只剩余1個的情況下才能綁定,否則不予綁定 53 // 當只剩余一個未綁定的情況下,將返回值與剩余的那個位置的下標進行綁定即可 54 maybeBindReturningVariable(); 55 break; 56 case STEP_PRIMITIVE_ARGS_BINDING: 57 // 5-原始類型參數綁定 58 // 只有在有1個原始類型參數,且只有一個候選位置時才執行綁定操作 59 maybeBindPrimitiveArgsFromPointcutExpression(); 60 break; 61 case STEP_THIS_TARGET_ARGS_BINDING: 62 // 6-切點表達式參數綁定 63 // 只有只存在一個變量名稱的時候才能執行綁定 64 maybeBindThisOrTargetOrArgsFromPointcutExpression(); 65 break; 66 case STEP_REFERENCE_PCUT_BINDING: 67 // 7-引用切點綁定 68 // 只有只存在一個變量名稱的時候才能執行綁定 69 maybeBindReferencePointcutParameter(); 70 break; 71 default: 72 throw new IllegalStateException("Unknown algorithmic step: " + (algorithmicStep - 1)); 73 } 74 } 75 } 76 catch (AmbiguousBindingException ambigEx) { 77 if (this.raiseExceptions) { 78 throw ambigEx; 79 } 80 else { 81 return null; 82 } 83 } 84 catch (IllegalArgumentException ex) { 85 if (this.raiseExceptions) { 86 throw ex; 87 } 88 else { 89 return null; 90 } 91 } 92 93 if (this.numberOfRemainingUnboundArguments == 0) { 94 return this.parameterNameBindings; 95 } 96 else { 97 if (this.raiseExceptions) { 98 throw new IllegalStateException("Failed to bind all argument names: " + 99 this.numberOfRemainingUnboundArguments + " argument(s) could not be bound"); 100 } 101 else { 102 // convention for failing is to return null, allowing participation in a chain of responsibility 103 return null; 104 } 105 } 106 }
源碼中針對各種情況進行了參數綁定操作,采用switch...case鏈式結構完成功能。雖然這一步最終目的是返回parameterNameBindings值,而且在源碼32中看起來只是用於判空,好像用處不大,其實大錯特錯,這個parameterNameBindings在這里綁定完成之后,在以后會有大用,判空只是它的一個小功能罷了。
至於針對每種參數類型的綁定邏輯就不再細究了。下面看看源碼32中bindExplicitArguments方法
源碼35-來自:AbstractAspectJAdvice
1 private void bindExplicitArguments(int numArgumentsLeftToBind) { 2 Assert.state(this.argumentNames != null, "No argument names available"); 3 this.argumentBindings = new HashMap<>(); 4 5 // 獲取通知方法的參數個數 6 int numExpectedArgumentNames = this.aspectJAdviceMethod.getParameterCount(); 7 if (this.argumentNames.length != numExpectedArgumentNames) { 8 // 參數個數不匹配 9 throw new IllegalStateException("Expecting to find " + numExpectedArgumentNames + 10 " arguments to bind by name in advice, but actually found " + 11 this.argumentNames.length + " arguments."); 12 } 13 14 // So we match in number... 15 // 獲取parameterTypes中剩余的參數偏移量,將其綁定到argumentBindings中,numArgumentsLeftToBind正是之前排除三大首位切點參數之后的剩余參數量 16 int argumentIndexOffset = this.parameterTypes.length - numArgumentsLeftToBind; 17 for (int i = argumentIndexOffset; i < this.argumentNames.length; i++) { 18 // 參數綁定的格式:name-> index 19 this.argumentBindings.put(this.argumentNames[i], i); 20 } 21 22 // Check that returning and throwing were in the argument names list if 23 // specified, and find the discovered argument types. 24 if (this.returningName != null) { 25 if (!this.argumentBindings.containsKey(this.returningName)) { 26 throw new IllegalStateException("Returning argument name '" + this.returningName + 27 "' was not bound in advice arguments"); 28 } 29 else { 30 // 獲取其對應的index值,將其另外綁定到discoveredReturningType和discoveredReturningGenericType中 31 Integer index = this.argumentBindings.get(this.returningName); 32 this.discoveredReturningType = this.aspectJAdviceMethod.getParameterTypes()[index]; 33 this.discoveredReturningGenericType = this.aspectJAdviceMethod.getGenericParameterTypes()[index]; 34 } 35 } 36 if (this.throwingName != null) { 37 if (!this.argumentBindings.containsKey(this.throwingName)) { 38 throw new IllegalStateException("Throwing argument name '" + this.throwingName + 39 "' was not bound in advice arguments"); 40 } 41 else { 42 // 獲取其對應的index值,並將其另外綁定到discoveredThrowingType中 43 Integer index = this.argumentBindings.get(this.throwingName); 44 this.discoveredThrowingType = this.aspectJAdviceMethod.getParameterTypes()[index]; 45 } 46 } 47 48 // configure the pointcut expression accordingly. 49 // 相應地配置切入點表達式。 50 configurePointcutParameters(this.argumentNames, argumentIndexOffset); 51 }
源碼36-來自:AbstractAspectJAdvice
1 private void configurePointcutParameters(String[] argumentNames, int argumentIndexOffset) { 2 int numParametersToRemove = argumentIndexOffset; 3 if (this.returningName != null) { 4 numParametersToRemove++; 5 } 6 if (this.throwingName != null) { 7 numParametersToRemove++; 8 } 9 // 之前將所有要移除的參數數量累加出來,需要移除首位的三種切點參數、returningName參數和throwingName參數 10 // 剩余的參數就是切點表達式中可以出現的候選參數,這里初始化一個切點參數名數組pointcutParameterNames,將這些參數囊括進來 11 // 另外再初始化兩個數組,分別用於存放這些剩余參數的參數類型(pointcutParameterTypes)和通知方法所有參數類型(methodParameterTypes) 12 String[] pointcutParameterNames = new String[argumentNames.length - numParametersToRemove]; 13 Class<?>[] pointcutParameterTypes = new Class<?>[pointcutParameterNames.length]; 14 Class<?>[] methodParameterTypes = this.aspectJAdviceMethod.getParameterTypes(); 15 16 int index = 0; 17 for (int i = 0; i < argumentNames.length; i++) { 18 if (i < argumentIndexOffset) { 19 // 小於偏移量的參數為移除的三大切點類型參數 20 continue; 21 } 22 if (argumentNames[i].equals(this.returningName) || 23 argumentNames[i].equals(this.throwingName)) { 24 // 這里在對returningName和throwingName參數進行排除 25 continue; 26 } 27 // 剩余的就是通知中的切點表達式候選參數,將這些參數的參數名逐個保存到切點參數名數組中 28 // 將這些參數的參數類型逐個保存到切點參數類型數組中 29 pointcutParameterNames[index] = argumentNames[i]; 30 pointcutParameterTypes[index] = methodParameterTypes[i]; 31 index++; 32 } 33 34 // 然后,分別將兩個數組設置到切點的對應屬性中 35 this.pointcut.setParameterNames(pointcutParameterNames);// 設置參數名稱 36 this.pointcut.setParameterTypes(pointcutParameterTypes);// 設置參數類型 37 }
到此為止我們看到了參數綁定操作的最終結果,將除了首位三大切點類型參數、returningName參數和throwingName參數之外的通知的剩余參數都作為候選參數,將其名稱和類型分別放置到一個數組中,再設置到切點之中。
邏輯到這里我們可以回到源碼30中,執行剩余的邏輯,將所有參數遷移到adviceInvocationArgs中進行綁定,綁定的邏輯還是一樣,都是以數組的方式保存。
然后再回到源碼28中,執行invokeAdviceMethodWithGivenArgs方法
1 protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable { 2 Object[] actualArgs = args; 3 if (this.aspectJAdviceMethod.getParameterCount() == 0) { 4 actualArgs = null; 5 } 6 try { 7 ReflectionUtils.makeAccessible(this.aspectJAdviceMethod); 8 // TODO AopUtils.invokeJoinpointUsingReflection 9 return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs); 10 } 11 catch (IllegalArgumentException ex) { 12 throw new AopInvocationException("Mismatch on arguments to advice method [" + 13 this.aspectJAdviceMethod + "]; pointcut expression [" + 14 this.pointcut.getPointcutExpression() + "]", ex); 15 } 16 catch (InvocationTargetException ex) { 17 throw ex.getTargetException(); 18 } 19 }
邏輯很簡單,就是依據給定的參數來反射調用通知的方法邏輯。
至此我們分析完JDK動態代理實現AOP切面編程的整個邏輯。其實這里針對AOP的織入原理還是沒有很明確的體現出來,為此我專門開了一文:Spring基礎系列--AOP織入邏輯跟蹤,可以帶你詳細了解Spring AOP代理織入的實現。