Spring AOP面向切面編程、動態代理的理解

AOP (aspect oriented programming 面向切面編程)

什麼是AOP?

與OOP相比,AOP是處理一些橫向性問題,這些橫向性問題不會影響到主邏輯的實現,但是會散落到代碼各部分,難以維護。AOP就是把這些問題和主業務分開,達到與主業務邏輯解耦的目的。

應用場景

  • 日誌記錄
  • 權限驗證
  • 效率檢查
  • 事務管理

spring Aop
AOP可以說是一種面向切面編程的思想,spring Aop和AspectJ是兩種實現方式,而spring Aop借用AspectJ中的註解語法。SpringAop又有JDK動態代理和CGLIB代理兩種實現方式。

spring Aop中的專業術語

aspect(切面)
交給spring去管理,裏面定義了 (pointcut)切點(advice)通知方法

Joinpoint(連接點)
目標對象,表示要關注的點和增強的方法,也就是我們要作用的點;

point(切點)
切點表示 (Joinpoint)連接點 的集合,主要告訴通知連接點在哪裏,切點表達式決定要通知哪些連接點。

advice(通知)
要給目標方法增強什麼功能,實現橫向業務的邏輯代碼;

target(目標對象、原始對象)
aop Proxy(代理對象)
包含了原始對象(target)的代碼和增加後的代碼的那個對象;

在這裏插入圖片描述

spring AOP提供兩種編程風格:@AspectJ support利用aspectJ註解和Schema-based AOP support 利用xml aop:config命令空間。

使用步驟

  1. 添加依賴
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.22.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
    </dependencies>
  1. 編寫切面

@Aspect
public class 切面類{
	@Around("切點表達式") //切點表達式用來匹配目標和通知


	public Object 通知方法(ProceedingJoinpoint pjp){
		//重複邏輯 計時,事務控制,權限控制
		//調用目標  result是目標方法返回的結果
		Object result=pjp.proceed();
		retrun result;
	}
}
//表示此類是一個切面類
@Aspect
@Component
public class TimeAspect {

        //表示匹配service包下的UserserviceTarget類中的所有方法
//    @Around("within(service.UserServiceTarget)")

    //表示匹配service包下的UserServiceTarget中的修飾符爲public 無返回值的a方法,參數任意個數、任意值
    @Around("execution(public void service.UserServiceTarget.a(..))")


    //組成部分1:通知方法
    //ProceedingJoinPoint 用來調用目標方法
    public Object time(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();

        //調用目標方法
        Object proceed = pjp.proceed();

        long end = System.nanoTime();
        System.out.println("方法運行時間:"+(end-start));
        return proceed;
    }
}
  1. 把切面類和目標類都交給Spring容器管理(使用基於xml的配置方式)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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"
>
    <!--啓用切面編程相關注解 如:@Aspect,@Around 還提供自動產生代理類的功能-->
    <aop:aspectj-autoproxy/>

    <context:component-scan base-package="service,aspect"/>

</beans>
  1. 使用切面
    從容器getBean根據接口獲取,容器返回的是代理對象,代理對象中進行切點的匹配,匹配到了進行通知,通知內部調用目標;
public class TestProxy {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        //實際上調用的是代理類
        UserService bean = context.getBean(UserService.class);
        System.out.println(bean.getClass());//class com.sun.proxy.$Proxy8
        bean.a(10,20);
    }
}

切點表達式

1. within 匹配類中的所有方法(粒度粗,最小粒度爲類)
語法:

within(包名.類名)

example:

@Pointcut(“within(com.chenss.dao.)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao…
)”)//匹配com.chenss.dao包及其子包中的任意方法

2. execution 表達式 可以精確到類中的每個方法(粒度細),推薦使用
語法:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern
(param-pattern) throws-pattern?)
  • modifires-pattern:方法的可見性即修飾符,?表示可有可無;
  • ret-type-pattern:返回值類型;
  • declaring-type-pattern:所在類的全路徑名,如com.spring.Aspect
  • name-pattern:方法名類型;
  • param-pattern:方法參數類型,如java.lang.String
  • throws-pattern:方法拋出異常的配型

example:

