1. 程式人生 > >【修真院JAVA小課堂】Spring AOP

【修真院JAVA小課堂】Spring AOP

大家好,我是IT修真院深圳分院第十二期學員,一枚正直純潔善良的JAVA程式設計師。

今天給大家分享一下,修真院官網JAVA任務三的一個知識點:spring AOP

課題:spring AOP

1.背景介紹

2.知識剖析

3.常見問題

4.解決方案

5.編碼實戰

6.擴充套件思考

7.參考文獻

8.更多討論

 

1.背景介紹:

什麼是aop:

        通過前面的學習,我們知道 Spring 的兩大核心是 IOC 和 AOP。這裡我們說得  AOP (全稱是Aspect Oriented Programming),翻譯成中文是面向切面程式設計。它的主要思想是在程式正常執行的某一個點切進去加入特定的邏輯。Spring AOP 在程式中,可以對業務邏輯的各個部分進行隔離,提高隔離的可重用性,從而降低耦合度。

 

2.知識剖析

2.1.Spring 中的AOP。

個人對AOP思想的理解:

        AOP 的核心思想是在動態地將程式碼切入到類的指定方法、指定位置上的程式設計思想。。橫向的抽取機制,規避了傳統面向物件中縱向繼承體系的重複性高耦合程式碼。

 

        其中,我們把分佈於應用中多處的功能稱為橫切關注點,通常這些橫切關注點在概念上是與應用的業務邏輯相分離的,但其程式碼往往直接嵌入在應用的業務邏輯之中。將這些橫切關注點與業務邏輯相分離正是面向切面程式設計(AOP)所要解決的。 

 

AOP具體可以在下面的場景中使用:
        許可權、快取、內容傳遞、錯誤處理、懶載入、除錯、日誌管理、效能優化、持久化、資源池、同步、事務

 

2.2.AOP的核心概念

1、橫切關注點

對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之為橫切關注點

2、切面(aspect)

切面是通知和切入點的結合。
通知說明了擴充套件的功能是什麼和什麼時候擴充套件,而切入點說明了在哪裡進擴充套件(指定到底是哪個方法),這就是一個完整的切面定義。可以看出切面就是表述的一個過程,就是將增強應用到具體方法上的過程。

3、連線點(joinpoint)

能夠應用通知的點 ,是在應用執行過程中能夠插入切面的一個點。在spring中,這些點指的是方法,spring只支援方法級別的連線點,一個類裡有多少方法就有多少Jointpoint。

4、切入點(pointcut)

指的是我們要對哪個連線點進行攔截的定義。

5、通知(advice)

也稱為增強。指的是攔截到Joinpoint後你想要擴充套件的功能,也就是上面說的 安全,事物,日誌等。
有五種型別的通知:

有五種型別的通知:
Before(前置):在方法被呼叫之前呼叫通知;
After(後置):在方法呼叫之後呼叫通知;(不管該方法是否執行成功)
After-returning(返回結果通知):在方法返回結果後;
After-throwing(異常通知):在方法丟擲異常後;
Around(環繞):在方法呼叫之前和之後都會呼叫通知;

6、目標(target)

代理的目標物件(要增強的類)

7、織入(weave)

是把增強應用到目標的過程。

8、引入(introduction)

引入允許我們向現有的類新增新方法或屬性,從而可以在無需修改現有類的情況下,讓它們具有新的行為和狀態。

 

3.常見問題

3.1.AOP 切割的兩種方式

3.2.AOP 切入點的定位是如何完成的?

3.3Spring AOP無法攔截Controller中的方法 

 

 

4.解決方案

4.1.AOP 切割的兩種方式:

基於 xml 和基於註解的配置方式

 

4.2.AOP 切入點的定位是如何完成的?

使用表示式來配置切入點,表示式有很多種,但是絕大多數問題都可以用execution表示式來解決。

1.execution(* com.ptteng.controller.controller.listCategory(..))

2.execution(* ccom.ptteng.controller.controller.*(..))

3.execution(* *.*(..))

4.匹配所有save開頭的方法:execution(* save*(..))

 

4.3.Spring AOP無法攔截Controller中的方法

