1. 程式人生 > >java學習筆記——spring之aop、切面類中五種通知的使用、存在多個切面類時切面通知的執行順序、註釋的方式定義切面類與通知、xml配置的方式定義切面類與通知

java學習筆記——spring之aop、切面類中五種通知的使用、存在多個切面類時切面通知的執行順序、註釋的方式定義切面類與通知、xml配置的方式定義切面類與通知

3、AOP

AOP:(Aspect Oriented Programming)面向切面程式設計;

OOP:(Object Oriented Programming )面向物件程式設計;

面向切面程式設計:基於OOP基礎之上新的程式設計思想;

指在程式執行期間,將某段程式碼動態的切入指定方法指定位置進行執行的這種程式設計方式,面向切面程式設計;

場景:計算器執行計算方法的時候進行日誌記錄;

加日誌記錄:

1)、直接編寫在方法內部;不推薦,修改維護麻煩;

          日誌記錄:系統的輔助功能;

          業務邏輯:(核心功能)

          耦合;

2)、我們希望的是;

          業務邏輯:(核心功能);日誌模組;在核心功能執行期間,自己動態的加上;

          執行的時候,日誌功能可以加上;

  可以使用動態代理來將日誌程式碼動態的在目標方法執行前後先進行執行;

package com.atguigu.proxy;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.Arrays;

import com.atguigu.inter.Calculator;

import com.atguigu.utils.LogUtils;

/**

 * Calculator.java生成代理物件的類

 * Object newProxyInstance

 * (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

 *

 * @author lfy

 *

 */

public class CalculatorProxy {

    /**

     * 為傳入的引數物件建立一個動態代理物件

     * @param calculator

     * @return

     *

     * Calculator calculator:被代理物件;(寶寶)

     * 返回的:宋喆

     */

    public static Calculator getProxy(final Calculator calculator) {

        // TODO Auto-generated method stub

        //方法執行器。幫我們目標物件執行目標方法

        InvocationHandler h = new InvocationHandler() {

            /**

             * Object proxy:代理物件;給jdk使用,任何時候都不要動這個物件

             * Method method:當前將要執行的目標物件的方法

             * Object[] args:這個方法呼叫時外界傳入的引數值

             */

            @Override

            public Object invoke(Object proxy, Method method, Object[] args)

                    throws Throwable {

                //System.out.println("這是動態代理將要幫你執行方法...");

                Object result = null;

                try {

                    LogUtils.logStart(method, args);

                    // 利用反射執行目標方法

                    //目標方法執行後的返回值

                    result = method.invoke(calculator, args);

                    LogUtils.logReturn(method, result);

                } catch (Exception e) {

                    LogUtils.logException(method,e);

                }finally{

                    LogUtils.logEnd(method);

                }

                //返回值必須返回出去外界才能拿到真正執行後的返回值

                return result;

            }

        };

        Class<?>[] interfaces = calculator.getClass().getInterfaces();

        ClassLoader loader = calculator.getClass().getClassLoader();

        //Proxy為目標物件建立代理物件;

        Object proxy = Proxy.newProxyInstance(loader, interfaces, h);

        return (Calculator) proxy;

    }

}

動態代理:

     1)、寫起來難;

     2)、jdk預設的動態代理,如果目標物件沒有實現任何介面,是無法為他建立代理物件的;

Spring動態代理難;Spring實現了AOP功能;底層就是動態代理;

     1)、可以利用Spring一句程式碼都不寫的去建立動態代理;

               實現簡單,而且沒有強制要求目標物件必須實現介面;

將某段程式碼(日誌)動態的切入(不把日誌程式碼寫死在業務邏輯方法中)指定方法(加減乘除)指定位置(方法的開始、結束、異常。。。)進行執行的這種程式設計方式(Spring簡化了面向切面程式設計)

AOP專業術語;

AOP使用步驟:

1)、導包;

commons-logging-1.1.3.jar

spring-aop-4.0.0.RELEASE.jar

spring-beans-4.0.0.RELEASE.jar

spring-context-4.0.0.RELEASE.jar

spring-core-4.0.0.RELEASE.jar

spring-expression-4.0.0.RELEASE.jar

Spring支援面向切面程式設計的包是:

spring-aspects-4.0.0.RELEASE.jar:基礎版

