天天看點

AOP(面對切面程式設計)

引例

若要求對方法進行日志記錄,而我們采取每個方法單獨寫日志代碼,将會導緻

原有業務急劇膨脹,每個方法處理必須兼顧多個關注點;并且導緻代碼分散,若

日志條件發生改變則必須修改所有子產品

為了解決這個問題,使用AOP程式設計(面向切面程式設計)

AOP術語:

切面(Aspect):  橫切關注點(跨越應用程式多個子產品的功能)被子產品化的特殊對象
通知(Advice):  切面必須要完成的工作
目标(Target): 被通知的對象
代理(Proxy): 向目标對象應用通知之後建立的對象
連接配接點(Joinpoint):程式執行的某個特定位置:如類某個方法調用前、調用後、
方法抛出異常後等。連接配接點由兩個資訊确定:方法表示的程式執行點;相對點表示
的方位。例如 ArithmethicCalculator#add() 方法執行前的連接配接點,執行點為
ArithmethicCalculator#add(); 方位為該方法執行前的位置
切點(pointcut):每個類都擁有多個連接配接點:例如 ArithmethicCalculator
的所有方法實際上都是連接配接點,即連接配接點是程式類中客觀存在的事務。AOP 通過切
點定位到特定的連接配接點。類比:連接配接點相當于資料庫中的記錄,切點相當于查詢條
件。切點和連接配接點不是一對一的關系,一個切點比對多個連接配接點,切點通過
org.springframework.aop.Pointcut 接口進行描述,它使用類和方法作為連接配接點的查詢條件。
           

實作方法

一.使用動态代理

好處:-每個事物邏輯位于一個位置,代碼不分散,便于維護和更新

-業務子產品更簡潔,隻包含核心業務代碼

方法執行需要進行日志記錄的類:算數類,實作的自定義接口中包含加減乘除四個方法

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

    @Override
    public int sub(int i, int j) {
        return i-j;
    }

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

    @Override
    public int div(int i, int j) {
        return i/j;
    }
}
           

動态代理類:

public class ArithmeticCalculatorLoggingProxy {
    private ArithmeticCalculator target;

    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
        this.target = target;
    }

    public ArithmeticCalculator getLoggingProxy() {
        ArithmeticCalculator proxy=null;
        //代理對象由哪一個類加載器負責加載
        ClassLoader loader=target.getClass().getClassLoader();
        //代理對象的類型,即其中有哪些方法
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};
        //當調用代理對象其中的方法時,該執行的代碼
        InvocationHandler h=new InvocationHandler() {
            /**
             * proxy:正在傳回的那個代理對象,一般情況下,在invoke方法中都不使用該對象
             使用該對象的方法會再次調用invoke,導緻死循環。
             * method:正在被調用的方法
             * args:調用方法時,傳入的參數
            */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName=method.getName();
                //記錄日志
                //執行方法
                try {
                    //前置通知
                    System.out.println("The method"+methodName+" begins with "+ Arrays.asList(args));
                    Object result = method.invoke(target, args);
                    //傳回通知,此時可以通路傳回值
                    System.out.println("The method"+methodName+" ends with "+ result);
                    return result;
                }catch (Exception e){
                    e.printStackTrace();
                    //異常通知,可以通路到方法出現的異常
                }
                System.out.println("The method"+methodName+" ends with ");
                //後置通知,因為方法可能會出現異常,是以通路不到方法的傳回值
                //記錄日志
                return null;
            }
        };
        proxy= (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
        return proxy;
    }
}
           

spring配置檔案

<!--配置自動掃描的包-->
    <context:component-scan base-package="com.itlc.spring.aop.impl"></context:component-scan>
    <!--使Aspject注解起作用:自動為比對的類生成代理對象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
           

一.spring AOP

步驟:

1.加入jar包,在idea中還需要導入aopalliance-1.0.jar和aspectjweaver.jar

這兩個jar包下載下傳,密碼:iqc5

2.在配置檔案中加入aop的命名空間

3.基于注解的方式

(1).使Aspject注解起作用:自動為比對的類生成代理對象

(2).把橫切關注點的代碼抽象到切面的類中

1).切面首先是一個IOC中的bean,即加入@Component注解
2).切面還需要加入@Aspect注解
           

