Spring配置AOP的方式

面向切面編程(AOP)是一種通過預編譯方式和運行期 動態代理 實現在不修改源代碼的情況下給程序動態添加功能的技術,是對傳統的面向對象編程(OOP)的一個補充。他家複雜的需求分解出不同方法,將散佈在系統中的公共功能集中解決,使每個事物邏輯位於一個位置,使代碼不分散,業務模塊更簡潔,只包含核心業務代碼,便於維護和升級。其實現方法就是動態代理設計模式,通過代理對象來調用原對象的方法,代理對象方法前後都可以插入代碼,這些代碼就是增強處理。一下將首先介紹使用動態代理模式實現AOP,然後介紹Spring實現AOP的兩種方式。
首先先了解關於AOP的一些相關術語,增強(通知)(Advice):切面必須完成的工作;切入點(Pointcut):通過切入點定位到連接點;連接點(Joinpoint):程序執行的某個特定位置(連接點相當於數據庫中的記錄,切入點相當於查詢條件);切面(Aspect):橫切關注點,跨越應用程序多個模塊的功能,被模塊化的特殊對象;代理(Proxy):向目標對象應用增強之後創建的對象;目標對象(Target):被增強的對象;織入(Weaving):將增強的切面應用到目標對象中的過程。
一,利用動態代理實現AOP
有一個接口及其實現類:

package cn.pb.testAOP;

public interface Calculator {
    public int add(int i,int j);
    public int mul(int i,int j);
}
package cn.pb.testAOP;

public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        return i+j;
    }

    @Override
    public int mul(int i, int j) {
        return i*j;
    }
}

現在要實現在調用add方法輸出結果前在控制檯輸出:Begining–>Parameter:[1, 2];Method:add;同時在輸出結果後還要輸出:Endding–>Result:3。同時在調用mul方法時也需要輸出同樣的格式,你可能會想到在所有的方法前後添加同樣的輸出語句,但當方法增多,或者在其他的類中也需要這個結果,逐條的寫將會使代碼重複繁瑣。此時可以使用代理模式爲此類創建一個代理,在代理類中寫這些重複的代碼而不需要改動上面原始的類,通過代理類調用該類的方法就能夠實現在每一個方法被調用時都可以輸出想要的結果。代理類相當於對該類進行了包裝並添加了額外的方法。
創建動態代理CalaultatorProxy:

package cn.pb.testAOP;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class CalculatorProxy {
    //要代理的對象
    private Calculator target;
    public CalculatorProxy(Calculator target) {
        this.target=target;
    }
    public Calculator getProxy(){
        Calculator proxy=null;
        //代理對象由哪一個類加載器負責加載
        ClassLoader loader=target.getClass().getClassLoader();
        //代理對象的類型,即其中有哪些方法
        Class[] interfaces=new Class[]{Calculator.class};
        //當調用代理對象的方法時,該執行的方法
        InvocationHandler h=new InvocationHandler() {
            /**
             * proxy:正在返回的那個代理對象
             * method:正在被調用的方法
             * args:調用方法時傳入的參數
             * */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args){
                String methodName=method.getName();
                Object result=null;
                try {
                    System.out.println("Begining-->Parameter:"+Arrays.asList(args)+";Method:"+methodName);//寫與此位置的是一個前置增強
                    result = method.invoke(target, args);
                    System.out.println("Endding-->Result:"+result);//寫與此位置的是一個後置增強
                } catch (Exception e) {
                    //寫與此位置的是一個異常增強
                    e.printStackTrace();
                } 
                //寫與此位置的是一個返回增強
                return result;
            }
        };
        proxy=(Calculator) Proxy.newProxyInstance(loader, interfaces, h);
        return proxy;
    }
}

增強類型時根據增強位置決定的,已在代碼中指出,此外還有環繞增強是指所有的增強類型同時存在的情況,此種情況較爲少見。
編寫測試類:

package cn.pb.testAOP;

public class Test {
    public static void main(String[] args) {
        Calculator cal=new CalculatorImpl();
        Calculator proxy=new CalculatorProxy(cal).getProxy();
        int result=proxy.add(1, 2);
        System.out.println(result);
        int result1=proxy.mul(2, 3);
        System.out.println(result1);
    }
}

輸出結果:

Begining-->Parameter:[1, 2];Method:add
Endding-->Result:3
3
Begining-->Parameter:[2, 3];Method:mul
Endding-->Result:6
6

說明在調用兩個方法前後都執行了兩個輸出語句,在這裏只是簡單的用兩個輸出語句演示動態代理的功能,其實他代表着一個增強的功能,即可以在執行主要方法的前後添加額外的功能。

第一種方法揭示了利用Java提供的接口如何實現動態代理實現AOP,Spring對他進行了封裝,提供了自己的方法:基於配置文件的方式配置AOP和基於註解的方式配置AOP。
二,基於註解驅動的方式配置AOP
一般步驟:
1)導入jar包,配置文件中加入aop的命名空間
2)在配置文件中加入如下配置:

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

3)把橫切關注點代碼抽象到切面的類中
切面首先是一個IOC中的bean,即加入@Componenet註解
切面還需要加入@Aspect註解
4)在類中申明各種增強(通知)
聲明一個方法
在方法前加入@Before註解(前置通知)
@Before(“execution(public int cn.pb.testAOP.Calculator.*(int, int))”) 表達式規則如前
5)同理在方法中加入JoinPoint類型的參數,可以訪問被增強類的細節