加強版的面向切面程式設計(即使目標物件沒有實現任何介面也能建立動態代理)

com.springsource.net.sf.cglib-2.2.0.jar

com.springsource.org.aopalliance-1.0.0.jar

com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

2)、寫配置;

     1)、將目標類和切面類(封裝了通知方法(在目標方法執行前後執行的方法))加入到ioc容器中

     2)、還應該告訴Spring到底哪個是切面類@Aspect

     3)、告訴Spring,切面類裡面的每一個方法,都是何時何地執行;

①MyMathCalculator.java

package com.gome.impl;

import org.springframework.stereotype.Service;

import com.gome.inter.Calculator;


@Service
public class MyMathCalculator {

    
    public int add(int i,double j){
        System.out.println("方法內部執行");
        return 0;
    }
    //@Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法內部執行");
        return result;
    }

    //@Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法內部執行");
        return result;
    }

    //@Override
    public int mul(int i, int j) {
        //方法的相容性;
        int result = i * j;
        System.out.println("方法內部執行");
        return result;
    }

    //@Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法內部執行");
        return result;
    }

}

② LogUtils.java

package com.gome.utils;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 如何將這個類(切面類)中的這些方法(通知方法)動態的在目標方法執行的各個位置切入
 * @author lfy
 *
 */
@Aspect
@Component
@Order(1)//使用Order改變切面順序;數值越小優先順序越高
public class LogUtils {
    
    /**
     * 告訴Spring每個方法都什麼時候執行;
     * try{
     *         @Before
     *         method.invoke(obj,args);
     *         @AfterReturning
     * }catch(e){
     *         @AfterThrowing
     * }finally{
     *         @After
     * }
     *     
     * 5個通知註解
     * @Before:在目標方法之前執行;                       前置通知
     * @After:在目標方法結束之後                        後置通知
     * @AfterReturning:在目標方法正常返回之後            返回通知
     * @AfterThrowing:在目標方法丟擲異常之後執行            異常通知
     * @Around:環繞                                環繞通知
     *
     *
     * 抽取可重用的切入點表示式;
     * 1、隨便宣告一個沒有實現的返回void的空方法
     * 2、給方法上標註@Pointcut註解
     */
    @Pointcut("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
    public void hahaMyPoint(){};
    


    
    //想在執行目標方法之前執行;寫切入點表示式
    //execution(訪問許可權符  返回值型別  方法簽名)
    @Before("hahaMyPoint()")
    public static void logStart(JoinPoint joinPoint){
        //獲取到目標方法執行是使用的引數
        Object[] args = joinPoint.getArgs();
        //獲取到方法簽名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-前置]【"+name+"】方法開始執行,用的引數列表【"+Arrays.asList(args)+"】");
    }
    
    /**
     * 切入點表示式的寫法;
     * 固定格式: execution(訪問許可權符  返回值型別  方法全類名(引數表))
     *   
     * 萬用字元:
     *         *:
     *             1)匹配一個或者多個字元:execution(public int com.atguigu.impl.MyMath*r.*(int, int))
     *             2)匹配任意一個引數:第一個是int型別,第二個引數任意型別;(匹配兩個引數)
     *                 execution(public int com.atguigu.impl.MyMath*.*(int, *))
     *             3)只能匹配一層路徑
     *             4)許可權位置*不能;許可權位置不寫就行;public【可選的】
     *         ..:
     *             1)匹配任意多個引數,任意型別引數
     *             2)匹配任意多層路徑:
     *                 execution(public int com.atguigu..MyMath*.*(..));
     *
     * 記住兩種;
     * 最精確的:execution(public int com.atguigu.impl.MyMathCalculator.add(int,int))
     * 最模糊的:execution(* *.*(..)):千萬別寫;
     *
     * &&”、“||”、“!
     *
     * &&:我們要切入的位置滿足這兩個表示式
     *     MyMathCalculator.add(int,double)
     * execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))
     *
     *
     * ||:滿足任意一個表示式即可
     * execution(public int com.atguigu..MyMath*.*(..))&&execution(* *.*(int,int))
     *
     * !:只要不是這個位置都切入
     * !execution(public int com.atguigu..MyMath*.*(..))
     *
     * 告訴Spring這個result用來接收返回值:
     *     returning="result";
     */
    //想在目標方法正常執行完成之後執行
    @AfterReturning(value="hahaMyPoint()",returning="result")
    public static void logReturn(JoinPoint joinPoint,Object result){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-返回]【"+name+"】方法正常執行完成,計算結果是:"+result);
    }

    
    /**
     * 細節四:我們可以在通知方法執行的時候,拿到目標方法的詳細資訊;
     * 1)只需要為通知方法的引數列表上寫一個引數:
     *         JoinPoint joinPoint:封裝了當前目標方法的詳細資訊
     * 2)、告訴Spring哪個引數是用來接收異常
     *         throwing="exception":告訴Spring哪個引數是用來接收異常
     * 3)、Exception exception:指定通知方法可以接收哪些異常
     *
     * ajax接受伺服器資料
     *     $.post(url,function(abc){
     *         alert(abc)
     *     })
     */
    //想在目標方法出現異常的時候執行
    @AfterThrowing(value="hahaMyPoint()",throwing="exception")
    public static void logException(JoinPoint joinPoint,Exception exception) {
        System.out.println("[LogUtils-異常]【"+joinPoint.getSignature().getName()+"】方法執行出現異常了,異常資訊是【"+exception+"】:;這個異常已經通知測試小組進行排查");
    }

