Spring 學習筆記心得(九)AOP面向切面編程

首先我們應該想想爲什麼要使用aop面向切面編程?面向切面的底層實現是什麼?小編在這裏舉個例子吧
小編首先給出Spring全家桶,方便大家下載使用---->Spring全家桶

1.自定義代理對象代理類以及實現類

1.1 定義接口(ArithmeticCacluetator)

public interface ArithmeticCacluetator {
/*  
   定義加減乘除四個方法
*/
    public void add(int i , int j);
    public void sub(int i , int j);
    public void mul(int i , int j);
    public void div(int i , int j);
}

1.2 創建實現類(ArithmeticCacluetatorImpl),並且這裏在每一個方法都輸出了操作日誌

public class ArithmeticCacluetatorImpl implements ArithmeticCacluetator {
    @Override
    public void add(int i, int j) {
        System.out.println(" i : "+ i + " + "  + " j : " + j + " = " + (i + j));
    }

    @Override
    public void sub(int i, int j) {
        System.out.println(" i : "+ i + " - "  + " j : " + j + " = " + (i - j));
    }

    @Override
    public void mul(int i, int j) {
        System.out.println(" i : "+ i + " * "  + " j : " + j + " = " + (i * j));
    }

    @Override
    public void div(int i, int j) {
        System.out.println(" i : " + i +  " / "  + " j : " + j + " = " + (i / j));
    }
}

1.3 創建測試類:

 ArithmeticCacluetator cacluetator = new ArithmeticCacluetatorImpl();
        cacluetator.add(1,3);
        cacluetator.sub(1,3);
        cacluetator.mul(1,3);
        cacluetator.div(1,3);

輸出結果:
i : 1 + j : 3 = 4
i : 1 - j : 3 = -2
i : 1 * j : 3 = 3
i : 1 / j : 3 = 0

不難發現,這樣一一輸出會十分的麻煩,每個方法裏面都要去寫一個輸出的日誌,這樣還會導致代碼實現類的可讀性很差,所以我們應當棄用這種寫法,而使用代理類來幫助我們實現


1.4 創建代理類(這裏默認大家有動態代理基礎哈)

public class ArithmeticCacluetatirProxy {

    public static void main(String[] args) {

        //真實對象
        ArithmeticCacluetatorImpl arithmeticCacluetator = new ArithmeticCacluetatorImpl();
        //代理對象
        ArithmeticCacluetator arithmeticCacluetator1_proxy = (ArithmeticCacluetator) Proxy.newProxyInstance(arithmeticCacluetator.getClass().getClassLoader(), arithmeticCacluetator.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				//這裏判斷只是用來模擬其他的不同的方法名,以及要執行的對應的日誌
                if(method.getName().equals("add")){
                    System.out.println("Welcome to "+method.getName()+" Arithmeticacluetator !!!");
                    Object obj = method.invoke(arithmeticCacluetator, args);
                    return obj;
                }else{
                    System.out.println("Welcome to AutoJugeMethod "+method.getName()+" Arithmeticacluetator !!!");
                    Object obj = method.invoke(arithmeticCacluetator, args);
                    return obj;
                }
            }
        });
        //這裏是通過代理類去調用方法
        arithmeticCacluetator1_proxy.add(12,13);
        arithmeticCacluetator1_proxy.sub(12,13);
        arithmeticCacluetator1_proxy.mul(12,13);
        arithmeticCacluetator1_proxy.div(12,13);

    }

輸出結果:
method : add
Welcome to add Arithmeticacluetator !!!
i : 12 + j : 13 = 25

method : sub
Welcome to AutoJugeMethod sub Arithmeticacluetator !!!
i : 12 - j : 13 = -1

method : mul
Welcome to AutoJugeMethod mul Arithmeticacluetator !!!
i : 12 * j : 13 = 156

method : div
Welcome to AutoJugeMethod div Arithmeticacluetator !!!
i : 12 / j : 13 = 0

於是我們通過動態代理的形式,簡化了日誌的輸出,這種編程思想,就稱之爲面向切面編程(AOP)