因為Spring的Bean掃描和Spring-MVC的Bean掃描是分開的, 兩者的Bean位於兩個不同的Application, 而且Spring-MVC的Bean掃描要早於Spring的Bean掃描, 所以當Controller Bean生成完成後, 再執行Spring的Bean掃描,Spring會發現要被AOP代理的Controller Bean已經在容器中存在, 配置AOP就無效了.

同樣這樣的情況也存在於資料庫事務中, 如果Service的Bean掃描配置在spring-mvc.xml中, 而資料庫事務管理器配置在application.xml中, 會導致資料庫事務失效, 原理一樣.

所以這裡 ,我們需要把AOP放置在Controller掃描配置的檔案中.

Spring的配置檔案application.xml包含了 開啟AOP自動代理,Service掃描配置, 現在只包含了service的自動掃描配置。

 

5.編碼實戰

以下是基於XML 的配置:

aop.xml

 <aop:config>
        <aop:aspect id ="time" ref="timeHandler">
            <aop:pointcut id="addAllMethod" expression="execution(* dao.HelloWorld.*(..))" />
            <aop:before method="printTime" pointcut-ref="addAllMethod"/>
            <aop:after method="printTime" pointcut-ref="addAllMethod"/>
        </aop:aspect>
        <aop:aspect id ="printLog" ref="logHandler" order="2">
            <aop:pointcut id="printLog" expression="execution(* dao.HelloWorld.*(..))"/>
            <aop:before method="LogBefore" pointcut-ref="printLog" />
            <aop:after method="LogAfter" pointcut-ref="printLog" />
        </aop:aspect>
    </aop:config>

HelloWorld.class

public interface  HelloWorld {

    void printHelloWorld();

    void doPrint();
}

HelloWorldImpl1.class

 

public class HelloWorldImpl1 implements HelloWorld {
    @Override
    public void printHelloWorld() {
        System.out.println("Enter HelloWorldImpl1.printHelloWorld()");
    }

    @Override
    public void doPrint() {
        System.out.println("Enter HelloWorldImpl1.doPrint");
//        return ;
    }
}

HelloWorldImpl2.class

public class HelloWorldImpl2 implements HelloWorld {
    @Override
    public void printHelloWorld() {
        System.out.println("Enter HelloWorldImpl2.printHelloWorld()");
    }

    @Override
    public void doPrint() {
        System.out.println("Enter HelloWorldImpl2.doPrint()");
    }
}
public class LogHandler {
    public void LogBefore(){
        System.out.println("Log before method");
    }

    public void LogAfter(){
        System.out.println("Log aafter method ");
    }
}
public class TimeHandler {
    public void printTime()
    {
        System.out.println("CurrentTime =" + System.currentTimeMillis());
    }
}
public class test01 {
    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new ClassPathXmlApplicationContext("aop.xml");

        HelloWorld hw1 = (HelloWorld)applicationContext.getBean("helloWorldImpl1");
        HelloWorld hw2 = (HelloWorld)applicationContext.getBean("helloWorldImpl2");

        hw1.printHelloWorld();
        System.out.println();
        hw1.doPrint();
        System.out.println();

        hw2.printHelloWorld();
        System.out.println();
        hw2.doPrint();
    }
}

 

以下是基於註解的配置:

@Aspect
@Component
public class TimeInterceptor {
    private static Log logger = LogFactory.getLog(TimeInterceptor.class);

    @Around("execution(* controller.*.*(..))")
    public Object aroundController(ProceedingJoinPoint proceedingJoinPoint){
        TimeInterceptorUtil timeInterceptorUtil1 = new TimeInterceptorUtil();
        Object obj = timeInterceptorUtil1.timeAround(proceedingJoinPoint);
        return obj;
    }

    @Around("execution(* mapper.*.*(..))")
    public Object aroundDao(ProceedingJoinPoint proceedingJoinPoint){
        TimeInterceptorUtil timeInterceptorUtil2 = new TimeInterceptorUtil();
        Object obj = timeInterceptorUtil2.timeAround(proceedingJoinPoint);
        return obj;
    }

}
public class TimeInterceptorUtil {
    private static Log logger = LogFactory.getLog(TimeInterceptorUtil.class);