    //想在目標方法結束的時候執行
    /**
     * Spring對通知方法的要求不嚴格;
     * 唯一要求的就是方法的引數列表一定不能亂寫?
     *     通知方法是Spring利用反射呼叫的,每次方法呼叫得確定這個方法的引數表的值;
     *     引數表上的每一個引數,Spring都得知道是什麼?
     *     JoinPoint:認識
     *     不知道的引數一定告訴Spring這是什麼?
     *
     * @param joinPoint
     */
    @After("hahaMyPoint()")
    private int logEnd(JoinPoint joinPoint) {
        System.out.println("[LogUtils-後置]【"+joinPoint.getSignature().getName()+"】方法最終結束了");
        return 0;
    }
    
    /**
     * @throws Throwable
     * @Around:環繞    :是Spring中強大的通知;
     * @Around:環繞:動態代理;
     *     try{
     *             //前置通知
     *             method.invoke(obj,args);
     *             //返回通知
     *     }catch(e){
     *             //異常通知
     *  }finally{
     *             //後置通知
     *     }
     *         
     *     四合一通知就是環繞通知;
     *     環繞通知中有一個引數:    ProceedingJoinPoint pjp
     *
     *環繞通知:是優先於普通通知執行,執行順序;
     *
     *[普通前置]
     *{
     *    try{
     *        環繞前置
     *        環繞執行:目標方法執行
     *        環繞返回
     *    }catch(){
     *        環繞出現異常
     *    }finally{
     *        環繞後置
     *    }
     *}
     *
     *
     *[普通後置]
     *[普通方法返回/方法異常]
     *新的順序:
     *        (環繞前置---普通前置)----目標方法執行----環繞正常返回/出現異常-----環繞後置----普通後置---普通返回或者異常
     *注意:
     */
    
    @Around("hahaMyPoint()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
        
        Object[] args = pjp.getArgs();
        String name = pjp.getSignature().getName();
        //args[0] = 100;
        Object proceed = null;
        try {
            //@Before
            System.out.println("【環繞前置通知】【"+name+"方法開始】");
            //就是利用反射呼叫目標方法即可,就是method.invoke(obj,args)
            proceed = pjp.proceed(args);
            //@AfterReturing
            System.out.println("【環繞返回通知】【"+name+"方法返回,返回值"+proceed+"】");
        } catch (Exception e) {
            //@AfterThrowing
            System.out.println("【環繞異常通知】【"+name+"】方法出現異常,異常資訊:"+e);
            //為了讓外界能知道這個異常,這個異常一定丟擲去
            throw new RuntimeException(e);
        } finally{
            //@After
            System.out.println("【環繞後置通知】【"+name+"】方法結束");
        }
        
        //反射呼叫後的返回值也一定返回出去
        return proceed;
    }
}

③ BValidateApsect.java