如下是一個切面類:

package cn.pb.testAOP;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

//把這個類申明爲一個切面:需要把該類放入到IOC容器中,再聲明爲一個切面
@Component
@Aspect
public class LoggingAspect {
    /**
     * 定義一個方法,用於聲明切入點表達式,一般的,該方法中不需要添加其他的代碼
     * */
    @Pointcut("execution(public int cn.pb.testAOP.Calculator.*(int, int))")
    public void pointcutExpression(){}

    //聲明該方法是一個前置增強(通知)
    @Before("pointcutExpression()")
    public void beforeMethod(JoinPoint jp){
        System.out.println("Begining-->Parameter:"+Arrays.asList(jp.getArgs())+";Method:"+jp.getSignature().getName());
    }
    //聲明該方法是一個後置增強(通知),此處不能獲得返回的結果
    @After("pointcutExpression()")
    public void afterMethod(JoinPoint jp){
        System.out.println("Endding-->Result:");
    }
    //聲明該方法是一個返回增強(通知)
    @AfterReturning(value="pointcutExpression()",returning="result")
    public void returnMethod(JoinPoint jp,Object result){
        System.out.println("Endding-->Method:"+jp.getSignature().getName()+",Result:"+result);
    }
    //聲明該方法是一個異常增強(通知)
    @AfterThrowing(value="pointcutExpression()",throwing="ex")
    public void throwingMethod(JoinPoint jp,Object ex){
        System.out.println("The method <"+jp.getSignature().getName()+"> throw exception:"+ex);
    }
}

需要注意的是此切面類和被增強的類都應該處於Spring容器的管理之下。代碼中重用了切點表達式,因此將表達式提出並放在了@Pointcut註解下,切點表達式匹配規則舉例:

》public * addUser(User) 《
“*”表示匹配所有類型的返回值
匹配所有修飾符爲public的addUser方法,且參數類型爲User
》public void * (User) 《
“*”表示匹配所有方法名
匹配所有修飾符爲public且無返回值的所有方法,且參數類型爲User
》public void addUser(..) 《
“..”表示匹配所有參數個數和類型
》* com.pb.service.* .*(..) 《
匹配com.pb.service包下所有類的所有方法
》* com.pb.service..*(..) 《
匹配com.pb.service包及子包下所有類的所有方法

測試類:

package cn.pb.testAOP;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext atx=new ClassPathXmlApplicationContext("ConfigurationAOP.xml");
        Calculator cal=(Calculator) atx.getBean("calculatorImpl");
        int result=cal.add(1, 2);
        System.out.println(result);
        int result1=cal.mul(2, 3);
        System.out.println(result1);
    }
}

輸出結果:

Begining-->Parameter:[1, 2];Method:add
Endding-->Result:
Endding-->Method:add,Result:3
3
Begining-->Parameter:[2, 3];Method:mul
Endding-->Result:
Endding-->Method:mul,Result:6
6

三,基於配置文件的方式配置AOP
編寫切面類:

package cn.pb.testAOP;

import java.util.Arrays;
import org.aspectj.lang.JoinPoint;

public class LoggingAspect {

    public void beforeMethod(JoinPoint jp){
        System.out.println("Begining-->Parameter:"+Arrays.asList(jp.getArgs())+";Method:"+jp.getSignature().getName());
    }

    public void afterMethod(JoinPoint jp){
        System.out.println("Endding-->Result:");
    }

    public void returnMethod(JoinPoint jp,Object result){
        System.out.println("Endding-->Method:"+jp.getSignature().getName()+",Result:"+result);
    }

    public void throwingMethod(JoinPoint jp,Object ex){
        System.out.println("The method <"+jp.getSignature().getName()+"> throw exception:"+ex);
    }
}

編寫配置文件:

<bean id="calculator" class="cn.pb.testAOP.CalculatorImpl"></bean>
    <!-- 配置切面的bean -->
    <bean id="aopTest" class="cn.pb.testAOP.LoggingAspect"></bean>                                  
    <aop:config>
        <!-- 定義切入點 -->
        <aop:pointcut id="pointcut" expression="execution(public * cn.pb.testAOP.Calculator.*(..))"/>
        <!-- 創建切面 -->
        <aop:aspect ref="aopTest">                  
            <!-- 前置增強 -->                           
            <aop:before method="beforeMethod" pointcut-ref="pointcut"/>     
            <!-- 後置增強 -->       
            <aop:after method="afterMethod" pointcut-ref="pointcut"/>   
             <!-- 返回增強 -->
            <aop:after-returning method="returnMethod" pointcut-ref="pointcut" returning="result"/>
            <!-- 異常增強 -->
            <aop:after-throwing method="throwingMethod" pointcut-ref="pointcut" throwing="ex"/>
        </aop:aspect>
    </aop:config>

此時的返回值結果是與上述方法是一致的。若存在多個切面,可以指定切面的優先級,若使用註釋方法,可在切面類前添加註釋@Order(i),i的值越小表示優先級越高;若使用xml文件的方式,可在aspect節點中添加屬性order=”i”定義優先級。

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