1. 程式人生 > 實用技巧 >【作業系統】編制實現程序的管道通訊的程式

【作業系統】編制實現程序的管道通訊的程式

AOP主要實現的目的是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。
AOP是軟體開發思想階段性的產物,我們比較熟悉面向過程OPP和麵向物件OOP,AOP是OOP的延續,但不是OOP的替代,而是作為OOP的有益補充。

參考《Spring 實戰 (第4版)》和《精通Spring4.x 企業應用開發實戰》兩本書的AOP章節和其他資料將其知識點整理起來。

部分程式碼例項摘自《精通Spring4.x 企業應用開發實戰》,文末我會給出兩本書的PDF下載地址!

AOP概述
AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計。那麼什麼又是面向切面呢?

我知道面向物件的特性:封裝、繼承、多型。通過抽象出程式碼中共有特性來優化程式設計,但是這種方式又往往不能完全適用任何場景,無法避免的造成程式碼之間的耦合。

如下面的程式碼,運用的OOP思想將共有操作抽象了出來,抽象出了兩個處理類:效能監視 PerformanceMonitor 和事務管理 TransactionManager

package com.smart.concept;

public class ForumService {
private TransactionManager transManager;
private PerformanceMonitor pmonitor;
private TopicDao topicDao;
private ForumDao forumDao;

public void removeTopic(int topicId) {
    pmonitor.start();
    transManager.beginTransaction();
    topicDao.removeTopic(topicId); // ①
    transManager.commit();
    pmonitor.end();
}

public void createForum(Forum forum) {
    pmonitor.start();
    transManager.beginTransaction();
    forumDao.create(forum); // ②
    transManager.commit();
    pmonitor.end();
}

}
①、②處是兩個方法 removeTopic 和 createForum 獨有的業務邏輯,但它們淹沒在了重複化非業務性程式碼之中。這種抽象為縱向抽取。

將removeTopic和createForum進行橫切重新審視:

我們無法通過縱向抽取的方式來消除程式碼的重複性。然而切面能幫助我們模組化橫切關注點,橫切關注點可以被描述為影響應用多處的功能。例如,效能監視和事務管理分別就是一個橫切關注點。

AOP提供了取代繼承和委託的另一種可選方案,那就是橫向抽取,可以在很多場景下更清晰簡潔。在使用面向切面程式設計時,我們仍然在一個地方定義通用功能,但是可以通過宣告的方式定義這個功能要以何種方式在何處應用,而無需修改受影響的類。橫切關注點可以被模組化為特殊的類,這些類被稱為切面(aspect)。這樣做有兩個好處:首先,現在每個關注點都集中於一個地方,而不是分散到多處程式碼中;其次,服務模組更簡潔,因為它們只包含主要關注點(或核心功能)的程式碼,而次要關注點的程式碼被轉移到切面中了。

AOP術語
AOP不容易理解的一方面原因就是概念太多,並且由英語直譯過來的名稱也不統一,這裡我選擇使用較多的譯名並給出英文供大家參考。

連線點(Joinpoint)
程式執行的某個時間點,如類初始化前/後,某個方法執行前/後,丟擲異常前/後。

Spring僅支援方法的連線點,即僅能在方法呼叫前、方法呼叫後、方法丟擲異常時這些程式執行點織入增強。
切點(Poincut)
一個程式類可以有多個連線點,但是如果某一部分連線點需要用什麼來定位呢?那就是切點,這麼說可能有點抽象。藉助資料庫查詢的概念來理解切點和連線點的關係再適合不過了:連線點相當於資料庫中的記錄,而切點相當於查詢條件。切點和連線點不是一對一的關係,一個切點可以匹配多個連線點。

在 Spring 中, 所有的方法都可以認為是 joinpoint, 但是我們並不希望在所有的方法上都新增 Advice, 而 Pointcut 的作用就是提供一組規則(使用 AspectJ pointcut expression language 來描述) 來匹配joinpoint, 給滿足規則的 joinpoint 新增 Advice.
增強/通知(Advice)
將一段執行邏輯新增到切點,並結合切點資訊定位到具體的連線點,通過攔截來執行邏輯。

Spring切面可以應用5種類型的通知:

前置通知(Before):在目標方法被呼叫之前呼叫通知功能;
後置通知(After):在目標方法完成之後呼叫通知,此時不會關心方法的輸出是什麼;
返回通知(After-returning):在目標方法成功執行之後呼叫通知;
異常通知(After-throwing):在目標方法丟擲異常後呼叫通知;
環繞通知(Around):通知包裹了被通知的方法,在被通知的方法呼叫之前和呼叫之後執行自定義的行為。
目標物件(Target)
增強邏輯的織入目標物件。目標物件也被稱為 advised object

