Java 面向切面編程(Aspect Oriented Programming,AOP)

本文內容

  • 實例
    • 引入
    • 原始方法
    • 裝飾者模式
  • JDK 動態代理和 cglib 代理
  • 直接使用 AOP 框架——AspectWerkz

最近跳槽了,新公司使用了 AOP 相關的技術,因而查點資料,複習一下。以前,多少知道點,但沒怎麼在實際項目中使用過~java

下載 demo

實例


引入

package com.cap.aop;
 
public interface ICalculator {
    public double add(double num1, double num2) throws Exception;
 
    public double sub(double num1, double num2) throws Exception;
 
    public double div(double num1, double num2) throws Exception;
 
    public double mul(double num1, double num2) throws Exception;
}
package com.cap.aop;
 
/**
 * 加減乘除
 * */
public class Calculator implements ICalculator {
    @Override
    public double add(double num1, double num2) {
        double result = num1 + num2;
        return result;
    }
 
    @Override
    public double sub(double num1, double num2) {
        double result = num1 - num2;
        return result;
    }
 
    @Override
    public double div(double num1, double num2) {
        double result = num1 / num2;
        return result;
    }
 
    @Override
    public double mul(double num1, double num2) {
        double result = num1 * num2;
        return result;
    }
}

定義 ICalculator 接口和 Calculator 類,而且 Calculator 也繼承 ICalculator正則表達式

若要爲這個類添加「日誌」功能該如何作?日誌在實際項目中頗有必要,好比數據庫日誌,業務日誌等等,經過日誌就能知道數據庫和業務存在的問題,這要比調試程序容易多了,此外還有性能統計,安全控制,事務處理,異常處理等等都是相似問題。數據庫

咱們最可能想到的是,在類的每一個方法內都寫日誌相關的代碼,或是在該類的基類中寫,在其子類中繼承。編程

原始方法

package com.cap.aop;
 
/**
 * 加減乘除,原始方式
 * */