@Pointcut(“execution(* com.chenss.dao..(…))”)//匹配com.chenss.dao包下的任意接口和類的任意方法
@Pointcut(“execution(public * com.chenss.dao..(…))”)//匹配com.chenss.dao包下的任意接口和類的public方法
@Pointcut(“execution(public * com.chenss.dao..())”)//匹配com.chenss.dao包下的任意接口和類的public 無方法參數的方法
@Pointcut(“execution(* com.chenss.dao..(java.lang.String, …))”)//匹配com.chenss.dao包下的任意接口和類的第一個參數爲String類型的方法
@Pointcut(“execution(* com.chenss.dao..(java.lang.String))”)//匹配com.chenss.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法
@Pointcut(“execution(* com.chenss.dao..(java.lang.String))”)//匹配com.chenss.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法
@Pointcut(“execution(public * (…))")//匹配任意的public方法
@Pointcut("execution(
te*(…))”)//匹配任意的以te開頭的方法
@Pointcut(“execution(* com.chenss.dao.IndexDao.(…))")//匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(
com.chenss.dao….(…))”)//匹配com.chenss.dao包及其子包中任意的方法

  • 返回值的位置寫 * 表示返回值類型是任意的;
  • 類名寫* 表示任意的類;
  • *寫在參數位置只能表示一個參數;
  • ..寫在參數位置,匹配任意個數和任意類型的參數

3. args
args表達式的作用是匹配指定參數類型和參數數量的方法,與包名和類型無關;

example:

@Pointcut(“args(java.io.Serializable)”)//匹配運行時傳遞的參數類型爲指定類型的、且參數個數和順序匹配
@Pointcut("@args(com.chenss.anno.Chenss)")//接受一個參數,並且傳遞的參數的運行時類型具有@Classified

4. @annotation註解方式

自定義一個註解,方法或類上有這個註解,就通知

@annotation(包名.註解名)
    //表示方法上有@Time註解的就通知
    @Around("@annotation(aspect.Time)")

5. this
指定一個類,如果某個類或接口是這個類的子類或者就是這個類本類,或者實現了這個接口,就通知;
6.target
指定一個類,如果某個類是這個類的目標類,就通知;

JDK代理和CGLIB代理
JDK代理的實現方式是基於接口實現,代理類繼承Proxy,實現指定接口;而CGLIB繼承被代理的類來實現。所以獲取一個代理類,如果是JDK代理則獲取到的是一個新類和目標類沒有關係;如果是CGLIB則獲取到的是目標類的子類或者目標接口的實現。

通知類型

  • @Around 環繞通知 (功能最強大)
  • @Before 前置通知:在目標方法調用之前通知
  • @After 後置通知
  • @AfterReturning 正常返回通知
  • @AfterThrowing 異常返回通知

所有的修飾都是針對目標方法;

面向切面的應用

  1. spring中的事務管理
    代理對象中與@Transactional進行匹配,如果方法上有這個註解,就會調用事務通知,繼續調用目標方法TransactionIntercepter跟事務相關的通知代碼;

  2. 統一的權限控制

  3. 緩存控制

  4. 統一的日誌記錄

spring中如果目標類沒有實現接口

如果目標對象實現了接口:底層spring會調用jdk的動態代理技術來生成代理;要求代理對象和目標對象實現共同的接口;
如果不使用接口,spring通過CGLIB代理技術來生成代理對象,並不要求目標對象和代理對象實現共同接口,底層代理類繼承自目標類;

基於XML的配置

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 https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd>
        http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--開啓註解掃描-->
    <context:component-scan base-package="com.luban"/>
    <!-- bean definitions here -->
</beans>

** 聲明切面**

 <bean id="aspectJ" class="com.luban.app.LubanAspectJ"></bean>
    <aop:config>
        <aop:aspect id="aspect" ref="aspectJ"></aop:aspect>
    </aop:config>

聲明切點
允許在多個切面和通知之間共享切點定義。

 <aop:config>
        <!--定義切面-->
        <aop:aspect id="aspect" ref="aspectJ">
            <!--定義一個切點-->
            <aop:pointcut id="pointCut" expression="execution(* com.luban.dao.*.*(..))"/>
        </aop:aspect>
    </aop:config>

聲明通知

 <aop:config>
        <!--定義切面-->
        <aop:aspect id="aspect" ref="aspectJ">
            <!--定義一個切點-->
            <aop:pointcut id="pointCut" expression="execution(* com.luban.dao.*.*(..))"/>
            <!--定義一個通知-->
            <aop:before method="before" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>

具體通知方法在<bean id="aspectJ" class="com.luban.app.LubanAspectJ"></bean>引用的類中實現;

public class LubanAspectJ {
    public void pointCut(){

    }


    public void pointCut1(){

    }