2.AOP註解:

@Before 前置通知,在方法執行之前執行
@After 後置通知,在方法執行之後執行
@AfterRunning; 返回通知,在方法返回結果之後執行
@AfterThrowing 異常通知,在方法拋出異常之後
@Around 環繞通知,圍繞着方法執行

注意:要在Spring中聲明AspectJ切面,只需要在I0C容器中將切面聲明爲Bean實例.當在Spring I0C容器中初始化AspectJ切面之後,Spring I0C容器就會爲那些與AspectJ切面相匹配的Bean創建代理.

2.1 搭建基本環境(要聲明爲IOC容器再聲明切面纔有效)

2.1.1 創建LogginAspect 類,首先聲明爲IOC容器(@Componet),再次聲明爲切面(@Aspect)創建beforeInfo方法。
1.添加註解@Before(在方法執行之前執行 ),所以我們應該是最先看到這個方法裏面的內容輸出。
2.表達式:execution([方法可見性] xx(具體類全路徑).xx(方法) ) 標識在xx包下的xx方法(是否包含參數 如果是包含參數就寫出對應的參數類型【int , int …】 , 如果是任意參數就傳入 ‘*’[ . . ])

@Component
@Aspect
public class LogginAspect {
		//參數:
		//JoinPoint 切入點 		
    @Before("execution(* cn.itcast.spring.aop.Impl.Service.ArithmeticCacluetatorImpl.*(..))")
    public void beforeInfo(JoinPoint joinpoint) {
        String name = joinpoint.getSignature().getName();//獲取方法名稱
        Object[] args = joinpoint.getArgs();//獲取傳入的參數
        System.out.println("before... " + " name : " + name);
        System.out.println("args : " + Arrays.asList(args));
    }

2.1.2 創建spring(bean-aop.xml)配置文件中配置掃描,並且開啓aop切面代理模式

 <context:component-scan base-package="cn.itcast.spring.aop.Impl.services"/>

    <aop:aspectj-autoproxy/>

2.1.3 編寫測試類AspectTestDemo

            // 創建IOC
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-aop.xml");
        ArithmeticCacluetator bean = context.getBean(ArithmeticCacluetator.class);

        System.out.println(bean.getClass().getName());  //這裏獲取bean的類型
        bean.add(121,131);

爲了清楚測試結果,小編將實現類(ArithmeticCacluetatorImpl)中日誌輸出全部都刪除了,現在測試的是add方法,所以小編在add方法中打印一句話

 @Override
    public void add(int i, int j) {
     System.out.println("我是add方法");
    }

    @Override
    public void sub(int i, int j) {
    }

    @Override
    public void mul(int i, int j) {
    }

