spring AOP

分布于應(yīng)用中多處的功能稱為橫切關(guān)注點(diǎn),通過(guò)這些橫切關(guān)注點(diǎn)在概念上是與應(yīng)用的業(yè)務(wù)邏輯相分離的,但其代碼往往直接嵌入在應(yīng)用的業(yè)務(wù)邏輯之中。將這些橫切關(guān)注點(diǎn)與業(yè)務(wù)邏輯相分離正是面向切面編程(AOP)所要解決的。

什么是面向切面編程

image.png

切面實(shí)現(xiàn)了橫切關(guān)注點(diǎn)的模塊化

面向切面編程中,通過(guò)聲明的方式定義通用功能(安全、事務(wù)等)以何種方式在何處應(yīng)用,而無(wú)需修改受影響的類(CourseService、StudentService等)。

AOP術(shù)語(yǔ)

通知(Advice):何種功能、何時(shí)

切面的工作被稱為通知,同時(shí)通知還要解決何時(shí)執(zhí)行這個(gè)工作的問(wèn)題。Spring切面可以應(yīng)用5種類型的通知:

Before:在方法被調(diào)用之前調(diào)用通知;

After:在方法調(diào)用之后調(diào)用通知;

After-returning:在方法成功執(zhí)行后;

After-throwing:在方法拋出異常后;

Around:在方法調(diào)用之前和之后都會(huì)調(diào)用通知;

連接點(diǎn)(Joinpoint):能夠應(yīng)用通知的點(diǎn)

連接點(diǎn)是在應(yīng)用執(zhí)行過(guò)程中能夠插入切面的一個(gè)點(diǎn),這個(gè)點(diǎn)可以是調(diào)用方法時(shí)、拋出異常時(shí)、甚至修改一個(gè)字段時(shí)。切面代碼可以利用這些點(diǎn)插入到應(yīng)用的正常流程中。

切點(diǎn)(Pointcut):何處,應(yīng)用通知的連接點(diǎn)的集合

切點(diǎn)的定義會(huì)匹配通知所要織入的一個(gè)或多個(gè)連接點(diǎn)。我們通常使用明確的類和方法名稱來(lái)指定這些切點(diǎn),或是利用正則表達(dá)式定義匹配來(lái)指定這些切點(diǎn)。

切面(Aspect)

切面是通知和切點(diǎn)的結(jié)合,即何時(shí)在何處完成何種功能。

引入(Introduction)

引入允許我們向現(xiàn)有的類添加新方法或?qū)傩?,從而可以在無(wú)需修改現(xiàn)有類的情況下,讓它們具有新的行為和狀態(tài)。

織入(Weaving)

將切面應(yīng)用到目標(biāo)對(duì)象來(lái)創(chuàng)建新的代理對(duì)象的過(guò)程。切面在指定的連接點(diǎn)被織入到目標(biāo)對(duì)象中,在目標(biāo)對(duì)象的生命周期里有多個(gè)點(diǎn)可以進(jìn)行織入:

編譯期:需要特殊的編譯器,AspectJ的織入編譯器就是這種方式;

類加載期:在目標(biāo)類加載到JVM時(shí)被織入,需要特殊的類加載器。

運(yùn)行期:在應(yīng)用運(yùn)行的某個(gè)時(shí)刻被織入,一般情況下,在織入切面時(shí),AOP容器會(huì)為目標(biāo)對(duì)象動(dòng)態(tài)地創(chuàng)建一個(gè)代理對(duì)象。Spring AOP就是這種方式。

Spring對(duì)AOP的支持

基于代理的經(jīng)典AOP;

@AspectJ注解驅(qū)動(dòng)的切面;

純POJO切面;

注入式AspectJ切面(適合Spring個(gè)版本);

Spring是在運(yùn)行期將切面織入到所管理的Bean中的,如圖所示,代理類封裝了目標(biāo)類,當(dāng)攔截到方法調(diào)用時(shí),在調(diào)用目標(biāo)Bean的方法之前,代理會(huì)執(zhí)行切面邏輯。真正應(yīng)用需要被代理的Bean時(shí),Spring才會(huì)創(chuàng)建代理對(duì)象。Spring的切面由包裹了目標(biāo)對(duì)象的代理類實(shí)現(xiàn),代理類處理方法的調(diào)用,執(zhí)行額外的切面邏輯,并調(diào)用目標(biāo)方法。

image.png