    public void pointCut2(){

    }


    public void aroundM(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("around before");
        joinPoint.proceed();
        System.out.println("目標類"+joinPoint.getTarget());
        System.out.println("代理類"+joinPoint.getThis());
        System.out.println("around after");
    }



    public void before(){
        System.out.println("before advance");
    }


    public void before1(){
        System.out.println("args match");
    }


}

擴展知識點

Introduction

在這裏插入圖片描述

代表類型聲明其他方法或字段。Spring AOP允許您向任何被建議的對象引入新的接口(以及相應的實現)。例如,可以使用Introduction使bean實現IsModified接口,以簡化緩存。(在AspectJ社區中,介紹稱爲類型間聲明。)

使用類型間聲明使Aspect能夠聲明被通知的對象實現了給定的接口,併爲這些對象提供了該接口的實現。

com.luban.dao.Dao

public interface Dao {
     void test();
}

com.luban.dao.IndexDao

@Repository
public class IndexDao implements Dao{

    public void test() {
        System.out.println("hello world");
    }
}

在com.luban.dao下定義一個空類,並交由spring去管理

@Repository("introDao")
public class IntroDao {
}

在Aspect切面類中使用Introduction

    @DeclareParents(value = "com.luban.dao.*",defaultImpl = IndexDao.class)
    public static Dao dao;

意思是找到com.luban.dao下面的所有類,讓這些類去引入Dao接口的IndexDao類的實現。

測試:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext a =
                new AnnotationConfigApplicationContext(Appconfig.class);
        Dao introDao = (Dao) a.getBean("introDao");
        introDao.test();
    }

}

運行結果:

在這裏插入圖片描述\

環繞通知Around案例

環繞通知意思就是在目標方法前執行一次,在目標方法後執行一次。

 @Pointcut("execution(* com.luban.dao.*.*(..))")
    public void pointCut2(){

    }

    @Around("pointCut2()")
    public void aroundM(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("around before");
        joinPoint.proceed();
        System.out.println("around after");
    }

其中proceed表示繼續執行下一個通知或目標方法的調用,可以理解爲調用執行底層方法可以理解爲執行了目標類裏的方法。
在這裏插入圖片描述

可以通過ProceedingJoinPoint訪問到代理類、目標類等信息,其父類JoinPoint也可以訪問到,但是父類沒有提供proceed。提供對連接點可用狀態和關於連接點的靜態信息的反射訪問。這些信息可以從使用特殊形式This joinpoint的advance正文中獲得。這種反射信息的主要用途是跟蹤和記錄應用程序。
在這裏插入圖片描述

sping AOP和Full AspectJ使用分析

使用最簡單的方法。Spring AOP比使用完整的AspectJ更簡單,因爲不需要在開發和構建過程中引入AspectJ編譯器/編織器。如果您只需要建議在Spring bean上執行操作,那麼Spring AOP是正確的選擇。如果需要通知Spring容器不管理的對象(通常是域對象),則需要使用AspectJ。如果希望建議連接點而不是簡單的方法執行(例如,字段get或set連接點等等),還需要使用AspectJ。

@AspectJ註解和XML的使用分析

現有Spring用戶可能最熟悉XML樣式,它由真正的pojo支持。當使用AOP作爲配置企業服務的工具時,XML可能是一個很好的選擇(一個好的測試是,您是否將切入點表達式視爲您可能希望獨立更改的配置的一部分)。使用XML樣式,從配置中可以更清楚地看出系統中存在哪些方面。

XML樣式有兩個缺點。首先,它沒有將它所處理的需求的實現完全封裝在一個地方。DRY原則認爲,系統中的任何知識都應該有一個單一的、明確的、權威的表示。當使用XML樣式時,關於如何實現需求的知識會在支持bean類的聲明和配置文件中的XML之間進行分割。當您使用@AspectJ樣式時,這些信息被封裝在一個模塊中:方面。其次,XML樣式稍微有些限制。

@AspectJ樣式支持額外的實例化模型和更豐富的切入點組合。它的優點是保持方面作爲模塊單元。它還有一個優點,即@AspectJ方面可以被Spring AOP和AspectJ理解(從而使用)。因此,如果您稍後決定需要AspectJ的功能來實現額外的需求,您可以輕鬆地遷移到一個經典的AspectJ設置。總的來說,除了簡單的企業服務配置之外,Spring團隊更喜歡使用@AspectJ樣式來定製方面。也可以混合兩種方式使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章