    /**
     * 統計方法執行耗時Around環繞通知
     *
     * @return
     * @param.joinPoint
     */

//    @Around(POINT)
    public Object timeAround(ProceedingJoinPoint joinPoint) {
        // 定義返回物件、得到方法需要的引數
        Object obj = null;

        //獲取傳入目標方法的引數物件
        Object[] args = joinPoint.getArgs();

        //打印出開始時間
        long startTime = System.currentTimeMillis();

        try {
            //用引數物件執行目標方法
            obj = joinPoint.proceed(args);
        } catch (Throwable e) {
            //異常通知
            logger.error("統計某方法執行耗時環繞通知出錯", e);
        }

        // 獲取執行的方法名
        long endTime = System.currentTimeMillis();

        //獲取封裝了署名資訊的物件,在該物件中可以獲取到目標方法名,所屬類的Class等資訊
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        //獲取執行的方法名並且打印出來
        String methodName = signature.getDeclaringTypeName() + "." + signature.getName();

        // 列印耗時的資訊
        this.printExecTime(methodName, startTime, endTime);

        return obj;
    }

    /**
     * 列印方法執行耗時的資訊,如果超過了一定的時間,才打印
     *
     * @param methodName
     * @param startTime
     * @param endTime
     */
    private void printExecTime(String methodName, long startTime, long endTime) {
        long diffTime = endTime - startTime;

        logger.warn("-----" + methodName + " 方法執行耗時: " + diffTime + " ms");

        System.out.println("-----" + methodName + " 方法執行耗時: " + diffTime + " ms");
    }
}

開啟自動註解裝配:

applicationContext.xml


    //自動裝配註解
    <aop:aspectj-autoproxy/>
    <context:annotation-config/>
    <context:component-scan base-package="controller"/>

    <context:component-scan base-package="service"/>
    <!--<context:component-scan base-package="pojo"/>-->
    <context:component-scan base-package="aop"/>

spring-MVC.xml


    <context:annotation-config/>

    <mvc:annotation-driven/>

    <context:component-scan base-package="controller"/>

    <mvc:default-servlet-handler/>

    <aop:aspectj-autoproxy/>

 

6.擴充套件思考

基於註解與基於配置檔案的優缺點:

1、註解可以充分利用 Java 的反射機制獲取類結構資訊,這些資訊可以有效減少配置的工作,註釋和 Java 程式碼位於一個檔案中,而XML 配置採用獨立的配置檔案,大多數配置資訊在程式開發完成後都不會調整,如果配置資訊和 Java 程式碼放在一起,有助於增強程式的內聚性。而採用獨立的 XML 配置檔案,程式設計師在編寫一個功能時,往往需要在程式檔案和配置檔案中不停切換,這種思維上的不連貫會降低開發效率。

2、個人認為如果開發的程式比較複雜的話還是多用xml配置,這樣以後方便維護,反之則使用註解,

 

7.參考文獻

 https://blog.csdn.net/loongshawn/article/details/72303040

 

https://my.oschina.net/GinkGo/blog/1577574

 

https://blog.csdn.net/yujin753/article/details/46688105

 

8.更多討論
1.spring aop 攔截controller怎麼傳遞request
在到達controller之前攔截,有兩種方法,一種AOP和一種Filter。都可以在到達Controller之前攔截。

2.Spring AOP 實現方式
Spring有兩種實現AOP的方式:Java動態代理 和 Cglib. 預設使用動態代理,當目標物件沒有實現介面時,
就會使用後者. 我們這裡使用Java的動態代理實現一個簡單的AOP, 可以更深刻的理解Spring AOP

3.怎麼開啟JDK代理或CGLib代理?
在Spring配置檔案的切面驅動中加入proxy-target-class="true"欄位,"true"表示開啟CGLib代理,
"false"表示開啟JDK代理,若不加入這一欄位,則預設啟用JDK代理

 

今天的分享就到這裡啦,歡迎大家點贊、轉發、留言、拍磚~

技能樹.IT修真院   

  “我們相信人人都可以成為一個工程師,現在開始,找個師兄,帶你入門,掌控自己學習的節奏,學習的路上不再迷茫”。

   這裡是技能樹.IT修真院,成千上萬的師兄在這裡找到了自己的學習路線,學習透明化,成長可見化,師兄1對1免費指導。

快來與我一起學習吧~http://www.jnshu.com/login/1/12744596

騰訊視訊:https://v.qq.com/x/page/c0698t2dclq.html