記錄spring AOP的一些知識

     上篇博客我們簡單的瞭解了Spring IOC的一些知識點,接下來讓我們再學習一下AOP相關的知識點。

一、什麼是AOP

     AOP相對於OOP而言,是面向切面(Aspect Oriented Programming)的,傳統的OOP開發中的代碼邏輯是至上而下的,在這些至上而下的過程中會產生一些橫切性的問題,這些橫切性的問題和我們的主業務邏輯關係不大,會散落在代碼的各個地方,造成難以維護,AOP的編程思想就是把業務邏輯和橫切的問題進行分離,從而達到解耦的目的,使代碼的重用性和開發效率高。

1、應用場景

      (1)日誌記錄

      (2)權限驗證

      (3)效率檢查

      (4)事務管理

3、aop,spring aop、aspectj的關係

    aop是一種思想,spring aop、aspectj都是實現aop的技術。

4、spring aop 中爲什麼還要使用aspcetj? spring aop 也有自己的一套語法,但是相當複雜,難以令人接受,因此spring aop藉助了aspectj的語法,但是底層還是用的自己的技術(通過源碼分析了,我們可以知道spring底層使用的是JDK或者CGLIB來完成的代理)。

5、啓用aspectj支持的兩種方式

二、Spring AOP的一應用

 aspect:一定要給spring去管理,是 抽象的概念      在類中用aspectj表示,在xml中用標籤label表示。

pointcut:切點表示連接點的集合 -------------------> 表

Joinpoint:連接點 目標對象 中的方法 ----------------> 記錄

Weaving :把代理邏輯加入到目標對象上的過程叫做織入

target: 目標對象 原始對象

aop Proxy: 代理對象 包含了原始對象的代碼和增強的代碼的那個對象

advice:通知 由需要放入邏輯代碼塊的切面內容  和 切面放入邏輯代碼的位置 組成   

通知類型: Before  、After 、 After throwing 、After (finally) 、Around advice:

三、各種連接點joinPoint的意義

1、execution

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

這裏問號表示當前項可以有也可以沒有,其中各項的語義如下:

modifiers-pattern:方法的可見性,如public,protected;

ret-type-pattern:方法的返回值類型,如int,void等;

declaring-type-pattern:方法所在類的全路徑名,如com.spring.Aspect;

name-pattern:方法名類型,如buisinessService();

param-pattern:方法的參數類型,如java.lang.String;

throws-pattern:方法拋出的異常類型,如java.lang.Exception;

example: execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))

      這是一個公共的方法 任意返回類型 包下面的BusinessObject類,businessService()方法,方法參數至少一個並且第一個爲String類型的 。

      關於這個表達式的詳細寫法,可以腦補也可以參考官網很容易的,可以作爲一個看spring官網文檔的入門,打破你害怕看官方文檔的心理,其實你會發覺官方文檔也是很容易的: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-exampl

注:由於Spring切面粒度最小是達到方法級別,而execution表達式可以用於明確指定方法返回類型,類名,方法名和參數名等與方法相關的信息,並且在Spring中,大部分需要使用AOP的業務場景也只需要達到方法級別即可,因而execution表達式的使用是最爲廣泛的

2、within

    within表達式的最小粒度爲類,其參數爲全路徑的類名(可使用通配符),表示匹配當前表達式的所有類都將被當前方法環繞。例如 (IndexDao爲類):@Pointcut("within(com.zlu.IndexDao*)")

3、args

    args表達式的作用是匹配指定參數類型和指定參數數量的方法,與包名和類名無關。例如:@Pointcut("args(String)")

4、this 和target

       this和target需要放在一起進行講解,主要目的是對其進行區別。this和target表達式中都只能指定類或者接口,在面向切面編程規範中,this表示匹配調用當前切點表達式所指代對象方法的對象,target表示匹配切點表達式指定類型的對象。比如有兩個類A和B,並且A實現了B接口,如果切點表達式爲this(B),那麼B的實例將會被匹配;如果這裏切點表達式爲target(B),那麼B的實例也即被匹配。

       在講解Spring中的this和target的使用之前,首先需要講解一個概念:業務對象(目標對象)和代理對象。對於切面編程,有一個目標對象,也有一個代理對象,目標對象是我們聲明的業務邏輯對象,而代理對象是使用切面邏輯對業務邏輯進行包裹之後生成的對象。如果使用的是Jdk動態代理,那麼業務對象和代理對象將是兩個對象,在調用代理對象邏輯時,其切面邏輯中會調用目標對象的邏輯;如果使用的是Cglib代理,由於是使用的子類進行切面邏輯織入的,那麼只有一個對象,即織入了代理邏輯的業務類的子類對象,此時是不會生成業務類的對象的。

       通過上面的講解可以看出,this和target的使用區別其實不大,大部分情況下其使用效果是一樣的,但其區別也還是有的。Spring使用的代理方式主要有兩種:Jdk代理和Cglib代理。針對這兩種代理類型,關於目標對象與代理對象,理解如下兩點是非常重要的:

(1)如果目標對象被代理的方法是其實現的某個接口的方法,那麼將會使用Jdk代理生成代理對象,此時代理對象和目標對象是兩個對象,並且都實現了該接口;

(2)如果目標對象是一個類,並且其沒有實現任意接口,那麼將會使用Cglib代理生成代理對象,並且只會生成一個對象,即Cglib生成的代理類的對象。

結合上述兩點來理解this和target的異同就相對比較簡單了。我們這裏分三種情況進行說明:

(1)this(SomeInterface)或target(SomeInterface):這種情況下,無論是對於Jdk代理還是Cglib代理,其目標對象和代理對象都是實現SomeInterface接口的(Cglib生成的目標對象的子類也是實現了SomeInterface接口的),因而this和target語義都是符合的,此時這兩個表達式的效果一樣;

(2)this(SomeObject)或target(SomeObject),這裏SomeObject沒實現任何接口:這種情況下,Spring會使用Cglib代理生成SomeObject的代理類對象,由於代理類是SomeObject的子類,子類的對象也是符合SomeObject類型的,因而this將會被匹配,而對於target,由於目標對象本身就是SomeObject類型,因而這兩個表達式的效果一樣;

(3)this(SomeObject)或target(SomeObject),這裏SomeObject實現了某個接口:對於這種情況,雖然表達式中指定的是一種具體的對象類型,但由於其實現了某個接口,因而Spring默認會使用Jdk代理爲其生成代理對象,Jdk代理生成的代理對象與目標對象實現的是同一個接口,但代理對象與目標對象還是不同的對象,由於代理對象不是SomeObject類型的,因而此時是不符合this語義的,而由於目標對象就是SomeObject類型,因而target語義是符合的,此時this和target的效果就產生了區別;這裏如果強制Spring使用Cglib代理,因而生成的代理對象都是SomeObject子類的對象,其是SomeObject類型的,因而this和target的語義都符合,其效果就是一致的。

     配置類  :

@Configuration
@ComponentScan("com.zlu")
@EnableAspectJAutoProxy(proxyTargetClass = false)//proxyTargetClass = true 表示使用cglib代 理,否則爲jdk代理
//@ImportResource("spring.xml")
public class AppConfig {
}

橫切代碼:

@Component
@Aspect
public class AspectTest {

    /**
     * Dao是接口 IndexDao是實現Dao接口的類
     */
    @Pointcut("this(com.zlu.dao.Dao)")
    //@Pointcut("this(com.zlu.dao.IndexDao)") 
    public void this1(){

    }

  
    @Before("this1()")
    public void before(JoinPoint jpt){
        System.out.println("before:");
    }

}

測試:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext a=new AnnotationConfigApplicationContext(AppConfig.class);
        Dao dao1 = a.getBean(Dao.class);
        dao1.query();
        // 使用如下代碼會報異常
//        IndexDao dao = a.getBean(IndexDao.class);
//        dao.query();
    }
}

得到結果:

如果將上述 @Pointcut("this(com.zlu.dao.Dao)")  改成  @Pointcut("this(com.zlu.dao.IndexDao)"),則顯示結果爲:

5、JoinPoint :是aop中的連接點,通過這個對象,獲得連接點的信息,比如她所在的類,他的代理對象,目標對象,參數,方法返回類型等。

例如:

   @Before("this1()")
    public void before(JoinPoint jpt){
        System.out.println("before:");
        System.out.println(jpt.getThis());
        System.out.println(jpt.getTarget());
    }

6、ProceedingJoinPoint繼承了JoinPoint,是JoinPoint類的增強,其中proceed()方法用來執行連接點方法(目標方法),用在環繞通知方法中。

@Around("myWithin()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around");
   Object o = pjp.proceed();//執行連接點方法
    System.out.println("around1");
}

大多數情況下,在環繞通知中改變連接點傳入的參數,例如:

@Around("myWithin()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
    Object[] args = pjp.getArgs();
    if(args!=null && args.length>0){
        for (int i = 0; i < args.length; i++) {
            args[i]+="hello---";
        }
    }
   System.out.println("around");
   pjp.proceed(args);//執行連接點方法
   System.out.println("around1");
}

7、引入(Introductions):將value="com.zlu.dao.*"目標對象 去實現Dao接口,可以使用defaultImpl中定義的類的方法

@DeclareParents(value="com.zlu.dao.*", defaultImpl= IndexDao.class)
public static Dao dao;
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
Dao dao = (Dao) context.getBean("testDao");
dao.query("a");
dao.query();

TestDao.java

@Repository("testDao")
public class TestDao  {
}

8、@aspect切面默認是單例模式

您可以perthis通過perthis@Aspect 註釋中來限定某個dao來創建實例,使切面成爲原型模式

@Aspect("perthis(within1())") 

@Scope("prototype")

9、spring aop和aspectj的區別:aspectj是靜態織入,springaop是動態織入。動態織入是指在運行期間織入,靜態織入是指在編譯期間完成織入

 

 

 

 

 

 

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