AOP面向切面編程

 AOP(Aspect-Oriented Programming,面向切面的編程),它是能夠經過預編譯方式和運行期動態代理實如今不修改源代碼的狀況下給程序動態統一添加功能的一種技術。它是一種新的方法論,它是對傳統OOP編程的一種補充。java

  OOP是關注將需求功能劃分爲不一樣的而且相對獨立,封裝良好的類,並讓它們有着屬於本身的行爲,依靠繼承和多態等來定義彼此的關係;AOP是但願可以將通用需求功能從不相關的類當中分離出來,可以使得不少類共享一個行爲,一旦發生變化,沒必要修改不少類,而只須要修改這個行爲便可。程序員

  AOP是使用切面(aspect)將橫切關注點模塊化,OOP是使用類將狀態和行爲模塊化。在OOP的世界中,程序都是經過類和接口組織的,使用它們實現程序的核心業務邏輯是十分合適。可是對於實現橫切關注點(跨越應用程序多個模塊的功能需求)則十分吃力,好比日誌記錄,驗證。編程

/*計算器接口*/
public interface Calculator
{
    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;
}
/*計算器接口的實現類*/
public class ArithmeticCalculator implements Calculator
{
    @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;
    }
    /*示意代碼 暫時不考慮除數0的狀況*/
    @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;
    }
}

   大多數應用程序都有一個通用的需求,即在程序運行期間追蹤正在發生的活動。爲了給計算機添加日誌功能,ArithmeticCalculator類改變以下:設計模式

/*計算器接口的實現類,添加記錄日誌功能*/
public class ArithmeticCalculator implements Calculator
{
    @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;
    }
    /*示意代碼 暫時不考慮除數0的狀況*/
    @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;
    }
}

  若ArithmeticCalculator規定只能計算正數時,又須要添加參數驗證方法:框架

/*計算器接口的實現類,添加記錄日誌功能*/
public class ArithmeticCalculator implements Calculator
{
    @Override
    public double add(double num1, double num2) throws Exception
    {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
        
         /*同上*/
    }
    @Override
    public double sub(double num1, double num2) throws Exception
    {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
        
         /*同上*/
    }
    /*示意代碼 暫時不考慮除數0的狀況*/
    @Override
    public double div(double num1, double num2) throws Exception
    {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
        
         /*同上*/
    }
    @Override
    public double mul(double num1, double num2) throws Exception
    {
        this.argsValidatior(num1);
        this.argsValidatior(num2);
        
        /*同上*/
    }
    
    private void argsValidatior(double arg)throws Exception
    {
        if(arg < 0)
            throw new Exception("參數不能爲負數");
    }
}

   上面的程序一個很直觀的特色就是,好多重複的代碼,而且當加入愈來愈多的非業務需求(例如日誌記錄和參數驗證),原有的計算器方法變得膨脹冗長。這裏有一件很是痛苦的事情,沒法使用原有的編程方式將他們模塊化,從核心業務中提取出來。例如日誌記錄和參數驗證,AOP裏將他們稱爲橫切關注點(crosscutting concern),它們屬於系統範圍的需求一般須要跨越多個模塊。
  在使用傳統的面向對象的編程方式沒法理想化的模塊化橫切關注點,程序員不能不作的就是將這些橫切關注點放置在每個模塊裏與核心邏輯交織在一塊兒,這將會致使橫切關注點在每個模塊裏處處存在。使用非模塊化的手段實現橫切關注將會致使,代碼混亂,代碼分散,代碼重複。你想一想看若是日誌記錄須要換一種顯示方式,那你要改多少代碼,一旦漏掉一處(機率很高),將會致使日誌記錄不一致。這樣的代碼很維護。種種緣由代表,模塊只須要關注本身本來的功能需求,須要一種方式來將橫切關注點沖模塊中提取出來。ide

  忍無可忍的大牛們提出了AOP,它是一個概念,一個規範,自己並無設定具體語言的實現,也正是這個特性讓它變的很是流行,如今已經有許多開源的AOP實現框架了。本次不是介紹這些框架的,咱們將不使用這些框架,而是使用底層編碼的方式實現最基本的AOP解決上面例子出現的問題。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP能夠說也是這種目標的一種實現。AOP可使用"代理模式"來實現。模塊化

 代理模式的原理是使用一個代理將對象包裝起來,而後用該代理對象取代原始的對象,任何對原始對象的調用首先要通過代理。代理對象負責決定是否以及什麼時候將方法調用信息轉發到原始對象上。與此同時,圍繞着每一個方法的調用,代理對象也能夠執行一些額外的工做。能夠看出代理模式很是適合實現橫切關注點。函數

  因爲本人只瞭解Java,因此姑且認爲代理模式有兩種實現方式,一種是靜態代理、另外一種是動態代理。他們的區別在於編譯時知不知道代理的對象是誰。在模塊比較多的系統中,靜態代理是不合適也很是低效的,由於靜態代理須要專門爲每個接口設計一個代理類,系統比較大成百上千的接口是很正常的,靜態代理模式太消耗人力了。動態代理是JDK所支持的代理模式,它能夠很是好的實現橫切關注點。this