package com.gome.utils;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(2)
public class BValidateApsect {
    
    
    @Before("com.atguigu.utils.LogUtils.hahaMyPoint()")
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[VaApsect-前置]【"+name+"】方法開始執行,用的引數列表【"+Arrays.asList(args)+"】");
    }
    
    @AfterReturning(value="com.atguigu.utils.LogUtils.hahaMyPoint()",returning="result")
    public void logReturn(JoinPoint joinPoint,Object result){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[VaApsect-返回]【"+name+"】方法正常執行完成,計算結果是:"+result);
    }
    
    @AfterThrowing(value="com.atguigu.utils.LogUtils.hahaMyPoint()",throwing="exception")
    public void logException(JoinPoint joinPoint,Exception exception) {
        System.out.println("[VaApsect-異常]【"+joinPoint.getSignature().getName()+"】方法執行出現異常了,異常資訊是【"+exception+"】:;這個異常已經通知測試小組進行排查");
    }

    @After("com.atguigu.utils.LogUtils.hahaMyPoint()")
    private int logEnd(JoinPoint joinPoint) {
        System.out.println("[VaApsect-後置]【"+joinPoint.getSignature().getName()+"】方法最終結束了");
        return 0;
    }
}

④ AOPTest.java

package com.gome.test;

import static org.junit.Assert.*;

import java.util.Arrays;

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

import com.gome.impl.MyMathCalculator;

public class AOPTest {
    
    ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    
    
    
    /**
     * 通知方法的執行順序;
     *
     *   try{
     *       @Before
     *       method.invoke(obj,args);
     *       @AfterReturning
     *   }catch(){
     *       @AfterThrowing
     *   }finally{
     *       @After
     *   }
     *
     * 正常執行:  @Before(前置通知)[email protected](後置通知)[email protected](正常返回);
     * 異常執行: @Before(前置通知)[email protected](後置通知)[email protected](方法異常)
     *
     *
     *
     *
     */
    @Test
    public void test03(){
        MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);
        
        
        int add = bean.add(1, 1);
        System.out.println("========="+add);
        //bean.div(1, 0);
        //System.out.println("======");
    }
    
    @Test
    public void test02(){
        MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);
        bean.add(1, 0.2);
        
    }

    /**
     commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar:基礎版

加強版的面向切面程式設計(即使目標物件沒有實現任何介面也能建立動態代理)
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

     *
     */
    @Test
    public void test() {
        
//        MyMathCalculator myMathCalculator = new MyMathCalculator();
//        myMathCalculator.add(2, 12);
        
        //1、從ioc容器中拿到目標物件;注意:如果想要用型別,一定用 他的介面型別,不要用它本類
        //細節一:[email protected]
        //class com.sun.proxy.$Proxy12
        
        
        //AOP的底層就是動態代理,容器中儲存的元件是他的代理物件;$Proxy12。當然不是本類的型別
        
        //Calculator bean = ioc.getBean(Calculator.class);
        //bean.add(2, 1);
        //System.out.println(bean);
        //System.out.println(bean.getClass());
        
        
        
        //Calculator bean2 = (Calculator) ioc.getBean("myMathCalculator");
        //System.out.println(bean2.getClass());
        
        //沒有介面就是本類型別
        //class com.atguigu.impl.MyMathCalculator$$EnhancerByCGLIB$$fe279f42
        //cglib幫我們建立好的代理物件
        MyMathCalculator bean = (MyMathCalculator) ioc.getBean("myMathCalculator");
        bean.add(1, 2);
        System.out.println(bean.getClass());
        
        
    }

}

⑤ applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <context:component-scan base-package="com.atguigu"></context:component-scan>

    <!--  開啟基於註解的AOP功能;aop名稱空間-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

          4)、開啟基於註解的AOP模式

① applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <context:component-scan base-package="com.atguigu"></context:component-scan>

    <!--  開啟基於註解的AOP功能;aop名稱空間-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

3)、測試;

Calculator bean = ioc.getBean(Calculator.class);

        bean.add(2, 1);

AOP使用場景:

     1)、AOP加日誌儲存到資料庫;

     2)、AOP做許可權驗證;

     3)、AOP做安全檢查;

     4)、AOP做事務控制;

以上是使用註釋的方式實現aop,現在我們再來看看使用xml配置的方式實現aop

MyMathCalculator.java

package com.gome.impl;

import org.springframework.stereotype.Service;

import com.atguigu.inter.Calculator;


