1. 程式人生 > >spring5 原始碼深度解析----- AOP的使用及AOP自定義標籤

spring5 原始碼深度解析----- AOP的使用及AOP自定義標籤

我們知道在面向物件OOP程式設計存在一些弊端,當需要為多個不具有繼承關係的物件引入同一個公共行為時,例如日誌,安全檢測等,我們只有在每個物件裡引入公共行為,這樣程式中就產生了大量的重複程式碼,所以有了面向物件程式設計的補充,面向切面程式設計(AOP),AOP所關注的方向是橫向的,不同於OOP的縱向。接下來我們就詳細分析下spring中的AOP。首先我們從動態AOP的使用開始。

AOP的使用

在開始前,先引入Aspect。

<!-- aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${aspectj.version}</version>
</dependency>

建立用於攔截的bean

public class TestBean {
    private String message = "test bean";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void test(){
        System.out.println(this.message);
    }
}

建立Advisor

Spring中摒棄了最原始的繁雜配置方式而採用@AspectJ註解對POJO進行標註,使AOP的工作大大簡化,例如,在AspectJTest類中,我們要做的就是在所有類的test方法執行前在控制檯beforeTest。而在所有類的test方法執行後列印afterTest,同時又使用環繞的方式在所有類的方法執行前後在此分別列印before1和after1,以下是AspectJTest的程式碼:

@Aspect
public class AspectJTest {
    @Pointcut("execution(* *.test(..))")
    public void test(){
    }
    
    @Before("test()")
    public void beforeTest(){
        System.out.println("beforeTest");
    }
    
    @Around("test()")
    public Object aroundTest(ProceedingJoinPoint p){
        System.out.println("around.....before");
        Object o = null;
        try{
            o = p.proceed();
        }catch(Throwable e){
            e.printStackTrace();
        }
        System.out.println("around.....after");
        return o;
    }
    
    @After("test()")
    public void afterTest()
    {
        System.out.println("afterTest");
    }
 }

建立配置檔案

要在Spring中開啟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">

    <aop:aspectj-autoproxy/>
    <bean id="test" class="com.yhl.myspring.demo.aop.TestBean">
        <property name="message" value="這是一個苦逼的程式設計師"/>
    </bean>
    <bean id="aspect" class="com.yhl.myspring.demo.aop.AspectJTest"/>
</beans>

 

測試

public class Test {
    public static void main(String[] args) {
        ApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
        TestBean bean = (TestBean)bf.getBean("test");
        bean.test();
    }
}

執行後輸出如下:

 

Spring實現了對所有類的test方法進行增強,使輔助功能可以獨立於核心業務之外,方便與程式的擴充套件和解耦。 
那麼,Spring是如何實現AOP的呢?首先我們知道,SPring是否支援註解的AOP是由一個配置檔案控制的,也就是<aop:aspectj-autoproxy/>,當在配置檔案中聲明瞭這句配置的時候,Spring就會支援註解的AOP,那麼我們的分析就從這句註解開始。

AOP自定義標籤

之前講過Spring中的自定義註解,如果聲明瞭自定義的註解,那麼就一定會在程式中的某個地方註冊了對應的解析器。我們搜尋 aspectj-autoproxy 這個程式碼,嘗試找到註冊的地方,全域性搜尋後我們發現了在org.springframework.aop.config包下的AopNamespaceHandler中對應著這樣一段函式:

@Override
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());
}

這裡我們就不再對spring中的自定義註解方式進行討論了。從這段程式碼中我們可以得知,在解析配置檔案的時候,一旦遇到了aspectj-autoproxy註解的時候會使用解析器AspectJAutoProxyBeanDefinitionParser進行解析,接下來我們就詳細分析下其內部實現。

註冊AnnotationAwareAspectJAutoProxyCreator

所有解析器,因為都是對BeanDefinitionParser介面的統一實現,入口都是從parse函式開始的,AspectJAutoProxyBeanDefinitionParser的parse函式如下:

@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 註冊AnnotationAwareAspectJAutoProxyCreator
    AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
    // 對於註解中子類的處理
    extendBeanDefinition(element, parserContext);
    return null;
}

通過程式碼可以瞭解到函式的具體邏輯是在registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中實現的,繼續進入到函式體內:

/**
 * 註冊AnnotationAwareAspectJAutoProxyCreator
 * @param parserContext
 * @param sourceElement
 */
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
        ParserContext parserContext, Element sourceElement) {
    // 註冊或升級AutoProxyCreator定義beanName為org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition
    BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    // 對於proxy-target-class以及expose-proxy屬性的處理
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    // 註冊元件並通知,便於監聽器做進一步處理
    registerComponentIfNecessary(beanDefinition, parserContext);
}