(3)在類中加入各種通知

1).申請一個方法
2).在方法前加入各種通知注解
使用方法為書寫完整方法,包括路徑與參數類型,用*表示上一個路徑中的所有檔案,也可用*代替
表示任意申請符,方法中參數的申請符,可以用..來代替所有
@Before: 前置通知, 在方法執行之前執行
@After: 後置通知, 在方法執行之後執行
@AfterRunning: 傳回通知, 在方法傳回結果之後執行
@AfterThrowing: 異常通知, 在方法抛出異常之後
@Around: 環繞通知, 圍繞着方法執行
通知内部綁定對應方法
           
例如:@Before("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(int ,int ))")
    @Before("execution(public int com.itlc.spring.aop.impl.*.*(int ,int ))")
    @Before("execution(* int com.itlc.spring.aop.impl.*.*(..))")
           

(4)可以在通知方法中聲明一個類型為JoinPoint的參數,然後就能通路連結細節,如方法名稱和參數值

(5)當有多個切面類綁定一個方法的時候,課用@Order(n),n為阿拉伯數字,表示切面執行的優先級,值越小優先級越高

(6) 在通知标簽裡中存在大量重複資訊

1)此時定義一個方法,用于作為切入點表達式,一般地,該方法再不需要添加其他的代碼
    2)使用@Pointcut來聲明切入點表達式
    3)後面的其他通知直接使用方法名來引用目前的切入點表達式
    例:@Pointcut("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(.. ))")
    public void declareJountPointExpression(){}
    則通知的注解可改為:@Before("declareJountPointExpression()")
           

方法執行需要進行日志記錄的類:算數類,實作的自定義接口中包含加減乘除四個方法

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

    @Override
    public int sub(int i, int j) {
        return i-j;
    }

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

    @Override
    public int div(int i, int j) {
        return i/j;
    }
}
           

切面類:

//把這個類聲明為一個切面:需要把該類放入IOC容器中,再聲明為一個切面
@Aspect
@Component
public class LoggingAspect {

    /*
    * 定義一個方法,用于作為切入點表達式,一般地,該方法再不需要添加其他的代碼
    * 使用@Pointcut來聲明切入點表達式
    * 後面的其他通知直接使用方法名來引用目前的切入點表達式
    * */
    @Pointcut("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(.. ))")
    public void declareJountPointExpression(){}

    /*聲明該方法是一個前置通知:在目标方法開始之前執行
    使用方法為書寫完整方法,包括路徑與參數類型,用*表示上一個路徑中的所有檔案,也可用*代替
    表示任意申請符*/
    @Before("declareJountPointExpression()")
    public void beforeMethod(JoinPoint joinPoint){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" begins with" + args);
    }
    //聲明該方法是一個後置通知:無論是否發生異常,在目标方法開始之後執行
    //注意:不能在後置通知中通路目标方法執行的結果,因為方法可能出現異常
    @After("declareJountPointExpression()")
    public void afterMethod(JoinPoint joinPoint){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" ends");
    }
    /*
    * 傳回通知
    * 在方法正常結束受執行的代碼
    * 傳回通知是可以通路到方法的傳回值的
    * */
    @AfterReturning(value="declareJountPointExpression()",returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" ends with" + result);
    }
    /*
    * 在目标方法方法出現異常時會執行的代碼
    * 可以通路到異常對象;
    * 且可以指定在出現特定異常時再執行通知代碼:将形參清單中的Exception換成特定的異常類名即可
    * */
    @AfterThrowing(value="declareJountPointExpression()",throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" occurs exception" + ex);
    }

    /*
    * 環繞通知需要攜帶ProceedingJoinPint
    * 環繞通知類似于動态代理的全過程:ProceedingJoinPint 類型的參數可以決定是否執行目标方法
    * 且環繞通知必須有傳回值,傳回值即為目标方法的傳回值
    * 環繞通知功能最強,但并不是最常用的
    * */
    @Around("execution(public int com.itlc.spring.aop.impl.ArithmeticCalculator.*(..))")
    public Object aroundMethod(ProceedingJoinPoint pjd){
       Object result=null;
       String methodName=pjd.getSignature().getName();
       try {
           //前置通知
           System.out.println("The method "+methodName+" begins with" + Arrays.asList(pjd.getArgs()));
           result=pjd.proceed();
           //傳回通知
           System.out.println("The method "+methodName+" ends with "+result);
       } catch (Throwable throwable) {
           //異常通知
           System.out.println("The method "+methodName+" occur exception:"+throwable);
       }
       //後置通知
        System.out.println("The method "+methodName+" ends");
       return result;
    }

}
           