Spring的切面由包裹了目標(biāo)對(duì)象的代理類實(shí)現(xiàn),代理類處理方法的調(diào)用,執(zhí)行額外的切面邏輯,并調(diào)用目標(biāo)方法。

Spring只支持方法連接點(diǎn),缺少對(duì)字段連接點(diǎn)的支持,例如攔截對(duì)象字段的修改。也不支持構(gòu)造器連接點(diǎn),也就無(wú)法在Bean創(chuàng)建時(shí)應(yīng)用通知。

使用切點(diǎn)選擇連接點(diǎn)

Spring AOP中,需要使用AspectJ的切點(diǎn)表達(dá)式來(lái)定義切點(diǎn)。

AspectJ指示器 描述
arg() 限制連接點(diǎn)匹配參數(shù)為指定類型的執(zhí)行方法
@args() 限制連接點(diǎn)匹配參數(shù)由指定注解標(biāo)注的執(zhí)行方法
execution() 用于匹配是連接點(diǎn)的執(zhí)行方法
this() 限制連接點(diǎn)匹配AOP代理的Bean引用為指定類型的類
target() 限制連接點(diǎn)匹配目標(biāo)對(duì)象為執(zhí)行類型的類
@target() 限制連接點(diǎn)匹配特定的執(zhí)行對(duì)象,這些對(duì)象對(duì)應(yīng)的類要具備指定類型的注解
within() 限制連接點(diǎn)匹配指定的類型
@within() 限制連接點(diǎn)匹配指定注解所標(biāo)注的類型
@annotation() 限制匹配帶有指定注解連接點(diǎn)

編寫切點(diǎn)

image.png

使用AspectJ切點(diǎn)表達(dá)式來(lái)定位

這里使用了execution()指示器來(lái)選擇Instrument的play()方法。表達(dá)式以*開(kāi)頭表示不關(guān)心返回值的類型,然后指定了全限定類名和方法名,使用..作為方法的參數(shù)列表,表示可以是任意的入?yún)ⅰ?/p>

使用&&將execution()和within()進(jìn)行連接,那么也就可以使用||(或)和!(非)。

image.png

使用within()指示器限制切點(diǎn)范圍

使用Spring的bean()指示器

bean()使用Bean id來(lái)作為參數(shù),從而限制切點(diǎn)只匹配特定的Bean,如:

execution(* com.springinaction.springidol.Instrument.play()) and bean(eddie)

這里,表示在執(zhí)行Instrument的play()方法時(shí)應(yīng)用通知,但限定Bean的id為eddie。

在XML中聲明切面

AOP配置元素 描述
<aop:advisor> 定義AOP通知器
<aop:after> 定義AOP后置通知(不管該方法是否執(zhí)行成功)
<aop:after-returning> 在方法成功執(zhí)行后調(diào)用通知
<aop:after-throwing> 在方法拋出異常后調(diào)用通知
<aop:around> 定義AOP環(huán)繞通知
<aop:aspect> 定義切面
<aop:aspect-autoproxy> 定義@AspectJ注解驅(qū)動(dòng)的切面
<aop:before> 定義AOP前置通知
<aop:config> 頂層的AOP配置元素,大多數(shù)的<aop:*>包含在<aop:config>元素內(nèi)
<aop:declare-parent> 為被通知的對(duì)象引入額外的接口,并透明的實(shí)現(xiàn)
<aop:pointcut> 定義切點(diǎn)
所需jar包:

image.png

表演者接口:

public interface Performer {

    void perform();

}

歌唱家類:

public class Instrumentalist implements Performer {

    private String song;

    private Instrument instrument;

    public Instrumentalist() {

    }

    public void perform() {

        System.out.print("Playing " + song + " : ");

        instrument.play();

    }

    public void setSong(String song) { // 注入歌曲

        this.song = song;

    }

    public String getSong() {

        return song;

    }

    public String screamSong() {

        return song;

    }

    public void setInstrument(Instrument instrument) { // 注入樂(lè)器

        this.instrument = instrument;

    }

}

樂(lè)器接口:

public interface Instrument {

    public void play();

}

吉他類:

public class Guitar implements Instrument {

    public void play() {

        System.out.println("Strum strum strum");

    }

}

下面定義一個(gè)觀眾類:

public class Audience {    // 表演之前
    public void takeSeats() {
        System.out.println("The audience is taking their seats.");
    }    // 表演之前
    public void turnOffCellPhones() {
        System.out.println("The audience is turning off their cellphones");
    }    // 表演之后
    public void applaud() {
        System.out.println("CLAP CLAP CLAP CLAP CLAP");
    }    // 表演失敗之后
    public void demandRefund() {
        System.out.println("Boo! We want our money back!");
    }
}

聲明前置和后置通知

<?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">

    <bean id="eddie" class="com.springinaction.springidol.Instrumentalist">
        <property name="instrument">
            <bean class="com.springinaction.springidol.Guitar" />
        </property>
        <property name="song" value="my love" />
    </bean>

    <bean id="audience" class="com.springinaction.springidol.Audience" />

    <aop:config>
        <aop:aspect ref="audience"><!-- 引用audience Bean -->
            <!-- 聲明切入點(diǎn) -->
            <aop:pointcut id="performance"
                expression="execution(* com.springinaction.springidol.Performer.perform(..))" />
            <!-- 表演之前 -->
            <aop:before pointcut-ref="performance" method="takeSeats" />
            <aop:before pointcut-ref="performance" method="turnOffCellPhones" />
            <!-- 表演之后 -->
            <aop:after-returning pointcut-ref="performance"
                method="applaud" />
            <!-- 表演失敗之后 -->
            <aop:after-throwing pointcut-ref="performance"
                method="demandRefund" />
        </aop:aspect>
    </aop:config></beans>

在<aop:config>中,可以聲明一個(gè)或多個(gè)通知器、切面或者切點(diǎn)。pointcut屬性定義了通知所引用的切點(diǎn)。最終的通知邏輯如何織入到業(yè)務(wù)邏輯中:


image.png

Audience切面包含4中通知,這些通知把通知=邏輯織入到匹配的切面的切點(diǎn)方法中

測(cè)試代碼:

@Test
    public void testBeforeAndAfter() throws PerformanceException{
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-idol.xml");
        Performer performer = (Performer) context.getBean("eddie");
        performer.perform();
    }

測(cè)試結(jié)果:

The audience is taking their seats.
The audience is turning off their cellphones
Playing my love : Guitar Guitar Guitar
CLAP CLAP CLAP CLAP CLAP

聲明環(huán)繞通知

前置通知和后置通知之間共享消息需要使用成員變量,而Audience是單例,使用成員變量有可能存在線程安全問(wèn)題。使用環(huán)繞通知可以完成之前前置和后置所實(shí)現(xiàn)的相同功能,而且只需一個(gè)方法。

package com.springinaction.springidol;
import org.aspectj.lang.ProceedingJoinPoint;
public class AroundAudience {    
public void watchPerformance(ProceedingJoinPoint joinpoint) {        
try {            // 表演之前
            System.out.println("The audience is taking their seats.");
            System.out.println("The audience is turning off their cellphones");            
            long start = System.currentTimeMillis();            // 執(zhí)行被通知的方法
            joinpoint.proceed();            // 表演之后
            long end = System.currentTimeMillis();
            System.out.println("CLAP CLAP CLAP CLAP CLAP");
            System.out.println("The performance took " + (end - start) + " milliseconds.");
        } catch (Throwable t) {            // 表演失敗之后
            System.out.println("Boo! We want our money back!");
        }
    }
}

ProceedingJoinPoint作為入?yún)ⅲ瑥亩梢栽谕ㄖ镎{(diào)用被通知的方法。

XML配置:

<bean id="audience" class="com.springinaction.springidol.AroundAudience" />
<aop:config>
    <aop:aspect ref="audience"><!-- 引用audience Bean -->
        <!-- 聲明切入點(diǎn) -->
        <aop:pointcut id="performance"
            expression="execution(* com.springinaction.springidol.Performer.perform(..))" />
        <aop:around method="watchPerformance" pointcut-ref="performance" />
    </aop:aspect>
    </aop:config></pre>

一個(gè)實(shí)際例子---日志記錄

AspectJ使用org.aspectj.lang.JoinPoint接口表示目標(biāo)類連接點(diǎn)對(duì)象,如果是環(huán)繞增強(qiáng)時(shí),使用org.aspectj.lang.ProceedingJoinPoint表示連接點(diǎn)對(duì)象,該類是JoinPoint的子接口。任何一個(gè)增強(qiáng)方法都可以通過(guò)將第一個(gè)入?yún)⒙暶鳛镴oinPoint訪問(wèn)到連接點(diǎn)上下文的信息。我們先來(lái)了解一下這兩個(gè)接口的主要方法:
1)JoinPoint
? java.lang.Object[] getArgs():獲取連接點(diǎn)方法運(yùn)行時(shí)的入?yún)⒘斜恚?br> ? Signature getSignature() :獲取連接點(diǎn)的方法簽名對(duì)象;
? java.lang.Object getTarget() :獲取連接點(diǎn)所在的目標(biāo)對(duì)象;
? java.lang.Object getThis() :獲取代理對(duì)象本身;
2)ProceedingJoinPoint
ProceedingJoinPoint繼承JoinPoint子接口,它新增了個(gè)用于執(zhí)行連接點(diǎn)方法的方法:
? java.lang.Object proceed() throws java.lang.Throwable:通過(guò)反射執(zhí)行目標(biāo)對(duì)象的連接點(diǎn)處的方法;

