(FS計劃)理清楚Spring的AOP到底怎么玩


前面簡單介紹了Spring中AOP的使用,是基于XML配置。這次詳細介紹一下Spring中AOP的使用和實現(xiàn)。

0x00 回顧

package com.zing.aspect_oriented_test;
/**
 * 方法切面
 */
public class SubjectService implements InterfaceSubjectService {
    @Override
    public void AspectMethod(String str) {
        System.out.println("yo yo yo!\t" + str);
    }
}
package com.zing.aspect_oriented_test;
/**
 * 對切面的處理,之前與之后
 */public class AspectTarget {
    public void beforeYouTalk() {
        System.out.println("************beforeYouTalk, I know everything,so do not lie!");
    }
    private void afterYouTalk() {
        System.out.println("************afterYouTalk, ha ha ha,good boy");
    }
}

我們知道了切面和切面處理方法,接下來告訴Spring,切面位置和處理方法
在XML配置文件中將兩個bean配置到IoC容器中

<bean id="aspectService" class="com.zing.aspect_oriented_test.SubjectService"></bean>
<bean id="aspect" class="com.zing.aspect_oriented_test.AspectTarget"></bean>

再配置切面和切面方法

<aop:config>
    <aop:pointcut id="pointCut" expression="execution(* com.zing...*.*(..))"></aop:pointcut>
    <aop:aspect ref="aspect">
        <aop:before pointcut-ref="pointCut" method="beforeYouTalk"></aop:before>
        <aop:after pointcut="execution(* com.zing..*.*(..))"  method="afterYouTalk"></aop:after>
    </aop:aspect>
</aop:config>

上面的配置可能不太明白,因為用的是AspectJ語法,* com.zing..*.*(..)表示com.zing包下的所有子包的類與方法

例子擼完,寫個Junit測試一下

package com.zing.aspect_oriented_test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * Created by zing on 16/4/23.
 */
public class AspectJunitTest {
    @Test
    public void AopTest(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
        InterfaceSubjectService subjectService = applicationContext.getBean("aspectService",InterfaceSubjectService.class);
        subjectService.AspectMethod("Monster");
    }
}

想必看完云里霧里的,這里面用到了AOP的配置,完全沒有概念,接下來詳細解釋一下,配置的含義

0x01 Aspect切面

Aspect,是織入的節(jié)點,前面兩篇文章已經(jīng)介紹了代理,而代理的節(jié)點,就是用Aspect來標記的,因為Spring封裝了優(yōu)秀的AspectJ解決方案,Aspect作為一個既定的接口,被Spring擴展了多個具體類型的通知BeforeAdvice,AfterAdvice,ThrowsAdvice

BeforeAdvice中分析,Spring中將前置接口設定為MethodBeforeAdvice,這個接口中只需要實現(xiàn)before方法

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method var1, Object[] var2, Object var3) throws Throwable;
}

這個方法將會在目標方法執(zhí)行前被回調(diào)。

同樣AfterAdvice是后置通知,具體的繼承有AfterReturningAdvice

public interface AfterReturningAdvice extends AfterAdvice {
    void afterReturning(Object var1, Method var2, Object[] var3, Object var4) throws Throwable;
}

實現(xiàn)這個接口的afterReturning方法,在目標方法成功執(zhí)行并返回值之后,AOP會回調(diào)afterReturning方法

最后ThrowsAdvice,其實是AfterAdvice子接口,在目標方法執(zhí)行發(fā)生異常時,會被回調(diào)。
具體可以這么實現(xiàn)


class BoomException implements ThrowsAdvice{
    public void afterThrowing(IOException ioEx){
        System.out.println("IO異常:"+ioEx.getMessage());
    }

    public void afterThrowing(ClassCastException ccEx){
        System.out.println("轉(zhuǎn)換異常:"+ccEx.getMessage());
    }
}

0x02 Pointcut切點

切點定義了一個代理接入位置,決定了通知作用的連接點。當然,也可以是一堆連接點,一般用一個正則表達式標識。

public interface Pointcut {
    Pointcut TRUE = TruePointcut.INSTANCE;

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();
}

其中getMethodMatcher()就是獲取一個方法過濾器,這個方法過濾器將符合標準的方法,作為切面的連接點。
關于MethodMatcher,可以查看一下Spring源碼。這里不做過多解釋。

0x03 Advisor通知器

配置中并沒有寫Advisor,所以簡單介紹一下,一個完整的模塊,當要進行AOP編程時,需要將方法標記為切面,并定義了切面前置通知、后置通知、異常通知。定義完成,需要通過通知器,將切面和通知綁定起來,這個通知器就是Advisor。

Advisor將Advice和Pointcut結(jié)合起來,通過IoC容器來配置AOP來使用。

0x04 ProxyFactoryBean

ProxyFactoryBean是Spring利用Java的代理模式或者CGLIB來實現(xiàn)Aop的一種方式,如何在XML中配置ProxyFactoryBean?

  • 通知器Advisor使用Bean來配置。
  • 織入方法類使用Bean配置
  • 定義ProxyFactoryBean,為這個bean配置幾個參數(shù):
    • 目標:target
    • 代理接口:proxyInterface
    • 織入類:interceptName

如果不清楚可以看AOP和Spring中AOP的簡單介紹中的第0x03小節(jié)的例子。這里就扣下代碼,里面還有配置后置方法織入,異常方法通知織入,不一一介紹。

  <bean id="aspectService" class="com.zing.aspect_oriented_test.SubjectService"></bean>
  <bean id="aspect" class="com.zing.aspect_oriented_test.AspectTarget"></bean>

  <bean id="rocketProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="launchingControl"></property>
        <property name="proxyInterfaces" value="com.zing.aoptest.IRocketLaunching"></property>
        <property name="interceptorNames" value="beforeLaunch"></property>
        <property name="proxyTargetClass" value="true"></property>
    </bean>

因為ProxyFactoryBean是依靠Java或CGLIB的Proxy方式來獲取對象的,使用依靠代理的getObject()方法來作為入口。使用接下來看一下這個方法的實現(xiàn)方式

public Object getObject() throws BeansException {
//初始化通知器
        this.initializeAdvisorChain();
//區(qū)分單例模式和原始模式prototype
        if(this.isSingleton()) {
            return this.getSingletonInstance();
        } else {
            if(this.targetName == null) {
                this.logger.warn("Using non-singleton proxies with singleton targets is often undesirable. Enable prototype proxies by setting the \'targetName\' property.");
            }
            return this.newPrototypeInstance();
        }
    }

具體可以追一追newPrototypeInstance()getSingletonInstance()兩個方法,得到實現(xiàn)方式的完整過程。留給感興趣的小伙伴。因為再往里挖就挖到CGLIB和JDK對象生成里去了,感覺刨過頭了。

0x05 Schema的AOP配置

前面啰嗦了一大堆,我覺得應該介紹一下具體的配置方式

 <!--aop定義開始-->
    <aop:config>
        <!--定義通知器-->
        <aop:advisor ref="aspectSupportBean"></aop:advisor>
        <!--定義切面 ref表示引用的bean-->
        <aop:aspect ref="aspectSupportBean">
            <!--定義切面增強位置-->
            <aop:pointcut id="pcut" expression="execution(* cn.javass..*.*(..))" ></aop:pointcut>
            <!--前置通知,下面的參數(shù)跟第一個類似-->
            <aop:before pointcut="切入點表達式" pointcut-ref="切入點Bean引用"  method="前置通知實現(xiàn)方法名" arg-names="前置通知實現(xiàn)方法參數(shù)列表參數(shù)名字"/>
            <!--后置返回通知-->
            <aop:after-returning></aop:after-returning>
            <!--異常通知-->
            <aop:after-throwing></aop:after-throwing>
            <!--最終通知-->
            <aop:after></aop:after>
            <!--環(huán)繞通知-->
            <aop:around></aop:around>
            <!--引入定義-->
            <aop:declare-parents  types-matching="AspectJ語法類型表達式" implement-interface=引入的接口"  default-impl="引入接口的默認實現(xiàn)"  delegate-ref="引入接口的默認實現(xiàn)Bean引用"/>
        </aop:aspect>
    </aop:config>

如果你看完前面的東西應該不難理解這些配置
但是有一個execution,為此查閱了一下文檔,切入點指示符。執(zhí)行表達式的格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

除了返回類型模式(上面代碼片斷中的ret-type-pattern),名字模式和參數(shù)模式以外, 所有的部分都是可選的。返回類型模式?jīng)Q定了方法的返回類型必須依次匹配一個連接點。 你會使用的最頻繁的返回類型模式是*,它代表了匹配任意的返回類型。 一個全限定的類型名將只會匹配返回給定類型的方法。名字模式匹配的是方法名。
你可以使用*通配符作為所有或者部分命名模式。 參數(shù)模式稍微有點復雜:()

匹配了一個不接受任何參數(shù)的方法, 而(..)

匹配了一個接受任意數(shù)量參數(shù)的方法(零或者更多)。 模式(*)

匹配了一個接受一個任何類型的參數(shù)的方法。 模式(*,String)

匹配了一個接受兩個參數(shù)的方法,第一個可以是任意類型, 第二個則必須是String類型。

0x06 @AspectJ的AOP

是基于注解的AOP,默認Spring是不開啟的,需要再XML里添加一行配置

<aop:aspectj-autoproxy/>

之后便可以用注解的方式使用AOP了,我列舉一下

//配置切面
@Aspect() 
Public class Aspect{ 
…… 
}
//配置織入方法
@Pointcut(value="切入點表達式", argNames = "參數(shù)名列表") 
public void pointcutName(……) {}
//前置通知
@Before(value = "切入點表達式或命名切入點", argNames = "參數(shù)列表參數(shù)名")

//后置返回通知
@AfterReturning( 
value="切入點表達式或命名切入點", 
pointcut="切入點表達式或命名切入點", 
argNames="參數(shù)列表參數(shù)名", 
returning="返回值對應參數(shù)名") 
// value:指定切入點表達式或命名切入點;
// pointcut:同樣是指定切入點表達式或命名切入點,如果指定了將覆蓋value屬性指定的,pointcut具有高優(yōu)先級;
// argNames:與Schema方式配置中的同義;
// returning:與Schema方式配置中的同義。

//后置通知
@After ( 
value="切入點表達式或命名切入點", 
argNames="參數(shù)列表參數(shù)名") 
//value:指定切入點表達式或命名切入點;
// argNames:與Schema方式配置中的同義;


//異常通知
@AfterThrowing ( 
value="切入點表達式或命名切入點", 
pointcut="切入點表達式或命名切入點", 
argNames="參數(shù)列表參數(shù)名", 
throwing="異常對應參數(shù)名")

//環(huán)繞通知
@Around ( 
value="切入點表達式或命名切入點", 
argNames="參數(shù)列表參數(shù)名")

//引用
@DeclareParents( 
value=" AspectJ語法類型表達式", 
defaultImpl=引入接口的默認實現(xiàn)類) 
private Interface MyInterface;

只是簡化了配置,用起來跟Schema類似

參考

http://www.importnew.com/17795.html
http://www.importnew.com/17813.html
《Spring 內(nèi)幕技術》

轉(zhuǎn)載請注明出處:理清楚Spring的AOP到底怎么玩


FS全棧計劃目錄:https://micorochio.github.io/fs-plan/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評論 19 139
  • 本博中關于spring的文章:Spring IOC和AOP原理,Spring事務原理探究,Spring配置文件屬性...
    Maggie編程去閱讀 4,205評論 0 34
  • title: Spring_AOP源碼分析date: 2016-11-03 01:15:11categories:...
    raincoffee閱讀 1,835評論 2 36
  • 對于常用標簽div和span 就不多說了,在我們開始接觸HTML5的時候最先了解的就應該是他們了。div和span...
    經(jīng)典式微笑閱讀 463評論 1 1
  • 盛唐的月 大秦的雨 千年的禪寺 佛經(jīng)里的茶 幾章回的英雄 還有夜里的銀河倒瀉 以及淋濕的驚慌失措的你
    艾特五百里閱讀 205評論 0 0

友情鏈接更多精彩內(nèi)容