日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring AOP 源码系列(一)解析 AOP 配置信息

發(fā)布時間:2024/9/30 javascript 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring AOP 源码系列(一)解析 AOP 配置信息 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在進行源碼閱讀之前建議先看一下這篇文章:Spring AOP 源碼分析系列文章導(dǎo)讀 by 田小波,寫的非常好,推薦閱讀。

關(guān)于 AOP 中常用的一些術(shù)語這里就不解釋了,如果不清楚的建議先看一遍上面推薦的文章。

一、AOP 配置入口

在分析源碼之前,我們先來看一下 XML 中關(guān)于 AOP 的配置:

<bean id="userServiceImpl" class="com.jas.mess.aop.UserServiceImpl"/><bean id="ownerAspect" class="com.jas.mess.aop.spring.OwnerAspect"/><aop:config><aop:aspect ref="ownerAspect"><aop:pointcut id="ownerPointcut" expression="execution(* com.jas.mess.aop.*.*(..))"/><aop:before method="recordLog" pointcut-ref="ownerPointcut"/></aop:aspect></aop:config>

在以前的文章里有總結(jié)過,XML 文件的配置信息會被轉(zhuǎn)化成 BeanDefinition,其中 AOP 相關(guān)的配置也不例外,從上面的例子中我們能看出一個明顯的區(qū)別:普通的 bean 標(biāo)簽與 AOP 配置標(biāo)簽不一樣,一個是 <bean /> 一個是 <aop />,除此之外你要想配置 AOP,還需要在 XMl 中引入 xmlns:aop="http://www.springframework.org/schema/aop" 命名空間。

普通的 bean 標(biāo)簽與 AOP 標(biāo)簽處理邏輯通過命名空間區(qū)分,下面我們來看源碼。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {// 命名空間檢查,默認的命名空間是 "http://www.springframework.org/schema/beans"if (delegate.isDefaultNamespace(root)) {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 /> 標(biāo)簽處理入口if (delegate.isDefaultNamespace(ele)) {parseDefaultElement(ele, delegate);}else {// <aop /> 標(biāo)簽處理入口delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}}

上面我們說過,AOP 的命名空間是 http://www.springframework.org/schema/aop,因此會執(zhí)行到 delegate.parseCustomElement(ele)。

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根據(jù)命名空間獲取 handlerNamespaceHandler 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));}public BeanDefinition parse(Element element, ParserContext parserContext) {BeanDefinitionParser parser = findParserForElement(element, parserContext);return (parser != null ? parser.parse(element, parserContext) : null);}

上面的流程比較簡單,根據(jù)命名空間 URI 獲取命名空間 handler,這里對應(yīng)的是 AopNamespaceHandler,通過 handler 去解析配置。handler 內(nèi)部會根據(jù)標(biāo)簽配置信息得到 ConfigBeanDefinitionParser,最終調(diào)用 parse 方法。

public BeanDefinition parse(Element element, ParserContext parserContext) {CompositeComponentDefinition compositeDef =new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));parserContext.pushContainingComponent(compositeDef);// 注冊 AspectJAwareAdvisorAutoProxyCreator 類型的beanDefinition,繼承自 BeanPostProcessorconfigureAutoProxyCreator(parserContext, element);// 獲取所有子標(biāo)簽List<Element> childElts = DomUtils.getChildElements(element);for (Element elt: childElts) {String localName = parserContext.getDelegate().getLocalName(elt);// 解析 <aop:poincut />if (POINTCUT.equals(localName)) {parsePointcut(elt, parserContext);}// 解析 <aop:>advisor />else if (ADVISOR.equals(localName)) {parseAdvisor(elt, parserContext);}// 解析 <aop:aspect />else if (ASPECT.equals(localName)) {parseAspect(elt, parserContext);}}parserContext.popAndRegisterContainingComponent();return null;}