因為 Spring AOP 使用執行時代理的方式來實現 aspect, 因此 adviced object 總是一個代理物件(proxied object)
注意, adviced object 指的不是原來的類, 而是織入 advice 後所產生的代理類.
織入(Weaving)
將增強新增到目標類的具體連線點上的過程。在目標物件的生命週期裡有多個點可以進行織入:

編譯期:切面在目標類編譯時被織入。這種方式需要特殊的編譯器。
類載入期:切面在目標類載入到JVM時被織入。這種方式需要特殊的類載入器(ClassLoader),它可以在目標類被引入應用之前增強該目標類的位元組碼。
執行期:切面在應用執行的某個時刻被織入。一般情況下,在織入切面時,AOP容器會為目標物件動態地建立一個代理物件。Spring AOP就是以這種方式織入切面的。
引介/引入(Introduction)
向現有的類新增新的方法或屬性。

Spring AOP 允許我們為 目標物件 引入新的介面(和對應的實現). 例如我們可以使用 introduction 來為一個 bean 實現 IsModified 介面, 並以此來簡化 caching 的實現。
代理(Proxy)
一個類被AOP織入增強後,就產生了一個結果類,它是融合了原類和增強邏輯的代理類。根據不同的代理方式,代理類既可能是和原類具有相同介面的類,也可能就是原類的子類,所以可以採用與呼叫原類相同的方式呼叫代理類。

在 Spring AOP 中, 一個 AOP 代理是一個 JDK 動態代理物件或 CGLIB 代理物件.
切面(Aspect)
切面由切點和增強/通知組成,它既包括了橫切邏輯的定義、也包括了連線點的定義。

Spring AOP就是負責實施切面的框架,它將切面所定義的橫切邏輯織入切面所指定的連線點中。
Spring對AOP的支援
Spring提供了4種類型的AOP支援:

基於代理的經典Spring AOP;
純POJO切面;
@AspectJ註解驅動的切面;
注入式AspectJ切面(適用於Spring各版本)。
Spring AOP原理
Spring AOP的底層原理就是動態代理。

Java實現動態代理的方式有兩種:

基於JDK的動態代理。
基於CGLib的動態代理。

JDK動態代理是需要實現某個介面了,而我們類未必全部會有介面,於是CGLib代理就有了。

CGLib代理其生成的動態代理物件是目標類的子類。
Spring AOP預設是使用JDK動態代理,如果代理的類沒有介面則會使用CGLib代理。
那麼JDK代理和CGLib代理我們該用哪個呢??在《精通Spring4.x 企業應用開發實戰》給出了建議:

如果是單例的我們最好使用CGLib代理,如果是多例的我們最好使用JDK代理
原因:

JDK在建立代理物件時的效能要高於CGLib代理,而生成代理物件的執行效能卻比CGLib的低。
如果是單例的代理,推薦使用CGLib
看到這裡我們就應該知道什麼是Spring AOP(面向切面程式設計)了:將相同邏輯的重複程式碼橫向抽取出來,使用動態代理技術將這些重複程式碼織入到目標物件方法中,實現和原來一樣的功能。

這樣一來,我們就在寫業務時只關心業務程式碼,而不用關心與業務無關的程式碼

程式碼例項
帶有橫切邏輯的例項
ForumService.java

package com.spring05;

interface ForumService {
    public void removeTopic(int topicId);
    public void removeForum(int forumId);
}
ForumServiceImpl.java
package com.spring05;

public class ForumServiceImpl implements ForumService{
    @Override
    public void removeTopic(int topicId) {

        // 開始對該方法進行效能監視
        PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeTopic");
        System.out.println("模擬刪除Topic記錄:"+topicId);
        try {
            Thread.currentThread().sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 結束對該方法的效能監視
        PerformanceMonitor.end();
    }

    @Override
    public void removeForum(int forumId) {

        // 開始對該方法進行效能監視
        PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeForum");
        System.out.println("模擬刪除Forum記錄:"+forumId);
        try {
            Thread.currentThread().sleep(40);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 結束對該方法的效能監視
        PerformanceMonitor.end();
    }
}

java
MethodPerformance.java
package com.spring05;

public class MethodPerformance {
private long begin;
private long end;
private String serviceMethod;

public MethodPerformance(String serviceMethod) {
    this.serviceMethod = serviceMethod;
    this.begin = System.currentTimeMillis(); // 記錄目標類方法開始執行點的系統時間
}

public void printPerformance() {
    this.end = System.currentTimeMillis(); // 獲取目標類方式執行完成後的系統時間,進而計算出目標類方法的執行時間
    long elapse = end - begin;
    System.out.println(serviceMethod + "花費" + elapse + "毫秒");
}

}