二、代碼演示。

SecurityHandler.java

package com.tgb.spring;  

import org.aspectj.lang.JoinPoint;  

public class SecurityHandler{  

    private void checkSecurity(JoinPoint joinPoint){  

        for (int i = 0; i < joinPoint.getArgs().length; i++) {  

            System.out.println(joinPoint.getArgs()[i]);  

        }  

        System.out.println(joinPoint.getSignature().getName());  
        System.out.println("=====checkSecurity====");  
    }  
}

Client.java

package com.tgb.spring;  

import org.springframework.beans.factory.BeanFactory;  

import org.springframework.context.support.ClassPathXmlApplicationContext;  

import com.tgb.spring.UserManager;  

public class Client {  

    public static void main(String[] args) {  

    BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");  

        UserManager userManager=(UserManager) factory.getBean("userManager");  

        userManager.addUser("張三", "123");  

        //userManager.delUser(1);  

    }  

}

UserManager.java

package com.tgb.spring;  

public interface UserManager {  

    public void addUser(String username,String password);  

    public void delUser(int userId);  

    public String findUserById(int userId);  

    public void modifyUser(int userId,String username,String password);  

}

UserManagerImpl.java

package com.tgb.spring; 
public class UserManagerImpl implements UserManager {  

    public void addUser(String username, String password) {  

        //checkSecurity();  
   System.out.println("===UserManager.addUser===");  

    }  
    public void delUser(int userId) {  

        //checkSecurity();  
  System.out.println("===UserManager.delUser===");  

    }  

    public String findUserById(int userId) {  

        //checkSecurity();  
      System.out.println("===UserManager.findUserById===");  
    return  "張三";  

    }  

    public void modifyUser(int userId, String username, String password) {  

        //checkSecurity();  
   System.out.println("===UserManager.modifyUser===");  
    }  

//  private void checkSecurity(){  

//      System.out.println("checkSecurity");  

//  

//  }  

}

applicationContext.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:tx="http://www.springframework.org/schema/tx"  
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd  
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd  
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">  
  
<bean id="userManager" class="com.tgb.spring.UserManagerImpl" />  

<bean id="securityHandler" class="com.tgb.spring.SecurityHandler"/>  

<aop:config>  

    <aop:aspect id="securityAspect" ref="securityHandler">   

         <aop:pointcut id="addAddMethod" expression="execution(* com.tgb.spring.*.*(..))" />  

        <aop:before method="checkSecurity" pointcut-ref="addAddMethod" />  

    </aop:aspect>  

</aop:config>  

</beans>

最后編輯于
?著作權(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)容

  • 前言 只有光頭才能變強(qiáng) 上一篇已經(jīng)講解了Spring IOC知識(shí)點(diǎn)一網(wǎng)打盡!,這篇主要是講解Spring的AOP模...
    Java3y閱讀 7,021評(píng)論 8 181
  • SpringAOP的使用Demo 通過(guò)配置管理特性,Spring AOP 模塊直接將面向方面的編程功能集成到了 S...
    獨(dú)念白閱讀 526評(píng)論 0 5
  • **** AOP 面向切面編程 底層原理 代理?。?! 今天AOP課程1、 Spring 傳統(tǒng) AOP2、 Spri...
    luweicheng24閱讀 1,498評(píng)論 0 1
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 核心觀念 打拐APP作為非營(yíng)利性的公益平臺(tái),其思考的邏輯出發(fā)點(diǎn)當(dāng)然是以解決用戶的最近本需求為出發(fā)點(diǎn),而非商業(yè)模式。...
    龍隱閱讀 1,162評(píng)論 0 3

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