spring配置:

<!--配置自動掃描的包-->
    <context:component-scan base-package="com.itlc.spring.aop.impl"></context:component-scan>
    <!--使Aspject注解起作用:自動為比對的類生成代理對象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
           

Main:

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator arithmeticCalculator=ctx.getBean(ArithmeticCalculator.class);
        int result=arithmeticCalculator.add(,);
        System.out.println(result);
        result=arithmeticCalculator.div(,);
        System.out.println(result);
    }
}
           

三.通過配置檔案來實作AOP

原理類似切面類來實作,不過不用注解标注

方法執行需要進行日志記錄的類:算數類,實作的自定義接口中包含加減乘除四個方法

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

    @Override
    public int sub(int i, int j) {
        return i-j;
    }

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

    @Override
    public int div(int i, int j) {
        return i/j;
    }
}
           

切面類1:

public class LoggingAspect {

    public void beforeMethod(JoinPoint joinPoint){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" begins with" + args);
    }
    public void afterMethod(JoinPoint joinPoint){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" ends");
    }
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" ends with" + result);
    }

    public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "+methodName+" occurs exception" + ex);
    }

 public Object aroundMethod(ProceedingJoinPoint pjd){
       Object result=null;
       String methodName=pjd.getSignature().getName();
       try {
           //前置通知
           System.out.println("The method "+methodName+" begins with" + Arrays.asList(pjd.getArgs()));
           result=pjd.proceed();
           //傳回通知
           System.out.println("The method "+methodName+" ends with "+result);
       } catch (Throwable throwable) {
           //異常通知
           System.out.println("The method "+methodName+" occur exception:"+throwable);
       }
       //後置通知
        System.out.println("The method "+methodName+" ends");
       return result;
    }

}
           

切面類2:

public class VlidationAspect {
    public void beforeMethod(JoinPoint joinPoint){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("VlidationAspect-->  The method "+methodName+" begins with" + args);
    }
    public void afterMethod(JoinPoint joinPoint){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("VlidationAspect-->  The method "+methodName+" ends");
    }
    public void afterReturningMethod(JoinPoint joinPoint,Object result){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("VlidationAspect-->  The method "+methodName+" ends with" + result);
    }

    public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
        //傳入的參數,包括調用的方法名都在joinPoint這個對象中
        String methodName = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("VlidationAspect-->  The method "+methodName+" occurs exception" + ex);
    }

}
           

spring配置檔案

<!--配置bean-->
    <bean id="arithmeticCalculator"
    class="com.itlc.spring.aop.impl.ArithmeticCalculatorImpl"></bean>

    <!--配置切面的bean-->
    <bean id="LoggingAspect" class="com.itlc.spring.aop.impl.LoggingAspect"></bean>
    <bean id="vlidationAspect" class="com.itlc.spring.aop.aopByBean.VlidationAspect"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切點表達式-->
        <aop:pointcut id="pointcut" expression="execution(* com.itlc.spring.aop.impl.ArithmeticCalculator.*(..))"/>
        <!--配置切面及通知-->
        <aop:aspect ref="LoggingAspect" order="2">
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
            <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
            <aop:after-returning method="afterReturningMethod" pointcut-ref="pointcut" returning="result"/>
            <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
         <!--<aop:around method="aroundMethod" pointcut-ref="pointcut"></aop:around>-->
        </aop:aspect>
        <aop:aspect ref="vlidationAspect" order="1">
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
        </aop:aspect>
    </aop:config>
           

Main:

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx=new ClassPathXmlApplicationContext("aopByBean.xml");
        ArithmeticCalculator arithmeticCalculator= (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
        int result=arithmeticCalculator.add(,);
        System.out.println(result);
        result=arithmeticCalculator.div(,);
        System.out.println(result);
    }
}