這里涉及到一個 <aop:advisor /> 標(biāo)簽,大家可能不太清楚它的作用,advisor 與 advice(<aop:before />,<aop:after />…)功能類似,都可以用來增強目標(biāo)方法,具體用法可以參考這篇文章:Spring Aop(八)——advisor標(biāo)簽 by 234390216

parse 方法會處理 <aop:config /> 標(biāo)簽下的子標(biāo)簽,具體的處理邏輯后面會分析到。在解析標(biāo)簽之前先調(diào)用了 configureAutoProxyCreator 方法,這個方法比較重要。

二、注冊 AspectJAwareAdvisorAutoProxyCreator beanDefinition

private void configureAutoProxyCreator(ParserContext parserContext, Element element) {AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);}public static void registerAspectJAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);registerComponentIfNecessary(beanDefinition, parserContext);}

registerAspectJAutoProxyCreatorIfNecessary方法中有三個處理邏輯,下面一一來看下。

@Nullablepublic static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {// 注意這里傳的是 AspectJAwareAdvisorAutoProxyCreator 類型return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);}private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");// AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator"if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);if (!cls.getName().equals(apcDefinition.getBeanClassName())) {int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());int requiredPriority = findPriorityForClass(cls);if (currentPriority < requiredPriority) {apcDefinition.setBeanClassName(cls.getName());}}return null;}RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);beanDefinition.setSource(source);beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);// 注冊一個類型為 AspectJAwareAdvisorAutoProxyCreator 的 beanDefinitionregistry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);return beanDefinition;}

注冊一個類型為 AspectJAwareAdvisorAutoProxyCreator 的 beanDefinition,注意此時還沒有創(chuàng)建 bean,AspectJAwareAdvisorAutoProxyCreator 其實是一個 BeanPostProcessor,后面在對方法增強的時候會用到,具體細節(jié)會在后面的文章里總結(jié)。

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {if (sourceElement != null) {boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));if (proxyTargetClass) {AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));if (exposeProxy) {AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);}}}

檢查 <aop:config />是否配置了 proxy-target-class 與 expose-proxy,如果有配置,則為 beanDefinition 添加 exposeProxy 與 proxyTargetClass 屬性,這兩個配置標(biāo)簽后面還會遇到,這里先簡單介紹下作用。

proxy-target-class:默認為 false,如果設(shè)置為 true,會使用 CGLIB 創(chuàng)建代理對象
expose-proxy:默認為 false,作用:假設(shè) A,B 方法都被增強,A 方法調(diào)用 B 方法,A 方法會被切面攔截,A 里的 B 方法是否會被切面攔截增強,默認不會

private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) {if (beanDefinition != null) {parserContext.registerComponent(new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME));}}

注冊組件。

三、解析 <aop:aspect ref="" id="" />

<aop:aspect /> 標(biāo)簽下可以配置多種類型的子標(biāo)簽,比如 <aop:before />、<aop:after />、<aop:pointcut />、<aop:declare-parents /> 等,不同類型的標(biāo)簽處理邏輯也是不一樣的。

