深入理解Spring(AOP)(三)

關於Spring自定義註解:

前兩篇,已經介紹了。spring官方文檔大部分功能,在學習AOP之前,我們先來了解spring自定義註解的知識點。(ps:沒有關注的小哥哥可以關注一下哦。持續更新spring源碼,暴力拆解,以及手寫spring

自定義註解的作用:
1.利用註解實現AOP攔截以及操作日誌記錄
2.利用註解實現對前端參數的數據有效性校驗。
3.利用註解結合反射獲取java bean屬性字段的額外信息
直接上代碼讓你瞭解mybatis部分原理(並理解自定義註解):(通過自定義註解獲取數據庫表名稱
第一步 創建一個包定義自定義註解:

這裏的@Entity可以理解爲@Table只是這裏是我自己實現的

/**
 * 自定義註解
 * @Target @Target定義註解作用的位置,如果不寫。默認出現在任何地方
 * ElementType.TYPE 表示自定義註解,能出現在類上面
 * ElementType.FIELD 表示自定義註解,能出現在屬性上
 * @Retention @Retention定義自定義註解的生命週期,默認只存在編譯期
 */
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
    /**
     * 申明一個屬性,並賦默認值 “”
     * @return
     */
    public String value() default "";

    public String name() default "";
}
第二步 使用自定義註解,並設置表的名稱
@Entity(value = "city")
public class CityEntity {
    String name;
    String id;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
}
第三步 創建一個工具類,編寫一個通過一個對象生成查詢sql的方法
public class Commutil {
    /**
     * 通過一個對象創建一條SQL語句
     * @param object
     * @return
     */
    public  static String buildQuerySqlForEntiey(Object object){
        //獲取當前傳入類,上面自定義註解的值
        Class clazz = object.getClass();
        //第一步:判斷該類是否加了@Entity 這個註解
        if (clazz.isAnnotationPresent(Entity.class)) {
            //第二步:得到這個註解
             Entity entity = (Entity) clazz.getAnnotation(Entity.class);
             //第三步:調用方法 得到註解的Value值
            String entityName = entity.value();
            String sql = "select * from "+ entityName;
            return sql;
        }
        return "";
    }
}
第四步創建對象,並測試。。

控制檯輸出:select * from city

獲取到了一句完整的sql語句。get到mybatis是怎麼做的了嘛?那麼其他查詢,其他條件的單表查詢。似乎看起來並沒有那麼難吧?(ps:後面會持續,講解mybatis源碼部分
public class Test {
    public static void main(String[] args) {
        CityEntity cityEntity = new CityEntity();
        cityEntity.setId("1");
        cityEntity.setName("test");
        //調用sql語句方法
        String sql = Commutil.buildQuerySqlForEntiey(cityEntity);
        System.out.println(sql);
    }
}

直接上 spring AOP常見面試題目:

1、Aop是什麼?

參考資料:
spring官方網站

OOP對比,面向切面,傳統的OOP開發中的代碼邏輯是自上而下的,而這些過程會產生一些橫切性問題,這些橫切性的問題和我們的主業務邏輯關係不大,這些橫切性問題不會影響到主邏輯實現的,但是會散落到代碼的各個部分,難以維護。AOP是處理一些橫切性問題,AOP的編程思想就是把這些問題和主業務邏輯分開,達到與主業務邏輯解耦的目的。使代碼的重用性和開發效率更高。
在這裏插入圖片描述
(面試題)aop的應用場景有哪些?
1、日誌記錄
2、權限驗證
3、效率檢查
4、事務管理
5、exception

springAop的底層技術整理:

JDK動態代理 CGLIB代理
編譯時期的織入還是運行時期的織入? 運行時期織入 運行時期織入
初始化時期織入還是獲取對象時期織入? 初始化時期織入 初始化時期織入

2、springAopAspectJ的關係?

Aop是一種概念(思想)
springAopAspectJ都是Aop的實現,SpringAop有自己的語法,但是語法複雜,所以SpringAop藉助了AspectJ的註解,但是底層實現還是Spring自己的

spring AOP提供兩種編程風格:

1、@AspectJ support ------------>Spring利用aspectj的註解
2、Schema-based AOP support ----------->xml aop:config 命名空間
證明: spring,通過源 碼分析了,我們可以知道spring底層使用的是JDK或者CGLIB來完成的代理,並且在官網上spring給出了aspectj的文檔,和springAOP是不同的

3、spring Aop的概念(重點)

aspect:(切面)一定要給spring去管理

pointcut:切點表示下面連接點的集合----------->pointcut理解爲一張表Joinpoint爲表中的記錄

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

Weaving :把代理邏輯(增強的部分)加入到目標對象上的過程叫做織入

target 目標對象 原始對象

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

advice:通知(表示連接的位置放在那裏)

advice通知類型:
Before 連接點執行之前,但是無法阻止連接點的正常執行,除非該段執行拋出異常
After  連接點正常執行之後,執行過程中正常執行返回退出,非異常退出
After throwing  執行拋出異常的時候
After (finally)  無論連接點是正常退出還是異常退出,都會執行
Around advice: 圍繞連接點執行,例如方法調用。這是最有用的切面方式。around通知可以在方法調用之前和之後執行自定義行爲。它還負責選擇是繼續加入點還是通過返回自己的返回值或拋出異常來快速建議的方法執行。


Proceedingjoinpoint 和JoinPoint的區別:
Proceedingjoinpoint 繼承了JoinPoint,proceed()這個是aop代理鏈執行的方法。並擴充實現了proceed()方法,用於繼續執行連接點。JoinPoint僅能獲取相關參數,無法執行連接點。

JoinPoint的方法
1.java.lang.Object[] getArgs():獲取連接點方法運行時的入參列表; 
2.Signature getSignature() :獲取連接點的方法簽名對象; 
3.java.lang.Object getTarget() :獲取連接點所在的目標對象; 
4.java.lang.Object getThis() :獲取代理對象本身;

proceed()有重載,有個帶參數的方法,可以修改目標方法的的參數

Introductions
perthis
使用方式如下:
@Aspect("perthis(this(com.bing.dao.IndexDaoImpl))")
要求:
1. AspectJ對象的注入類型爲prototype
2. 目標對象也必須是prototype的
原因爲:只有目標對象是原型模式的,每次getBean得到的對象纔是不一樣的,由此針對每個對象就會產生新的切面對象,才能產生不同的切面結果。

4、 springAop支持AspectJ步驟:

1、啓用@AspectJ支持
使用Java Configuration啓用@AspectJ支持
要使用Java @Configuration啓用@AspectJ支持,請添加@EnableAspectJAutoProxy註釋

@Configuration
@ComponentScan(value = "com")
@EnableAspectJAutoProxy
public class Appconfig {
}

2、使用XML配置啓用@AspectJ支持
要使用基於xml的配置啓用@AspectJ支持,可以使用aop:aspectj-autoproxy元素

<aop:aspectj-autoproxy/>

3、聲明一個Aspect
申明一個@Aspect註釋類,並且定義成一個bean交給Spring管理。

@Component
@Aspect
public class UserAspect {
}

4、申明一個pointCut
切入點表達式由@Pointcut註釋表示。切入點聲明由兩部分組成:一個簽名包含名稱和任何參數,以及一個切入點表達式,該表達式確定我們對哪個方法執行感興趣。

切入點確定感興趣的join points(連接點),從而使我們能夠控制何時執行通知。Spring AOP只支持Spring bean的方法執行 join points(連接點),所以您可以將切入點看作是匹配Spring bean上方法的執行。

/**
 * 申明Aspect,並且交給spring容器管理
 */
@Component
@Aspect
public class UserAspectj {
    /**
     * 申明切入點,匹配IndexDao所有方法調用
     * execution匹配方法執行連接點
     * within:將匹配限制爲特定類型中的連接點
     * args:參數
     * target:目標對象
     * this:代理對象
     */
    @Pointcut("execution(* com.bing.dao.IndexDao.*(..))")// 切入點表達式
    private void pointCut() {
        System.out.println("point cut");// 切入點簽名
    }

4、申明一個Advice通知

advice通知與pointcut切入點表達式相關聯,並在切入點匹配的方法執行@Before之前、@After之後或前後運行。

/**
 * 申明Aspect,並且交給spring容器管理
 */
@Component
@Aspect
public class UserAspectj {
    /**
     * 申明切入點,匹配IndexDao所有方法調用
     * execution匹配方法執行連接點
     * within:將匹配限制爲特定類型中的連接點
     * args:參數
     * target:目標對象
     * this:代理對象
     */
    @Pointcut("execution(* com.bing.dao.IndexDao.*(..))")// 切入點表達式
    private void pointCut() {
        System.out.println("point cut");// 切入點簽名
    }
    /**
     * 申明before通知,在pintCut切入點前執行
     * 通知與切入點表達式相關聯,
     * 並在切入點匹配的方法執行之前、之後或前後運行。
     * 切入點表達式可以是對指定切入點的簡單引用,也可以是在適當位置聲明的切入點表達式。
     */
    @Before("pointCut()")
    public void beforeAdvice(){
        System.out.println("before");
    }
}

5、各種連接點joinPoint的意義:

1、execution:
用於匹配方法執行join points連接點,最小粒度方法,方法的返回類型,方法的名稱,在aop主要使用。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
這裏問號表示當前項可以有也可以沒有,其中各項的語義如下:

modifiers-pattern:方法的可見性,如publicprotected *表示任何方法;

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

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

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

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

throws-pattern:方法拋出的異常類型,如java.lang.Exception;
example:
@Pointcut("execution(* com.bing.dao.*.*(..))")//匹配com.bing.dao包下的任意接口和類的任意方法

@Pointcut("execution(public * com.bing.dao.*.*(..))")//匹配com.bing.dao包下的任意接口和類的public方法

@Pointcut("execution(public * com.bing.dao.*.*())")//匹配com.bing.dao包下的任意接口和類的public 無方法參數的方法

@Pointcut("execution(* com.bing.dao.*.*(java.lang.String, ..))")//匹配com.bing.dao包下的任意接口和類的第一個參數爲String類型的方法

@Pointcut("execution(* com.bing.dao.*.*(java.lang.String))")//匹配com.bing.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法

@Pointcut("execution(* com.bing.dao.*.*(java.lang.String))")//匹配com.bing.dao包下的任意接口和類的只有一個參數,且參數爲String類型的方法

@Pointcut("execution(public * *(..))")//匹配任意的public方法

@Pointcut("execution(* te*(..))")//匹配任意的以te開頭的方法

@Pointcut("execution(* com.bing.dao.IndexDao.*(..))")//匹配com.bing.dao.IndexDao接口中任意的方法

@Pointcut("execution(* com.bing.dao..*.*(..))")//匹配com.bing.dao包及其子包中任意的方法

關於這個表達式的詳細寫法,可以腦補也可以參考官網很容易的,可以作爲一個看spring官網文檔的入門,打破你害怕看官方文檔的心理,其實你會發覺官方文檔也是很容易的
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
由於Spring切面粒度最小是達到方法級別,而execution表達式可以用於明確指定方法返回類型,類名,方法名和參數名等與方法相關的信息,並且在Spring中,大部分需要使用AOP的業務場景也只需要達到方法級別即可,因而execution表達式的使用是最爲廣泛的

2、(面試問題)within與execution區別

™表達式的最小粒度爲類
// within與execution相比,粒度更大,僅能實現到包和接口、類級別。而execution可以精確到方法的返回值,參數個數、修飾符、參數類型等

@Pointcut("within(com.bing.dao.*)")//匹配com.chenss.dao包中的任意方法

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

3、args,(args同execution區別)

args表達式的作用是匹配指定參數類型和指定參數的方法,與包名和類名無關
/**
 * args同execution不同的地方在於:
 * args匹配的是運行時傳遞給方法的參數類型
 * execution(* *(java.io.Serializable))匹配的是方法在聲明時指定的方法參數類型。
 */
@Pointcut("args(java.io.Serializable)")//匹配運行時傳遞的參數類型爲指定類型的、且參數個數和順序匹配
@args爲自定義註解:
@Pointcut("@args(com.bing.anno.Chenss)")//接受一個參數,並且傳遞的參數的運行時類型具有`@Classified`

4、this JDK代理時,指向接口和代理類proxy,cglib代理時 指向接口和子類(不使用proxy)
this就是基於JDK代理的方式。

這裏講一個關於JDK面試重要的問題,源代碼如下:

問題一:思考一下?爲什麼會報錯說找不到 IndexDao,我放在容器裏,爲什麼就找不到了?Spring給我弄丟了嘛?

//配置類
@Configuration
@ComponentScan(value = "com")
@EnableAspectJAutoProxy
public class Appconfig {
}

//聲明接口
public interface Dao {
    public void query();
    public void query(String str);
}

/**
 * 實現Dao 接口
 */
@Repository(value = "indexDao")
public class IndexDao implements Dao{
    @Override
    public void query(){
        System.out.println("Query");
    }
    @Override
    public void query(String str) {
        System.out.println("query " + str);
    }
}
/**
 * 測試方法
 */
public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(Appconfig.class);
        Dao bean = (IndexDao) applicationContext.getBean("indexDao");
        bean.query();
        bean.query("aa");

    }
}
/*
*  報錯信息 ================================
*/
四月 09, 2020 12:35:43 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7106e68e: startup date [Thu Apr 09 00:35:43 CST 2020]; root of context hierarchy
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.bing.dao.IndexDao' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:346)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:333)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1107)
	at com.bing.test.Test.main(Test.java:12)

問題二:我們改成用接口接收。爲什麼我們從容器中拿出來的indexDao代理對象居然不等於 IndexDao?

在這裏插入圖片描述

問題三 :再次顛覆 爲什麼我們從spring容器中拿到的對象會等於Proxy?

在這裏插入圖片描述

問題四:爲什麼拿出來的對象會跟接口相等,難道spring重新給我生成了一個對象?

在這裏插入圖片描述

解答:下面我用一段代碼撕開,關於SpringAOP JDK動態代理的問題

將spring動態代理的Class還原,並寫到本地磁盤D文件。

/**
 * 測試方法
 */
public class Test {
    public static void main(String[] args) throws IOException {
        //還原JDK動態代理的Class
        Class<?>[] interfaces = new Class[]{Dao.class};
        //定義生成的文件名,與要代理的Class 寫到本地磁盤
        byte[] bytes = ProxyGenerator.generateProxyClass("BingLi", interfaces);
        File file = new File("D:\\Test.class");
        FileOutputStream fw = new FileOutputStream(file);
        fw.write(bytes);
        fw.flush();
        fw.close();
如下圖,可以看到,springJDK動態代理是基於,接口代理的方式。實現Dao接口。所以就能解釋。上面的問題二,問題三,問題四。原因:,JDK基於接口代碼。代理對象,不等於目標對象了

·(面試挖坑)那麼爲什麼JDK動態代理不能用繼承的方式?爲什麼要用接口的方式?

很明顯了,已經繼承了Proxy 還能多繼承嘛?(ps:下一篇,會對源碼深度的分析。不要錯過哦)

在這裏插入圖片描述

那麼這個問題怎麼解決呢?

只需要將這裏設爲true 就改爲cglib動態代理方式就行了。(cglib是基於繼承目標對象,完成動態代理!)深入瞭解了這個問題那麼This與target就迎刃而解
在這裏插入圖片描述
5、target 指向接口和子類

/**
 * 此處需要注意的是,如果配置設置proxyTargetClass=false,或默認爲false,則是用JDK代理,否則使用的是CGLIB代理
 * JDK代理的實現方式是基於接口實現,代理類繼承Proxy,實現接口。
 * 而CGLIB繼承被代理的類來實現。
 * 所以使用target會保證目標不變,關聯對象不會受到這個設置的影響。
 * 但是使用this對象時,會根據該選項的設置,判斷是否能找到對象。
 */
@Pointcut("target(com.bing.dao.IndexDaoImpl)")//目標對象,也就是被代理的對象。限制目標對象爲com.bing.dao.IndexDaoImpl類

@Pointcut("this(com.bing.dao.IndexDaoImpl)")//當前對象,也就是代理對象,代理對象時通過代理目標對象的方式獲取新的對象,與原值並非一個

@Pointcut("@target(com.bing.anno.Chenss)")//具有@Chenss的目標對象中的任意方法

@Pointcut("@within(com.bing.anno.Chenss)")//等同於@targ

這個比較難…
proxy模式裏面有兩個重要的術語
proxy Class
target Class
CGLIB和JDK有區別 JDK是基於接口 cglib是基於繼承所有this可以在cglib作用

6、@annotation

這個很簡單........
作用方法級別

上述所有表達式都有@ 比如@Target(裏面是一個註解類xx,表示所有加了xx註解的類,和包名無關)

注意:上述所有的表達式可以混合使用,|| && !

@Pointcut("@annotation(com.bing.anno.Chenss)")//匹配帶有com.chenss.anno.Chenss註解的方法

@Pointcut("bean(dao1)") //名稱爲dao1的bean上的任意方法
@Pointcut("bean(dao*)")

關於AOP 的生命週期,與AOP 多例,模式會出現的一些問題?下一篇,會接着講。AOP相關的技術!!

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