public class MyMathCalculator {

    
    public int add(int i,double j){
        System.out.println("方法內部執行");
        return 0;
    }
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法內部執行");
        return result;
    }

    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法內部執行");
        return result;
    }

    public int mul(int i, int j) {
        //方法的相容性;
        int result = i * j;
        System.out.println("方法內部執行");
        return result;
    }

    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法內部執行");
        return result;
    }

}

② LogUtils.java

package com.gome.utils;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

public class LogUtils {
    
    
    public static void logStart(JoinPoint joinPoint){
        //獲取到目標方法執行是使用的引數
        Object[] args = joinPoint.getArgs();
        //獲取到方法簽名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-前置]【"+name+"】方法開始執行,用的引數列表【"+Arrays.asList(args)+"】");
    }
    public static void logReturn(JoinPoint joinPoint,Object result){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[LogUtils-返回]【"+name+"】方法正常執行完成,計算結果是:"+result);
    }

    
    public static void logException(JoinPoint joinPoint,Exception exception) {
        System.out.println("[LogUtils-異常]【"+joinPoint.getSignature().getName()+"】方法執行出現異常了,異常資訊是【"+exception+"】:;這個異常已經通知測試小組進行排查");
    }

    public int logEnd(JoinPoint joinPoint) {
        System.out.println("[LogUtils-後置]【"+joinPoint.getSignature().getName()+"】方法最終結束了");
        return 0;
    }
    
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
        
        Object[] args = pjp.getArgs();
        String name = pjp.getSignature().getName();
        //args[0] = 100;
        Object proceed = null;
        try {
            //@Before
            System.out.println("【環繞前置通知】【"+name+"方法開始】");
            //就是利用反射呼叫目標方法即可,就是method.invoke(obj,args)
            proceed = pjp.proceed(args);
            //@AfterReturing
            System.out.println("【環繞返回通知】【"+name+"方法返回,返回值"+proceed+"】");
        } catch (Exception e) {
            //@AfterThrowing
            System.out.println("【環繞異常通知】【"+name+"】方法出現異常,異常資訊:"+e);
            //為了讓外界能知道這個異常,這個異常一定丟擲去
            throw new RuntimeException(e);
        } finally{
            //@After
            System.out.println("【環繞後置通知】【"+name+"】方法結束");
        }
        
        //反射呼叫後的返回值也一定返回出去
        return 1000;
    }
}

③ BValidateApsect.java

package com.gome.utils;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

public class BValidateApsect {
    
    
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[VaApsect-前置]【"+name+"】方法開始執行,用的引數列表【"+Arrays.asList(args)+"】");
    }
    
    public void logReturn(JoinPoint joinPoint,Object result){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println("[VaApsect-返回]【"+name+"】方法正常執行完成,計算結果是:"+result);
    }
    
    public void logException(JoinPoint joinPoint,Exception exception) {
        System.out.println("[VaApsect-異常]【"+joinPoint.getSignature().getName()+"】方法執行出現異常了,異常資訊是【"+exception+"】:;這個異常已經通知測試小組進行排查");
    }

    public int logEnd(JoinPoint joinPoint) {
        System.out.println("[VaApsect-後置]【"+joinPoint.getSignature().getName()+"】方法最終結束了");
        return 0;
    }
}

④ AOPTest.java

package com.gome.test;

import static org.junit.Assert.*;

import java.util.Arrays;

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

import com.gome.impl.MyMathCalculator;

