1. 程式人生 > >29--aspectj-autoproxy解析及Spring解析自定義標籤

29--aspectj-autoproxy解析及Spring解析自定義標籤

前兩個小節已經介紹了AOP的一些基礎知識回顧並對靜態代理、JDK動態代理、CGLIB動態代理做了一些簡單的介紹,本節介紹AOP標籤的解析過程。

1.aspectj-autoproxy標籤簡介

使用註解方式應用aop需要在配置檔案中配置<aop:aspectj-autoproxy/>, 而該標籤有兩個屬性,proxy-target-class he expose-proxy

  • proxy-target-class 如果被代理的目標物件至少實現了一個介面,則會使用JDK動態代理,所有實現該目標類實現的介面都將被代理;如果該目標物件沒有實現任何介面,則建立CGLIB動態代理。但是可以通過proxy-target-class屬性強制指定使用CGLIB代理。所以如果指定了proxy-target-class="true"
    則將強制開啟CGLIB動態代理。
  • expose-proxy 解決目標物件內部的自我呼叫無法實施切面增強的問題。
2.aspectj-autoproxy標籤解析

在第十七小節,講解了Spring對預設標籤的解析過程,但是沒有對預設標籤解析進行分析,今天就來分析一下Spring是如何解析自定義標籤aspectj-autoproxy的。順便通過對該標籤的解析,補充十七節欠缺的分析。

  • 自定義標籤解析入口 開啟DefaultBeanDefinitionDocumentReader類的parseBeanDefinitions方法
protected void parseBeanDefinitions
(Element root, BeanDefinitionParserDelegate delegate) { // 1、解析預設名稱空間 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; // 預設名稱空間 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } // 自定義名稱空間 else { delegate.parseCustomElement(ele); } } } } // 2、解析自定義名稱空間 else { delegate.parseCustomElement(root); } }

通過parseCustomElement解析自定義標籤

  • 自定義標籤解析過程
/**
 * 解析自定義標籤
 * @param ele
 * @param containingBd
 * @return
 */
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 1、獲取namespaceUri
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    }
    // 2、根據namespaceUri得到名稱空間解析器
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    // 2、使用名稱空間解析器解析自定義標籤
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

對自定義標籤的解析交給了NamespaceHandler介面,通過這個我們就可以知道,如果我們想在Spring中實現自己的自定義標籤,那麼就需要實現NamespaceHandler介面,並通過重寫其中的方法,來完成對自定義標籤的解析。

  • 獲取NamespaceHandler
public NamespaceHandler resolve(String namespaceUri) {
    // 1.獲取所有的namespaceUri,NamespaceHandler鍵值對map集合並得到
    // 當前namespaceUri對應的NamespaceHandler類
    Map<String, Object> handlerMappings = getHandlerMappings();
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
        return (NamespaceHandler) handlerOrClassName;
    }
    else {
        // 2、通過BeanUtils例項化NamespaceHandler並呼叫其init方法進行初始化操作
        String className = (String) handlerOrClassName;
        try {
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            namespaceHandler.init();
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
                    "] for namespace [" + namespaceUri + "]", ex);
        }
        catch (LinkageError err) {
            throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
                    className + "] for namespace [" + namespaceUri + "]", err);
        }
    }
}

該方法非常簡單,首先根據namespaceUri從所有的Map<namespaceUri,NamespaceHandler>中獲取對應的NamespaceHandler實現類,然後通過BeanUtils.instantiateClass例項化該類,最後呼叫NamespaceHandler例項的init方法。

  • AopNamespaceHandler的init方法
public void init() {
    // In 2.0 XSD as well as in 2.1 XSD.
    registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
    registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
    registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

    // Only in 2.0 XSD: moved to context namespace as of 2.1
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}

在該方法中我們已經看到了aspectj-autoproxy標籤,該方法也很簡單,不做過多的分析。接下來分析aspectj-autoproxy解析的過程。

3.通過NamespaceHandler的parse方法解析aspectj-autoproxy標籤
public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionParser parser = findParserForElement(element, parserContext);
    return (parser != null ? parser.parse(element, parserContext) : null);
}

具體的轉換工作委託給了BeanDefinitionParser例項:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 1、註冊AnnotationAwareAspectJAutoProxyCreator
    AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
    // 2、處理子標籤<aop:include/>, 指定@Aspect類,支援正則表示式,符合該表示式的切面類才會被應用
    extendBeanDefinition(element, parserContext);
    return null;
}

該轉換過程主要完成兩個功能,註冊AnnotationAwareAspectJAutoProxyCreator和解析子標籤<aop:include/>,我們逐步分析一下:

