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方法的同時增強。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM