Spring AOP

AOP簡(jiǎn)介

AOP的全稱是 Aspect-Oriented Programming,即面向切面編程(也稱面向方面編程)。它是面向?qū)ο缶幊蹋∣OP)的一種補(bǔ)充,目前已成為一種比較成熟的編程方式。

AOP作用

在軟件開發(fā)中,散布于應(yīng)用中多處的功能被稱為橫切關(guān)注點(diǎn),通常來(lái)講,這些橫切關(guān)注點(diǎn)從概念上是與應(yīng)用的業(yè)務(wù)邏輯相分離的(但是往往會(huì)直接嵌入到應(yīng)用的業(yè)務(wù)邏輯之中),比如通常會(huì)進(jìn)行事務(wù)處理、日志記錄等操作。在OOP設(shè)計(jì)中,它們導(dǎo)致了大量代碼的重復(fù),而不利于各個(gè)模塊的重用;但在面向切面編程中,我們?nèi)匀辉谝粋€(gè)地方定義這些功能,但是可以通過(guò)聲明的方式定義這些功能要以何種方式在何處使用,而無(wú)需修改受影響的類,這樣使得服務(wù)模塊更加簡(jiǎn)潔,因?yàn)檫@些次要關(guān)注點(diǎn)的代碼已經(jīng)被轉(zhuǎn)移到切面中了。

AOP相關(guān)術(shù)語(yǔ)

1.Aspect(切面):是通知和切點(diǎn)的結(jié)合,共同定義了切面的全部?jī)?nèi)容——它是什么,在何時(shí)和何處完成其功能。
2.Joinpoint(連接點(diǎn)):是應(yīng)用執(zhí)行過(guò)程中能夠插入切面的一個(gè)點(diǎn),切面代碼可以利用這些點(diǎn)插入到應(yīng)用的正常流程之中來(lái)添加新的行為,也就是指方法的調(diào)用。
3.Pointcut(切入點(diǎn)):定義了切面中何處完成其功能,即在哪些類以及哪些方法上切入。
4.Advice(通知/增強(qiáng)處理):定義了切面中何時(shí)做什么功能。比如應(yīng)該在目標(biāo)方法被調(diào)用之前、之后還是拋出異常后調(diào)用通知?
5.Introduction(引入):在不修改代碼的前提下,允許我們向現(xiàn)有的類添加新方法或?qū)傩浴?br> 6.Weaving(織入):是把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程。

相關(guān)通知類型

1.@After:通知方法會(huì)在目標(biāo)方法返回或拋出異常后調(diào)用。
2.@AfterReturning:通知方法會(huì)在目標(biāo)方法返回后調(diào)用。
3.@AfterThrowing:通知方法會(huì)在目標(biāo)方法拋出異常后調(diào)用。
4.@Around:通知方法會(huì)將目標(biāo)方法封裝起來(lái)。
5.@Before:通知方法會(huì)在目標(biāo)方法調(diào)用之前執(zhí)行。

使用注解創(chuàng)建切面

現(xiàn)在模擬一個(gè)場(chǎng)景,在某個(gè)演出中我們需要引入觀眾的一些反應(yīng),這里演出是核心功能,而觀眾的反應(yīng)是次要功能,因此要將觀眾定義為一個(gè)切面并應(yīng)用到表演家演出上。
1.AOP相關(guān)的pom依賴

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>aopalliance</groupId>
      <artifactId>aopalliance</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.0.7.RELEASE</version>
    </dependency>

2.創(chuàng)建表演類

@Component("performance") //聲明為一個(gè)bean
public class Performance {
    public void perform(){
        System.out.println("表演家正在表演中!");
    }
}

3.創(chuàng)建觀眾類

@Component("audience") //聲明為一個(gè)bean
@Aspect //聲明此類是個(gè)切面
public class Audience {
    /**
     * 表演家演出前需要的觀眾反應(yīng)
     */
    @Before("execution(* com.mybean.concert.Performance.perform())")
    public void beforePerform(){
        System.out.println("觀眾入席!");
        System.out.println("觀眾手機(jī)調(diào)至靜音!");
    }

    /**
     * 表演家演出成功后需要的觀眾反應(yīng)
     */
    @AfterReturning("execution(* com.mybean.concert.Performance.perform())")
    public void afterReturningPerform(){
        System.out.println("觀眾鼓掌!");
    }

    /**
     * 表演家演出出狀況后需要的觀眾反應(yīng)
     */
    @AfterThrowing("execution(* com.mybean.concert.Performance.perform())")
    public void afterThrowingPerform(){
        System.out.println("觀眾要求退款!");
    }
}

可以看出我們?cè)陬l繁地使用切點(diǎn)表達(dá)式,可以用@Pointcut使代碼變得簡(jiǎn)介:

@Component("audience")
@Aspect
public class Audience {

    @Pointcut("execution(* com.mybean.concert.Performance.perform())")
    public void performance(){
    }

    @Before("performance()")
    public void beforePerform(){
        System.out.println("觀眾入席!");
        System.out.println("觀眾手機(jī)調(diào)至靜音!");
    }

    @AfterReturning("performance()")
    public void afterReturningPerform(){
        System.out.println("觀眾鼓掌!");
    }

    @AfterThrowing("performance()")
    public void afterThrowingPerform(){
        System.out.println("觀眾要求退款!");
    }
}

通過(guò)兩個(gè)注解可以看出,雖然此類被聲明為一個(gè)切面,但是它仍然能夠像普通類一樣被其他java類調(diào)用,它的方法也能夠進(jìn)行獨(dú)立的單元測(cè)試,只不過(guò)@Aspect表明它會(huì)被作為切面使用而已,這里還將它聲明為一個(gè)bean。

4.在配置類上啟動(dòng)注解的自動(dòng)代理

@Configuration //說(shuō)明此類是個(gè)配置類
@ComponentScan //啟動(dòng)組件掃描
@EnableAspectJAutoProxy //啟用Aspect自動(dòng)代理
public class BeanConfig {
}

5.測(cè)試

public class App {
    public static void main( String[] args ) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Performance performance = (Performance) applicationContext.getBean("performance");
        performance.perform();
    }
}

控制臺(tái)打印結(jié)果如下:


截圖

創(chuàng)建環(huán)繞通知

環(huán)繞通知是最為強(qiáng)大的通知類型,它能夠讓你編寫的邏輯將被通知的目標(biāo)方法完全包裝起來(lái)。實(shí)際上就像在一個(gè)通知方法中同時(shí)編寫前置通知和后置通知。
例如上方的觀眾類可寫為:

@Component("audience")
@Aspect
public class Audience {

    @Pointcut("execution(* com.mybean.concert.Performance.perform())")
    public void performance(){
    }

    @Around("performance()")
    public void aroundPerformance(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("觀眾入席!");
            System.out.println("觀眾手機(jī)調(diào)至靜音!");
            joinPoint.proceed();
            System.out.println("觀眾鼓掌!");
        } catch (Throwable e) {
            System.out.println("觀眾要求退款!");
        }
    }
}

其它代碼不變,運(yùn)行后控制臺(tái)結(jié)果和上方一樣。
其中通過(guò)ProceedingJoinPoint來(lái)調(diào)用被通知的方法,當(dāng)要將控制權(quán)交給被通知的方法時(shí)(這里指表演類的表演方法),需要調(diào)用ProceedingJoinPoint的proceed()方法。

處理通知中的參數(shù)

如果被通知的方法中有參數(shù),且切面需要使用到這些參數(shù)呢?接著上面的情景,如果表演家表演的內(nèi)容需要被指定,通過(guò)以下例子切面能夠獲得被通知方法中的參數(shù):

@Component("performance")
public class Performance {
    public void perform(String performanceName){
        System.out.println("表演家正在演奏" + performanceName);
    }
}
@Component("performanceNumber")
@Aspect
public class PerformanceNumber {

    @Pointcut("execution(* com.mybean.concert.Performance.perform(String))" + "&& args(performanceName)")
    public void performance(String performanceName){
    }

    @Before("performance(performanceName)")
    public void beforePerform(String performanceName){
        System.out.println("觀眾入席!");
        System.out.println("觀眾手機(jī)調(diào)至靜音!");
    }

    @AfterReturning("performance(performanceName)")
    public void afterReturningPerform(String performanceName){
        System.out.println("觀眾為表演家演奏的" + performanceName + "節(jié)目鼓掌!");
    }

    @AfterThrowing("performance(performanceName)")
    public void afterThrowingPerform(String performanceName){
        System.out.println("觀眾要求退款!");
    }
}

args(performanceName)表明傳遞給perform()方法的string類型的參數(shù)也會(huì)傳遞到通知去,參數(shù)的名稱performanceName也與切點(diǎn)方法簽名中的參數(shù)相匹配。

如果是環(huán)繞通知,則:

@Component("performanceNumber")
@Aspect
public class Audience {

    @Pointcut("execution(* com.mybean.concert.Performance.perform(String))")
    public void performance(){
    }

    @Around("performance()")
    public void aroundPerformance(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("觀眾入席!");
            System.out.println("觀眾手機(jī)調(diào)至靜音!");
            joinPoint.proceed();
            Object[] args = joinPoint.getArgs();
            System.out.println("觀眾為表演家演奏的" + args[0] + "節(jié)目鼓掌!");
        } catch (Throwable e) {
            System.out.println("觀眾要求退款!");
        }
    }
}

部分ProceedingJoinPoint對(duì)象的方法信息如下:


截圖

測(cè)試:

public class App {
    public static void main( String[] args ) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Performance performance = (Performance) applicationContext.getBean("performance");
        performance.perform("鋼琴");
    }
}

運(yùn)行結(jié)果如下:


截圖

通過(guò)注解為被通知方法引入新功能

切面可以為Spring bean添加新方法。 在Spring中,切面只是實(shí)現(xiàn)了它們所包裝bean相同接口的代理。實(shí)際上,除了實(shí)現(xiàn)這些接口,代理也能暴露新接口,那樣的話,切面所通知的bean看起來(lái)像是實(shí)現(xiàn)了新的接口,即便底層實(shí)現(xiàn)類并沒有實(shí)現(xiàn)這些接口也無(wú)所謂。
先引入新接口和對(duì)應(yīng)的實(shí)現(xiàn):

public interface Encoreable {
    void performEncore();
}
@Component("encoreableImpl")
public class EncoreableImpl implements Encoreable{

    @Override
    public void performEncore() {
        System.out.println("添加的新方法");
    }
}

創(chuàng)建一個(gè)用于注入新方法的切面EncoreableIntroducer:

@Component("encoreableIntroducer")
@Aspect
public class EncoreableIntroducer {

    @DeclareParents(value="com.mybean.concert.Performance+",defaultImpl= EncoreableImpl.class)
    public static Encoreable encoreable;
}

可以看到,EncoreableIntroducer是一個(gè)切面,但是它與我們之前所創(chuàng)建的切面不同,它并沒有提供前置、后置或環(huán)繞通知,而是通過(guò)@DeclareParents注解,將Encoreable接口引入 到Performance bean中。@DeclareParents注解由三部分組成:
1.value屬性:指定了哪種類型的bean要引入該接口。
2.defaultImpl:指定了為引入功能提供實(shí)現(xiàn)的類。
3.@DeclareParents:所標(biāo)注的靜態(tài)屬性指明了要引入的接口。

測(cè)試:

public class App {
    public static void main( String[] args ) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Performance performance = (Performance) applicationContext.getBean("performance");
        ((Encoreable) performance).performEncore();
    }
}

控制臺(tái)打印如下,說(shuō)明Encoreable的方法已經(jīng)添加到Performance中,注意要進(jìn)行強(qiáng)制轉(zhuǎn)換:


截圖

在xml中聲明切面

如果需要聲明切面,但是又不能為通知類添加注解的時(shí)候,就必須轉(zhuǎn)向xml配置了。
去掉上面類的@Aspect和@Component注解后,把聲明bean和切面全在xml文件中配置:

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

    <bean id="audience" class="com.mybean.aspect.Audience">
    </bean>

    <bean id="performance" class="com.mybean.concert.Performance">
    </bean>

    <aop:config>
        <aop:aspect ref="audience">
            <aop:before pointcut="execution(* com.mybean.concert.Performance.perform())" method="beforePerform"/>
            <aop:after-returning pointcut="execution(* com.mybean.concert.Performance.perform())" method="afterReturningPerform"/>
            <aop:after-throwing pointcut="execution(* com.mybean.concert.Performance.perform())" method="afterThrowingPerform"/>
        </aop:aspect>
    </aop:config>
</beans>

如果用pointcut,則:

<aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut expression="execution(* com.mybean.concert.Performance.perform())" id="performance"/>
            <aop:before pointcut-ref="performance" method="beforePerform"/>
            <aop:after-returning pointcut-ref="performance" method="afterReturningPerform"/>
            <aop:after-throwing pointcut-ref="performance" method="afterThrowingPerform"/>
        </aop:aspect>
</aop:config>

如果是聲明環(huán)繞通知,則:

<aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut expression="execution(* com.mybean.concert.Performance.perform())" id="performance"/>
            <aop:around method="aroundPerformance" pointcut-ref="performance"></aop:around>
        </aop:aspect>
</aop:config>

如果是為通知傳遞參數(shù),則:

<aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut expression="execution(* com.mybean.concert.Performance.perform(String)) and args(performanceName)" id="performance"/>
            <aop:before pointcut-ref="performance" method="beforePerform"/>
            <aop:after-returning pointcut-ref="performance" method="afterReturningPerform"/>
            <aop:after-throwing pointcut-ref="performance" method="afterThrowingPerform"/>
        </aop:aspect>
    </aop:config>

因?yàn)椤?amp;”在xml中有特殊含義,是轉(zhuǎn)義字符的前綴,所以用and來(lái)代替“&&”。

如果要通過(guò)切面引入新功能,則:

<aop:config>
        <aop:aspect ref="audience">
            <aop:declare-parents types-matching="com.mybean.concert.Performance+" implement-interface="com.mybean.concert.Encoreable" default-impl="com.mybean.concert.EncoreableImpl"/>
        </aop:aspect>
</aop:config>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • SpringAOP的使用Demo 通過(guò)配置管理特性,Spring AOP 模塊直接將面向方面的編程功能集成到了 S...
    獨(dú)念白閱讀 538評(píng)論 0 5
  • 前言 只有光頭才能變強(qiáng) 上一篇已經(jīng)講解了Spring IOC知識(shí)點(diǎn)一網(wǎng)打盡!,這篇主要是講解Spring的AOP模...
    Java3y閱讀 7,033評(píng)論 8 181
  • 一、AOP 簡(jiǎn)介 AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方...
    leeqico閱讀 918評(píng)論 0 1
  • 1.概述 Aop(Aspect Oriented Programming),即面向切面編程,這是面向?qū)ο笏枷氲囊环N...
    Tian_Peng閱讀 3,592評(píng)論 0 2
  • 【spring-boot】spring aop 面向切面編程初接觸 眾所周知,spring最核心的兩個(gè)功能是aop...
    可可西里的星星閱讀 513評(píng)論 1 0

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