    @Override
    public void div(int i, int j) {
    }

輸出結果:
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法

不難發現,我們在方法中輸出的這句話是最後面才輸出的,所以@Before註解是在方法執行前調用的

2.2 @After後置通知註解配置使用

@After("execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetator.*(..))")
    public void afterMethod(JoinPoint joinpoint) {
        String name = joinpoint.getSignature().getName();
        System.out.println("after... " + name);
    }

然後再次跑一次測試類:輸出結果如下

com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add

2.3 @AfterReturning返回值通知註解配置使用。
注意:
1.只有當沒有發生異常時,纔會執行此方法。2.添加returning = value 參數 用來接受返回值。3.在方法中聲明Object value 返回值

/**
     只有當成功了纔會
      @param joinpoint
      @param result
     */
    @AfterReturning(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",returning = "result")
    public void returnMethod(JoinPoint joinpoint,Object result) {
        String name = joinpoint.getSignature().getName();
        System.out.println("return... " + name + " : " + result);
    }

調用測試方法:

輸出結果:
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
return… add : null

因爲小編的add方法是void類型,所以這裏沒有返回值,返回的是null。這裏小編臨時性的將add方法的返回值改爲int類型,再次查看輸出結果如下:

@Override
    public int add(int i, int j) {
        System.out.println("我是add方法");
        return i+j;
    }

com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
return… add : 252

2.4 @AfterThrowing異常通知註解配置使用。
注意:1.只會在指定的異常出現時纔會觸發。2.定義throwing = value 設置異常參數名稱。3.在方法中聲明 (異常類型 value)這裏小編聲明的是空指針異常

 /**
     * 指定什麼異常  就在什麼異常發生時執行
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",
    throwing = "ex")
    public void throwException(JoinPoint joinPoint , NullPointerException ex){
        System.out.println("ERROR ... " + ex);
    }

2.4.1 修改實現類中的除法方法,自定義一個數學異常,來測試異常聲明

 @Override
    public void div(int i, int j) {
        int c = i / j;
    }

2.4.2 調用div方法,傳入參數【100,0】

輸出結果:
com.sun.proxy.$Proxy19
before… name : div
args : [100, 0]
after… div
Exception in thread “main” java.lang.ArithmeticException: / by zero

2.4.3 修改throwException方法中的異常聲明爲Exception

@AfterThrowing(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",
    throwing = "ex")
    public void throwException(JoinPoint joinPoint , Exception ex){
        System.out.println("ERROR ... " + ex);
    }

輸出結果:
com.sun.proxy.$Proxy19
before… name : div
args : [100, 0]
after… div
ERROR … java.lang.ArithmeticException: / by zero

2.4.4 @Pointcut註解的使用。1.同類目錄下可直接引用註解。2.異類目錄下需要寫全路徑。3.好處:因爲小編剛纔演示的時候每一次都要寫一次表達式,並且表達式都是一模一樣的,這樣就寫了很多重複的代碼,做着同樣的事,避免麻煩,我們可以使用@Pointcut註解來聲明表達式,可以在需要的地方直接引用即可

@Pointcut("execution(* cn.itcast.spring.aop.Impl.Order.Service.ArithmeticCacluetator.*(..))")
    public void declareExecutionExpression(){};
    
    @Before("declareExecutionExpression()")
    public void sayMehtod(){
        System.out.println(" VildationAspect aop ... ");
    }

2.4.5 異類下的的引用:

@Before("cn.itcast.spring.aop.Impl.Order.VildationAspect.declareExecutionExpression()")
    public void beforeMethod(){
        System.out.println("before Method ... ");
    }

2.5 @Around環繞通知註解使用(基於動態代理 , 小編這裏單獨將環繞通知提取出來了,不會受到之前配置過的四個通知的影響)。注意:1.切入點應該聲明爲ProceedingJoinPoint。2.聲明返回值爲Object類型。


    /**
      可以獲取  String method;
     @param jdp
      @return
     */
    @Around("execution(* cn.itcast.spring.aop.Impl.Around.Services.ArithmeticCacluetatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint jdp){
       Object result = null;
        String name = jdp.getSignature().getName();
        try {
            //前置通知
            System.out.println("Around Before ... getArgs ...  " + Arrays.asList(jdp.getArgs()));
            result = jdp.proceed();  //method.invoke(); 代理對象調用
            //後置通知
            System.out.println("Around After .... " + name);
            //返回通知
            System.out.println("Around AfterReturing  ... " + result);
        }catch (Throwable e){
            //出現異常,這裏不選擇手動拋出,用控制檯打印
            System.out.println("The Method : " + name +"Error Appentend ...  " + e);
        }
        return result;
    }

2.5.1 調用實現類中的div方法

 bean.div(10,0);

輸出結果:
Around Before … getArgs … [10, 0]
The Method : divError Appentend … java.lang.ArithmeticException: / by zero

這裏出現了異常,僅僅只執行了前置通知,後面的通知都沒有被執行,就直接走進catch中去了

2.5.2 在接口中添加public String show(String name)方法,實現類實現

  @Override
    public String show(String name) {
        System.out.println(name+" comming...");
        return name;
    }

2.5.3 執行show(“Audi”)方法

測試結果如下:
Around Before … getArgs … [Audi]
Audi comming…
Around After … show
Around AfterReturing … Audi

3.AOP 使用XML形式實現:

3.1 因爲是在XML中聲明使用,所以我們便不需要再添加註解。創建spring配置文件使用(bean-application-xml.xml)這裏需要用到aop,context標籤,將這段複製到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:p="http://www.springframework.org/schema/p"
       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/aop
       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd">
       </beans>

3.2 聲明所有使用到的Bean

   <!-- 配置使用到的bean -->
<bean id="logginAspect" class="cn.itcast.spring.aop_xml.LogginAspect"/>
<bean id="vildationAspect" class="cn.itcast.spring.aop_xml.VildationAspect"/>
<bean id="arithmeticCacluetator" class="cn.itcast.spring.aop_xml.Service.ArithmeticCacluetatorImpl"></bean>
<bean id="logginAround" class="cn.itcast.spring.aop.Impl.Around.LogginAround"></bean>

3.3 切面類LogginAspect

public class LogginAspect {


    public void beforeInfo(JoinPoint joinpoint) {
        String name = joinpoint.getSignature().getName();
        Object[] args = joinpoint.getArgs();
        System.out.println("before... " + " name : " + name);
        System.out.println("args : " + Arrays.asList(args));
    }

    public void afterMethod(JoinPoint joinpoint) {
        String name = joinpoint.getSignature().getName();
        System.out.println("after... " + name);
    }

    public void returnMethod(JoinPoint joinpoint,Object result) {
        String name = joinpoint.getSignature().getName();
        System.out.println("return... " + name + " : " + result);
    }


    public void throwException(JoinPoint joinPoint , NullPointerException ex){
        System.out.println("ERROR ... " + ex);
    }

}

3.4 VildationAspect類(設置了Order屬性,方法是最先被執行的)

public void sayMehtod(){
        System.out.println(" VildationAspect aop ... ");
    }

3.5 配置程序一致:

<aop:config>
        <!--
          配置的基本邏輯:
                1.配置pointcut 公共指向
                2.配置Aspect 註解 ref  - 涉及到的類
                3.配置 [ 前置 , 後置 , 返回值 , 拋出異常 , 環繞通知]
        -->
        <aop:pointcut id="declearExpression" expression="execution(* cn.itcast.spring.aop_xml.Service.ArithmeticCacluetator.*(..))"></aop:pointcut>
       <!-- 切面作用在哪個類上 相當於註解@Aspect  -->
        <aop:aspect ref="logginAspect">
           <!--  基本配置  -->
           <aop:before method="beforeInfo" pointcut-ref="declearExpression"/>
            <aop:after method="afterMethod" pointcut-ref="declearExpression"/>
        	<!-- 這裏注意參數一定要對應 -->
            <aop:after-returning method="returnMethod" pointcut-ref="declearExpression" returning="result"/>
            <aop:after-throwing method="throwException" pointcut-ref="declearExpression" throwing="ex"/>
          
        </aop:aspect>
        
        <!--  配置優先級別 -->
    <aop:aspect ref="vildationAspect" order="1">
        <aop:before method="sayMehtod" pointcut-ref="declearExpression"/>
    </aop:aspect>
        <aop:aspect ref="logginAround">
            <aop:around method="aroundMethod" pointcut-ref="declearExpression"></aop:around>
        </aop:aspect>

    </aop:config>

3.6 測試類:

ArithmeticCacluetator bean = (ArithmeticCacluetator) context.getBean("arithmeticCacluetator");
        System.out.println(bean);
        int result = bean.add(12, 3);//返回值爲int類型
        System.out.println("result : " + result);

輸出結果:
VildationAspect aop … 設置了Order屬性,所以sayMethod方法最先被執行
Around Before … getArgs … [12, 3] //前置通知
Around After … add // 後置通知
Around AfterReturing … 15 //返回值通知
result : 15 //自己打印的返回值

好了,這裏就是AOP面向切面編程的內容了,雖然有點抽象,但是相信通過不斷地學習,可以不斷地去了解它

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