在registerAspectJAnnotationAutoProxyCreatorIfNeccessary方法中主要完成了3件事情,基本上每行程式碼都是一個完整的邏輯。接下來我們詳細分析每一行程式碼。

註冊或升級AnnotationAwareAspectJAutoProxyCreator

對於AOP的實現,基本上都是靠AnnotationAwareAspectJAutoProxyCreator去完成,它可以根據@Point註解定義的切點來自動代理相匹配的bean。但是為了配置簡便,Spring使用了自定義配置來幫助我們自動註冊AnnotationAwareAspectJAutoProxyCreator,其註冊過程就是在這裡實現的。我們繼續跟進到方法內部:

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
        @Nullable Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

public static final String AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator";

private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
        @Nullable Object source) {

    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    //如果已經存在了自動代理建立器且存在的自動代理建立器與現在的不一致那麼需要根據優先順序來判斷到底需要使用哪個
    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) {
                //改變bean最重要的就是改變bean所對應的className屬性  
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }
    //註冊beanDefinition,Class為AnnotationAwareAspectJAutoProxyCreator.class,beanName為internalAutoProxyCreator
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

以上程式碼實現了自動註冊AnnotationAwareAspectJAutoProxyCreator類的功能,同時這裡還涉及了一個優先順序的問題,如果已經存在了自動代理建立器,而且存在的自動代理建立器與現在的不一致,那麼需要根據優先順序來判斷到底需要使用哪個。

處理proxy-target-class以及expose-proxy屬性

useClassProxyingIfNecessary實現了proxy-target-class屬性以及expose-proxy屬性的處理,進入到方法內部:

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
    if (sourceElement != null) {
        //實現了對proxy-target-class的處理
        boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
        if (proxyTargetClass) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        //對expose-proxy的處理
        boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
        if (exposeProxy) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}

在上述程式碼中用到了兩個強制使用的方法,強制使用的過程其實也是一個屬性設定的過程,兩個函式的方法如下:

public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
    }
}

public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
    }
}
  • proxy-target-class:Spring AOP部分使用JDK動態代理或者CGLIB來為目標物件建立代理。(建議儘量使用JDK的動態代理),如果被代理的目標物件實現了至少一個介面, 則會使用JDK動態代理。所有該目標型別實現的介面都將被代理。若該目標物件沒有實現任何介面,則建立一個CGLIB代理。如果你希望強制使用CGLIB代理,(例如希望代理目標物件的所有方法,而不只是實現自介面的方法)那也可以。但是需要考慮以下兩個問題。
  1. 無法通知(advise) Final方法,因為它們不能被覆寫。
  2. 你需要將CGLIB二進位制發行包放在classpath下面。

與之相較,JDK本身就提供了動態代理,強制使用CGLIB代理需要將<aop:config>的 proxy-target-class 厲性設為 true:

<aop:config proxy-target-class = "true">...</aop:config>

當需要使用CGLIB代理和@AspectJ自動代理支援,可以按照以下方式設罝<aop:aspectj- autoproxy>的 proxy-target-class 屬性:

<aop:aspectj-autoproxy proxy-target-class = "true"/>
  • JDK動態代理:其代理物件必須是某個介面的實現,它是通過在執行期間建立一個介面的實現類來完成對目標物件的代理。
  • CGIJB代理:實現原理類似於JDK動態代理,只是它在執行期間生成的代理物件是針對目標類擴充套件的子類。CGLIB是高效的程式碼生成包,底層是依靠ASM (開源的Java位元組碼編輯類庫)操作位元組碼實現的,效能比JDK強。
  • expose-proxy:有時候目標物件內部的自我呼叫將無法實施切面中的增強,如下示例:
public interface AService { 
    public void a(); 
    public void b();
}

@Service()
public class AServicelmpll implements AService {
    @Transactional(propagation = Propagation.REQUIRED) 
    public void a() { 
        this.b{);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW) 
    public void b() {
    }
}

此處的this指向目標物件,因此呼叫this.b()將不會執行b事務切面,即不會執行事務增強, 因此 b 方法的事務定義“@Transactional(propagation = Propagation.REQUIRES_NEW)” 將不會實施,為了解決這個問題,我們可以這樣做:

<aop:aspectj-autoproxy expose-proxy = "true"/>

然後將以上程式碼中的 “this.b();” 修改為 “((AService) AopContext.currentProxy()).b();” 即可。 通過以上的修改便可以完成對a和b方法的同時增強。

&n