public class AOPTest {
    
    ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    
    
    
    /**
     * 通知方法的執行順序;
     *
     *   try{
     *       @Before
     *       method.invoke(obj,args);
     *       @AfterReturning
     *   }catch(){
     *       @AfterThrowing
     *   }finally{
     *       @After
     *   }
     *
     * 正常執行:  @Before(前置通知)[email protected](後置通知)[email protected](正常返回);
     * 異常執行: @Before(前置通知)[email protected](後置通知)[email protected](方法異常)
     *
     *
     *
     *
     */
    @Test
    public void test03(){
        MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);
        
        
        int add = bean.add(1, 1);
        System.out.println("========="+add);
        //bean.div(1, 0);
        //System.out.println("======");
    }
    
    @Test
    public void test02(){
        MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);
        bean.add(1, 0.2);
        
    }

    /**
     commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar:基礎版

加強版的面向切面程式設計(即使目標物件沒有實現任何介面也能建立動態代理)
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

     *
     */
    @Test
    public void test() {
        
//        MyMathCalculator myMathCalculator = new MyMathCalculator();
//        myMathCalculator.add(2, 12);
        
        //1、從ioc容器中拿到目標物件;注意:如果想要用型別,一定用 他的介面型別,不要用它本類
        //細節一:[email protected]
        //class com.sun.proxy.$Proxy12
        
        
        //AOP的底層就是動態代理,容器中儲存的元件是他的代理物件;$Proxy12。當然不是本類的型別
        
        //Calculator bean = ioc.getBean(Calculator.class);
        //bean.add(2, 1);
        //System.out.println(bean);
        //System.out.println(bean.getClass());
        
        
        
        //Calculator bean2 = (Calculator) ioc.getBean("myMathCalculator");
        //System.out.println(bean2.getClass());
        
        //沒有介面就是本類型別
        //class com.atguigu.impl.MyMathCalculator$$EnhancerByCGLIB$$fe279f42
        //cglib幫我們建立好的代理物件
        MyMathCalculator bean = (MyMathCalculator) ioc.getBean("myMathCalculator");
        bean.add(1, 2);
        System.out.println(bean.getClass());
        
        
    }

}

⑤ applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

    <!-- 基於註解的AOP步驟;
    1、將目標類和切面類都加入到ioc容器中。@Component
    2、告訴Spring哪個是切面類。@Aspect
    3、在切面類中使用五個通知註解來配置切面中的這些通知方法都何時何地執行
    4、開啟基於註解的AOP功能
     -->
    <!--  開啟基於註解的AOP功能;aop名稱空間-->
    
    <!--  基於配置的AOP-->
    <bean id="myMathCalculator" class="com.atguigu.impl.MyMathCalculator"></bean>
    <bean id="BValidateApsect" class="com.atguigu.utils.BValidateApsect"></bean>
    <bean id="logUtils" class="com.atguigu.utils.LogUtils"></bean>
    
    <!-- 需要AOP名稱空間 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.atguigu.impl.*.*(..))" id="globalPoint"/>
    
    
        <!-- 普通前置  ===== 目標方法  =====(環繞執行後置/返回)====s普通後置====普通返回    -->
        <!-- 指定切面:@Aspect -->
        <aop:aspect ref="logUtils" order="1">
            <!-- 配置哪個方法是前置通知;method指定方法名
            [email protected]("切入點表示式")
            -->
            <!-- 當前切面能用的 -->
            <aop:around method="myAround" pointcut-ref="mypoint"/>
            <aop:pointcut expression="execution(* com.atguigu.impl.*.*(..))" id="mypoint"/>
            <aop:before method="logStart" pointcut="execution(* com.atguigu.impl.*.*(..))"/>
            <aop:after-returning method="logReturn" pointcut-ref="mypoint" returning="result"/>
            <aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="exception"/>
            <aop:after method="logEnd" pointcut-ref="mypoint"/>
        </aop:aspect>
    
        <aop:aspect ref="BValidateApsect" order="3">
            <aop:before method="logStart" pointcut-ref="globalPoint"/>
            <aop:after-returning method="logReturn" pointcut-ref="globalPoint" returning="result"/>
            <aop:after-throwing method="logException" pointcut-ref="globalPoint" throwing="exception"/>
            <aop:after method="logEnd" pointcut-ref="globalPoint"/>
        </aop:aspect>
        <!-- 在切面類中使用五個通知註解來配置切面中的這些通知方法都何時何地執行 -->
    </aop:config>

    <!--註解:快速方便
        配置:功能完善;重要的用配置,不重要的用註解;
      -->
</beans>

注意:

存在多個切面類時切面通知的執行順序預設是按照 類名字的首字母順序來執行的,我們可以使用(@Order(1)//使用Order改變切面順序;數值越小優先順序越高),並且先執行@Before: 前置通知的切面類會最後執行@After:後置通知,即先進後出原則;

還有就是五種通知中的@Around:環繞通知比較特殊,但也僅僅只是作用在本切面類的而已,無法控制其他的切面類,帶有環繞通知的切面類的通知執行順序為:

(環繞前置---普通前置)----目標方法執行----環繞正常返回/出現異常-----環繞後置----普通後置---普通返回或者異常

剩下的看上邊的原始碼吧,寫的很細。。