public class CalculatorOriginalWay implements ICalculator {
    @Override
    public double add(double num1, double num2) {
        System.out.println("the method [add()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = num1 + num2;
 
        System.out.println("the method [add()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double sub(double num1, double num2) {
        System.out.println("the method [sub()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = num1 - num2;
 
        System.out.println("the method [sub()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double div(double num1, double num2) {
        System.out.println("the method [div()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = num1 / num2;
 
        System.out.println("the method [div()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double mul(double num1, double num2) {
        System.out.println("the method [mul()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = num1 * num2;
 
        System.out.println("the method [mul()]" + "end with result (" + result
                + ")");
 
        return result;
    }
}

這樣作的缺點顯而易見,重複代碼太多,耦合也很差。要是該類只針對正數運算呢,還要對變量作檢查,代碼以下所示:設計模式

package com.cap.aop;
 
/**
 * 加減乘除,只對正數
 * */
public class CalculatorForPositiveNumber implements ICalculator {
    @Override
    public double add(double num1, double num2) throws Exception {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
 
        System.out.println("the method [add()]" + "begin with args (" + num1
                + "," + num2 + ")");
        double result = num1 + num2;
        System.out.println("the method [add()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double sub(double num1, double num2) throws Exception {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
 
        System.out.println("the method [sub()]" + "begin with args (" + num1
                + "," + num2 + ")");
        double result = num1 - num2;
        System.out.println("the method [sub()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double div(double num1, double num2) throws Exception {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
 
        System.out.println("the method [div()]" + "begin with args (" + num1
                + "," + num2 + ")");
        double result = num1 / num2;
        System.out.println("the method [div()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double mul(double num1, double num2) throws Exception {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
 
        System.out.println("the method [mul()]" + "begin with args (" + num1
                + "," + num2 + ")");
        double result = num1 * num2;
        System.out.println("the method [mul()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    private void argsValidatior(double arg) throws Exception {
        if (arg < 0)
            throw new Exception("參數不能爲負數");
    }
}

這也僅僅是一個類而已,實際項目中那麼多類,要是都這麼幹,顯然不現實,那麼如何改進?——設計模式「裝飾者模式」,在沒必要改變原類文件和繼承的狀況下,動態地擴展一個對象的功能。安全

裝飾者方法

package com.cap.aop;
 
/**
 * 加減乘除,裝飾者模式
 */
public class CalculatorDecorator implements ICalculator {
    private ICalculator cal;
 
    public CalculatorDecorator(ICalculator iCalculator) {
        cal = iCalculator;
    }
 
    @Override
    public double add(double num1, double num2) throws Exception {
        System.out.println("the method [add()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = cal.add(num1, num2);
 
        System.out.println("the method [add()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double sub(double num1, double num2) throws Exception {
        System.out.println("the method [sub()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = cal.sub(num1, num2);
 
        System.out.println("the method [sub()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double div(double num1, double num2) throws Exception {
        System.out.println("the method [div()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = cal.div(num1, num2);
 
        System.out.println("the method [div()]" + "end with result (" + result
                + ")");
 
        return result;
    }
 
    @Override
    public double mul(double num1, double num2) throws Exception {
        System.out.println("the method [mul()]" + "begin with args (" + num1
                + "," + num2 + ")");
 
        double result = cal.mul(num1, num2);
 
        System.out.println("the method [mul()]" + "end with result (" + result
                + ")");
 
        return result;
    }
}

這個方法比上面的強點,用原來的類 Calculator 建立一個新類 CalculatorDecorator,也繼承 ICalculator 接口,這樣,在對原來的類不作任何修改的狀況下,在新類中添加日誌功能。但該方法也有弱點,須要爲每一個類都應用「裝飾者模式」,工做量也不小。如何解決?——代理。服務器

 

JDK 動態代理和 cglib 代理


JDK 從 1.3 版本開始,引入了動態代理。JDK 動態代理很是簡單,但動態代理的對象必須是一個或多個接口。網絡

若想代理類,就須要使用 cglib 包。cglib 是一個強大的、高性能的代碼生成包,cglib 包的底層是經過使用一個小而快的字節碼處理框架 ASM,來轉換字節碼並生成新的類,cglib 被許多 AOP 框架使用,例如 Spring 的 AOP;Hibernate 使用 cglib 來代理單端 single-ended(多對一和一對一)關聯;EasyMock 經過使用模仿(moke)對象來測試 java 包……它們都經過 cglib 來爲那些沒有接口的類建立模仿(moke)對象。app

JDK 動態代理

package com.cap.aop;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
 
/**
 * 加減乘除,JDK 代理<br/>
 * 只能代理接口,不能代理類
 * 
 * */
public class CalculatorInvocationHandler implements InvocationHandler {
    // 動態代理只有在運行時才知道代理誰,因此定義爲Object類型
    private Object target = null;
 
    /**
     * 經過構造函數傳入原對象
     * 
     * @param target
     *            要代理的對象
     */
    public CalculatorInvocationHandler(Object target) {
        this.target = target;
    }
 
    /**
     * InvocationHandler 接口的 invoke 方法,調用代理的方法
     * 
     * @param proxy在其上調用方法的代理實例
     * @param method攔截的方法
     * @param args攔截的參數
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("JDK proxy...");
        // 日誌開始
        System.out.println("the method [" + method.getName() + "]"
                + "begin with args (" + Arrays.toString(args) + ")");
 
        Object result = method.invoke(this.target, args);
 
        // 日誌結束
        System.out.println("the method [" + method.getName() + "]"
                + "end with result (" + result + ")");
 
        return result;
    }
 
    /**
     * 獲取代理類
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new CalculatorInvocationHandler(target));
    }
}

cglib 代理

package com.cap.aop;
 
import java.lang.reflect.Method;
import java.util.Arrays;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
/**
 * 加減乘除,cglib 代理<br/>
 * 能代理接口和類,不能代理final類
 */
public class CalculatorInterceptor implements MethodInterceptor {
    private Object target;
 
    public CalculatorInterceptor(Object target) {
        this.target = target;
    }
 
    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
            MethodProxy invocation) throws Throwable {
        System.out.println("cglib proxy...");
        // 日誌開始
        System.out.println("the method [" + method.getName() + "]"
                + "begin with args (" + Arrays.toString(args) + ")");
 
        Object result = invocation.invoke(target, args);
        // 日誌結束
        System.out.println("the method [" + method.getName() + "]"
                + "end with result (" + result + ")");
 
        return result;
    }
 
    public Object proxy() {
        return Enhancer.create(target.getClass(), new CalculatorInterceptor(
                target));
    }
}
測試主程序,代碼以下所示:
package com.cap.aop;
 
public class Client {
    public static void main(String[] args) throws Exception {
        ICalculator calculatorProxy = (ICalculator) new CalculatorInvocationHandler(
                new Calculator()).getProxy();
        calculatorProxy.add(10, 10);
 
        Calculator calculator = (Calculator) new CalculatorInterceptor(
                new Calculator()).proxy();
        calculator.add(20, 20);
    }
}

運行結果:框架

JDK proxy...
the method [add]begin with args ([10.0, 10.0])
the method [add]end with result (20.0)
cglib proxy...
the method [add]begin with args ([20.0, 20.0])
the method [add]end with result (40.0)

 

直接使用 AOP 框架——AspectWerkz


利用 AOP 框架,你只須要利用註釋和 Aspect 就能夠完成上面的全部工做。

AOP,Aspect Oriented Programming,稱爲「面向切面編程」,AOP 是 OOD/OOP 和 GoF 的延續,GoF 孜孜不倦的追求是調用者和被調用者之間的解耦,提升代碼的靈活性和可擴展性,AOP 的目標也是同樣。

AOP 是一種經過預編譯和運行時動態代理實如今不修改源代碼的狀況下給程序動態統一添加功能的技術。利用 AOP 能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發效率。在 Spring 中,經過分離應用的業務邏輯與系統級服務,應用對象只完成業務邏輯,不負責(甚至是意識)其它的系統級關注點,例如日誌、事務、審計等等。

本文最開始的「日誌功能」,就是一個切面。

AOP 主要應用在日誌記錄,性能統計,安全控制,事務處理,異常處理等等,將它們從業務邏輯代碼中分離,將它們獨立到非業務邏輯的方法中,進而在改變這些行爲時不影響業務邏輯的代碼。

AOP 與 OOP/OOD

AOP(面向切面編程)與 OOP(面向對象編程)字面上雖然相似,但倒是面向不一樣領域的兩種設計思想。

OOP 針對業務中的實體及其屬性和行爲進行抽象封裝,以便劃分邏輯單元。而 AOP 則是針對業務中的「切面」進行提取,它面對的是處理過程當中的某個步驟或階段,以得到各部分之間低耦合性的隔離效果。所以,這兩種設計思想有着本質的差別。

簡單來講,對「僱員」這個業務實體進行封裝,是 OOP 的任務,咱們能夠創建一個「Employee」類,並將「僱員」相關的屬性和行爲封裝其中。而用 AOP 對「僱員」進行封裝將無從談起;權限檢查也是如此,它是 AOP 的領域。

換而言之,OOD/OOP 面向名詞領域,AOP 面向動詞領域。

不少人在初次接觸 AOP 的時候可能會說,AOP 能作到的,一個定義良好的 OOP 接口也能,這個觀點是值得商榷。AOP 和定義良好的 OOP 能夠說都是用來解決而且實現需求中的橫切問題。但對於 OOP 中的接口來講,它仍然須要咱們在相應的模塊中去調用該接口中相關的方法,這是 OOP 所沒法迴避的,而且一旦接口不得不進行修改的時候,全部事情會變得一團糟;AOP 則不會,你只須要修改相應的 Aspect,再從新 weave(編織)便可。 AOP 也絕對不會取代 OOP。核心的需求仍然會由 OOP 來加以實現,而 AOP 將會和 OOP 整合起來,揚長避短。

AOP 涉及的概念

下面概念在 AspectWerkz 的註釋中都有所體現。

  • Aspect: Aspect 聲明相似於 Java 中的類聲明,在 Aspect 中會包含着一些 Pointcut 以及相應的 Advice。
  • Joint point:表示在程序中明肯定義的點,典型的包括方法調用,對類成員的訪問以及異常處理程序塊的執行等等,它自身還能夠嵌套其它 joint point。
  • Pointcut:表示一組 joint point,這些 joint point 或是經過邏輯關係組合起來,或是經過通配、正則表達式等方式集中起來,它定義了相應的 Advice 將要發生的地方。
  • Advice:Advice 定義了在 pointcut 裏面定義的程序點具體要作的操做,它經過 before、after 和 around 來區別是在每一個 joint point 以前、以後仍是代替執行的代碼。

Java 的 AOP 框架

  • AspectWerkz 是簡單、動態、輕量級、強大的 AOP 框架,更容易的集成 AOP 的項目中。
  • JBoss AOP 是 JBoss 4.0 帶了一個 AOP 框架,但也可以在你的應用中單獨的運行它。
  • Nanning。Nanning Aspects is a simple yet scaleable aspect-oriented framework for Java. Nanning is also nice "little" town in Guanxi province in southern China. It's about 1-2 million inhabitants which barely qualifies it as a town by chinese standards. It is not to be confused with the much larger Nanking/Nanjing.
  • JAC,Java Aspect Components 是一個應用服務器。它爲 Java 2 平臺、J2EE和基於 Web 的分佈式應用,提供開放式資源的又一個選擇。JAC 包括統一模型語言(UML)IDE,該 IDE 模塊化應用商業邏輯,並自動生成和編譯純商業邏輯 Java 類。這些類,在 JAC 容器內執行,可從一組技術和/或商業的橫切關係 (crosscutting concerns),如數據持久性、認證、配置文件管理、訪問權限檢測、演示和負載平衡中無縫地受益。基於 AOP 的 JAC 將這些關係(concerns)從應用程序的核心商業邏輯中分離出來。
  • DynamicAspects。This project is no longer maintained! DynamicAspects enables you to do aspect-oriented programming in pure Java. Using the "instrumentation" and "agent" features introduced with Sun JDK 1.5, aspects can be installed and deinstalled during runtime!
  • CAESAR。CaesarJ is a new Java based programming language, which facilitates better modularity and development of reusable components. The components are collaborations of classes, but they can modularize crosscutting features or non-functional concerns. Caesar language features help to implement, abstract and integrate such components. Caesar can be used in combination with plain Java. Tool support is available in the form of an Eclipse plugin.
  • PROSE 是一個動態編排(weaving)工具,容許在運行時插入或抽取 aspects。PROSE aspects 是規則的 Java 對象可以被髮送到或從網絡上的計算機接收。簽名可被用於保證它們的完整性。一旦一個 aspect 插入到 JVM 中,任何事件的發生將影響在相應 aspect advice 執行的結果。假如一個 aspect 從 JVM 中撤消,aspect 代碼將被丟棄而且相應的攔截也將不會再發生。
  • FastAOP。FastAOP is an very high performant AOP (Aspect Oriented Programming) framework for java. The framework was initially
    developped to support performance profiling and monitoring for large J2EE applications with nearly no runntime overhad.

.Net 的 AOP 框架

  • Encase 是 C# 編寫開發的爲 .NET 平臺提供的 AOP 框架。Encase 獨特的提供了把方面(aspects)部署到運行時代碼,其它 AOP 框架依賴配置文件的方式。這種部署方面(aspects)的方法幫助缺乏經驗的開發人員提升開發效率。
  • NKalore 是這款編程語言,擴展了 C#,容許在 .NET 平臺使用 AOP。NKalore 的語法簡單、直觀,編譯器是基於 Mono C#編譯器(MCS)。NKalore 目前只能在命令行或 #Develop 內部使用。NKalore 兼容公共語言規範(Common Language Specification,CLS),能夠在任何 .NET 開發環境中使用,包括微軟的 Visual Studio .NET。
  • PostSharp 讀取 .NET 字節模塊,轉換成對象模型。讓插件分析和轉換這個模型並寫回到MSIL。PostSharp 使開發程序分析應用程序容易得像分析代碼規則和設計模式,它使程序開發的思想變革爲面向方面軟件開發(AOSD/AOD)思想。
  • AspectDNG 的目標是爲 .NET 開發人員提供簡單而功能強大的 AOP-GAOP 實現。它效仿 java 下的開源工具 AspectJ 和 Spoon,成熟程度也很接近它們。
  • RAIL,Runtime Assembly Instrumentation Library,開源項目能夠在 C# 程序集加載和運行前進行處理控制調整和從新構建。C#在 CLR 中,咱們已經可以動態加載程序集而且得到程序集中的類和方法,RAIL(Runtime Assembly Instrumentation Library)的出現填補了CLR處理過程當中的一些空白。
  • SetPoint 是一款 .NET 框架下的全功能 AOP 引擎,它着重爲稱爲語義切點(semantic pointcuts)的定義依賴 RDF/OWL 的使用,它的功能爲一個 IL-level,highly dynamic weaver&LENDL,,一個引人注目的定義語言 DotNetAOP 爲 CLR language 提供 AOP 框架基礎屬性。
  • NAop 是一個 .NET 下的 AOP 框架。
  • AspectSharp 是 .NET 下的免費 AOP框架,它以 Dynamic Proxies 和 XML 做爲配置文件。

 

下載 demo

相關文章
相關標籤/搜索