/*使用動態代理須要實現InvocationHandler接口*/
public class ArithmeticCalculatorInvocationHandler implements InvocationHandler
{
    /*要代理的對象,動態代理只有在運行時才知道代理誰,因此定義爲Object類型,能夠代理任意對象*/
    private Object target = null;
    
    /*經過構造函數傳入原對象*/
    public ArithmeticCalculatorInvocationHandler(Object target)
    {
        this.target = target;
    }
    /*InvocationHandler接口的方法,proxy表示代理,method表示原對象被調用的方法,args表示方法的參數*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
    {
        /*原對象方法調用前處理日誌信息*/
        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(this.target.getClass().getClassLoader(), this.getClass().getInterfaces(), this);
    }
}

場景類調用:編碼

public class Client
{
    public static void main(String[] args) throws Exception
    {
        /*得到代理*/
        Calculator arithmeticCalculatorProxy = (Calculator)new ArithmeticCalculatorInvocationHandler(
                 new ArithmeticCalculator()).getProxy();
        /*調用add方法*/
        arithmeticCalculatorProxy.add(10, 10);
    }
}

控制檯的輸出:

能夠看到使用動態代理實現了橫切關注點。

若須要添加參數驗證功能,只須要再建立一個參數驗證代理便可:

public class ArithmeticCalculatorArgsInvocationHandler implements
        InvocationHandler
{
    /*要代理的對象,動態代理只有在運行時才知道代理誰,因此定義爲Object類型,能夠代理任意對象*/
    private Object target = null;
    
    /*經過構造函數傳入原對象*/
    public ArithmeticCalculatorArgsInvocationHandler(Object target)
    {
        this.target = target;
    }
    /*InvocationHandler接口的方法,proxy表示代理,method表示原對象被調用的方法,args表示方法的參數*/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
    {
        System.out.println("begin valid method ["+method.getName()+"] with args "+Arrays.toString(args));
        
        for(Object arg : args)
        {
            this.argValidtor((Double)arg);
        }
        
        Object result = method.invoke(this.target, args);
        
        return result;
    }
    
    /*獲取代理類*/
    public Object getProxy()
    {
        return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
    }
    
    private void argValidtor(double arg) throws Exception
    {
        if(arg < 0)
            throw new Exception("參數不能爲負數!");
    }
}

場景類調用:

public class Client
{
    public static void main(String[] args) throws Exception
    {
        /*得到代理*/
        Calculator arithmeticCalculatorProxy = (Calculator)new ArithmeticCalculatorInvocationHandler(
                 new ArithmeticCalculator()).getProxy();
        
        Calculator argValidatorProxy = (Calculator)new ArithmeticCalculatorArgsInvocationHandler(arithmeticCalculatorProxy).getProxy();
        /*調用add方法*/
        argValidatorProxy.add(10, 10);
    }
}

控制檯輸出:

輸入一個負數數據:

public class Client
{
    public static void main(String[] args) throws Exception
    {
        /*得到代理*/
        Calculator arithmeticCalculatorProxy = (Calculator)new ArithmeticCalculatorInvocationHandler(
                 new ArithmeticCalculator()).getProxy();
        
        Calculator argValidatorProxy = (Calculator)new ArithmeticCalculatorArgsInvocationHandler(arithmeticCalculatorProxy).getProxy();
        /*調用add方法*/
        argValidatorProxy.add(-10, 10);
    }
}

控制檯輸出:

不知道你有沒有使用過Struts2,這個結構和Struts2的攔截器很是類似,一個個Action對象比如咱們的原對象業務核心,一個個攔截器比如是這裏的代理,通用的功能實現成攔截器,讓Action能夠共用,Struts2的攔截器也是AOP的優秀實現。

相關文章
相關標籤/搜索