3.1 註冊AnnotationAwareAspectJAutoProxyCreator
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {

    // 1、註冊AnnotationAwareAspectJAutoProxyCreator
    BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            parserContext.getRegistry(),
            parserContext.extractSource(sourceElement));
    // 2、解析子標籤 proxy-target-class 和 expose-proxy
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    // 3、註冊元件併發送元件註冊事件
    registerComponentIfNecessary(beanDefinition, parserContext);
}
  • 註冊AnnotationAwareAspectJAutoProxyCreator
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
			@Nullable Object source) {
    // cls --> org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
    // AUTO_PROXY_CREATOR_BEAN_NAME --> org.springframework.aop.config.internalAutoProxyCreator
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        // 如果registry已經包含了internalAutoProxyCreator,
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        // 如果已經註冊的internalAutoProxyCreator不是AnnotationAwareAspectJAutoProxyCreator,則需要判斷優先順序並決定使用哪個
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        // 如果已經註冊的internalAutoProxyCreator是AnnotationAwareAspectJAutoProxyCreator,則無需特殊處理
        return null;
    }

    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    // HIGHEST_PRECEDENCE --> 指定最高優先順序
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    // ROLE_INFRASTRUCTURE --> 表示Spring的內部bean
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    // 註冊BeanDefinition
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}
  • 解析子標籤 proxy-target-class 和 expose-proxy
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
    if (sourceElement != null) {
        // PROXY_TARGET_CLASS_ATTRIBUTE --> proxy-target-class
        boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
        if (proxyTargetClass) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        // EXPOSE_PROXY_ATTRIBUTE --> expose-proxy
        boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
        if (exposeProxy) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}

該過程最核心的作用就是註冊AnnotationAwareAspectJAutoProxyCreator,關於該類的作用,我們會在下面的章節分析。

3.2 處理子標籤<aop:include/>

<aop:include/>標籤的作用:例如有下面的配置檔案,並指定了<aop:include name="aspectJTest"></aop:include>,則aspectJTest切面類會被應用,且name支援正則表示式,如果配置檔案中的切面類不符合name的表示式,則切面類不會被應用。

<?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: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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!--
		1、proxy-target-class
			如果被代理的目標物件至少實現了一個介面,則會使用JDK動態代理,所有實現該目標類實現的介面都將被代理
			如果該目標物件沒有實現任何介面,則建立CGLIB動態代理。
			但是可以通過proxy-target-class屬性強制指定使用CGLIB代理,
		2、expose-proxy
			解決目標物件內部的自我呼叫無法實施切面增強的問題
	-->
	<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true">
		<!-- 指定@Aspect類,支援正則表示式,符合該表示式的切面類才會被應用-->
		<aop:include name="aspectJTest"></aop:include>
	</aop:aspectj-autoproxy>

	<bean id="aopTestBean" class="com.lyc.cn.v2.day04.AopTestBean"></bean>

	<bean name="aspectJTest" class="com.lyc.cn.v2.day04.AspectJTest"></bean>
</beans>

瞭解了標籤的作用之後,我們來看其解析過程,比較簡單就不附加註釋了:

private void extendBeanDefinition(Element element, ParserContext parserContext) {
    BeanDefinition beanDef = parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
    if (element.hasChildNodes()) {
        addIncludePatterns(element, parserContext, beanDef);
    }
}

private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) {
    ManagedList<TypedStringValue> includePatterns = new ManagedList<>();
    NodeList childNodes = element.getChildNodes();
    for (int i = 0; i < childNodes.getLength(); i++) {
        Node node = childNodes.item(i);
        if (node instanceof Element) {
            Element includeElement = (Element) node;
            TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));
            valueHolder.setSource(parserContext.extractSource(includeElement));
            includePatterns.add(valueHolder);
        }
    }
    if (!includePatterns.isEmpty()) {
        includePatterns.setSource(parserContext.extractSource(element));
        beanDef.getPropertyValues().add("includePatterns", includePatterns);
    }
}

那麼到這裡<aop:aspectj-autoproxy/>標籤的解析工作也就完成了。

4.總結

本節主要分析了<aop:aspectj-autoproxy/>標籤的解析,而該標籤也是Spring的自定義標籤,所以,如果大家有自己的自定義標籤時,可以參考借鑑Spring的原始碼,寫出自己的標籤解析過程。通過繼承NamespaceHandler實現自己的名稱空間解析器即可。

在解析<aop:aspectj-autoproxy/>的過程中,除了對標籤的解析工作之外,還註冊了AnnotationAwareAspectJAutoProxyCreator,該類的作用是什麼呢,我們留在下節分析。