private void parseAspect(Element aspectElement, ParserContext parserContext) {String aspectId = aspectElement.getAttribute(ID);// 切面 beanNameString aspectName = aspectElement.getAttribute(REF);try {// 添加進 LinkedListthis.parseState.push(new AspectEntry(aspectId, aspectName));List<BeanDefinition> beanDefinitions = new ArrayList<>();List<BeanReference> beanReferences = new ArrayList<>();// 獲取 <aop:declare-parents /> 子標(biāo)簽List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);for (Element declareParentsElement : declareParents) {beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));}NodeList nodeList = aspectElement.getChildNodes();boolean adviceFoundAlready = false;for (int i = 0; i < nodeList.getLength(); i++) {Node node = nodeList.item(i);// 處理通知節(jié)點 <aop:before,after, around ... />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));}// 處理通知AbstractBeanDefinition advisorDefinition = parseAdvice(aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);beanDefinitions.add(advisorDefinition);}}// 創(chuàng)建 AspectComponentDefinitionAspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);parserContext.pushContainingComponent(aspectComponentDefinition);// 獲取所有切點 elementList<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);for (Element pointcutElement : pointcuts) {parsePointcut(pointcutElement, parserContext);}parserContext.popAndRegisterContainingComponent();}finally {this.parseState.pop();}}

主要流程如下:

  • 獲取 <aop:aspect > 的 id 與 ref 屬性,封裝成 AspectEntry 添加到 parseState 集合中,流程處理完后,出棧
  • 獲取 <aop:declare-parents /> 子標(biāo)簽,如果有配置則封裝成 beanDefinition
  • 解析 <aop:aspect > 下所有子標(biāo)簽,循環(huán)處理通知配置
  • 解析切點配置
  • 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)));RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);// 切面 bean(類)methodDefinition.getPropertyValues().add("targetBeanName", aspectName);// 設(shè)置切面方法methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));methodDefinition.setSynthetic(true);// 創(chuàng)建切面 BeanDefinitionRootBeanDefinition aspectFactoryDef =new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);aspectFactoryDef.setSynthetic(true);// 創(chuàng)建通知 BeanDefinition,此時已明確通知類型AbstractBeanDefinition adviceDef = createAdviceDefinition(adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,beanDefinitions, beanReferences);RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);advisorDefinition.setSource(parserContext.extractSource(adviceElement));// 將上面創(chuàng)建的 adviceDef 注入到 advisorDefinition 中advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);// <aop:aspect ref="" order="" />,控制切面方法優(yōu)先級if (aspectElement.hasAttribute(ORDER_PROPERTY)) {advisorDefinition.getPropertyValues().add(ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));}// 注冊通知 BeanDefinitionparserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);return advisorDefinition;}finally {this.parseState.pop();}}

    上面邏輯主要是創(chuàng)建出 advisorDefinition,并注冊到 beanFactory 中,由此可見,我們配置的每一個通知,最終都會被轉(zhuǎn)化成 beanDefinition 并注冊到 IoC 容器中。

    上面這個方法深追下去知識點還挺多的,比如 createAdviceDefinition 方法,注冊 beanDefinition 時 beanName 的生成規(guī)則等,有興趣的自己可以研究下,這里就不介紹了,看不懂的地方可以通過 DEBUG 的方式幫助理解。

    private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {String id = pointcutElement.getAttribute(ID);// 獲取切點表達式String expression = pointcutElement.getAttribute(EXPRESSION);AbstractBeanDefinition pointcutDefinition = null;try {this.parseState.push(new PointcutEntry(id));// 創(chuàng)建切點 beanDefinition,類型為 ConfigurableBeanFactory.SCOPE_PROTOTYPEpointcutDefinition = createPointcutDefinition(expression);pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));String pointcutBeanName = id;// 注冊切點 BeanDefinition 到 beanFactory 中if (StringUtils.hasText(pointcutBeanName)) {parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);}else {// <aop:pointcut id="" expression=""/> 沒有配置 id 先生成 beanName 在注冊到 beanFactory 中pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);}parserContext.registerComponent(new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));}finally {this.parseState.pop();}return pointcutDefinition;}protected AbstractBeanDefinition createPointcutDefinition(String expression) {RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);// 切點作用域原型beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);beanDefinition.setSynthetic(true);beanDefinition.getPropertyValues().add(EXPRESSION, expression);return beanDefinition;}

    處理切點的邏輯與通知類似,注意切點 beanDefinition 的作用域并非單例而是原型,初始化 beanDefinition 后注冊到 IoC 容器中。

    參考

    【Spring源碼分析】AOP源碼解析(上篇) by 五月的倉頡

    總結(jié)

    以上是生活随笔為你收集整理的Spring AOP 源码系列(一)解析 AOP 配置信息的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。