1. 程式人生 > >基於註解@Aspect的AOP實現

基於註解@Aspect的AOP實現

Spring只支援XML方式而沒有實現註解的方式(也叫AspectJ方式)的AOP,所以要使用@Aspect註解,只能引入AspectJ相關的 jar 包 aopalliance-1.0.jar 和 aspectjweaver.jar,這個坑把我給坑慘了。

使用步驟如下:

1、引入相關jar包

這裡寫圖片描述

2、Spring的配置檔案 applicationContext.xml 中引入context、aop對應的名稱空間;配置自動掃描的包,同時使切面類中相關方法中的註解生效,需自動地為匹配到的方法所在的類生成代理物件。

<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"
>
<!-- 配置自動掃描的包 --> <context:component-scan base-package="com.qcc.beans.aop"></context:component-scan> <!-- 自動為切面方法中匹配的方法所在的類生成代理物件。 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>

3、建立簡單計算器的介面ArithmeticCalculator.java及實現類ArithmeticCalculatorImpl.java

package com.qcc.beans.aop;

public interface ArithmeticCalculator {

    int add(int i, int j);

    int sub(int i, int j);

    int mul(int i, int j);

    int div(int i, int j);
}
package com.qcc.beans.aop;

import org.springframework.stereotype.Component;

//將實現類加入Spring的IOC容器進行管理
@Component("arithmeticCalculator")
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int add(int i, int j) {
        int result = i + j;
        return result;
    }

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

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

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

}

4、現在想在實現類中的每個方法執行前、後、以及是否發生異常等資訊打印出來,需要把日誌資訊抽取出來,寫到對應的切面的類中 LoggingAspect.java 中
要想把一個類變成切面類,需要兩步,
① 在類上使用 @Component 註解 把切面類加入到IOC容器中
② 在類上使用 @Aspect 註解 使之成為切面類

下面直接上完整程式碼,用@Aspect註解方式來實現前置通知、返回通知、後置通知、異常通知、環繞通知。

package com.qcc.beans.aop;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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.springframework.stereotype.Component;

/**
 * 日誌切面
 * 
 * @author QianChaoChen 00002336<br>
 * @date 2017年3月3日 下午3:03:29
 */
@Component
@Aspect
public class LoggingAspect {

    /**
     * 前置通知:目標方法執行之前執行以下方法體的內容 
     * @param jp
     */
    @Before("execution(* com.qcc.beans.aop.*.*(..))")
    public void beforeMethod(JoinPoint jp){
        String methodName = jp.getSignature().getName();
        System.out.println("【前置通知】the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));
    }

    /**
     * 返回通知:目標方法正常執行完畢時執行以下程式碼
     * @param jp
     * @param result
     */
    @AfterReturning(value="execution(* com.qcc.beans.aop.*.*(..))",returning="result")
    public void afterReturningMethod(JoinPoint jp, Object result){
        String methodName = jp.getSignature().getName();
        System.out.println("【返回通知】the method 【" + methodName + "】 ends with 【" + result + "】");
    }

    /**
     * 後置通知:目標方法執行之後執行以下方法體的內容,不管是否發生異常。
     * @param jp
     */
    @After("execution(* com.qcc.beans.aop.*.*(..))")
    public void afterMethod(JoinPoint jp){
        System.out.println("【後置通知】this is a afterMethod advice...");
    }

    /**
     * 異常通知:目標方法發生異常的時候執行以下程式碼
     */
    @AfterThrowing(value="execution(* com.qcc.beans.aop.*.*(..))",throwing="e")
    public void afterThorwingMethod(JoinPoint jp, NullPointerException e){
        String methodName = jp.getSignature().getName();
        System.out.println("【異常通知】the method 【" + methodName + "】 occurs exception: " + e);
    }

//  /**
//   * 環繞通知:目標方法執行前後分別執行一些程式碼,發生異常的時候執行另外一些程式碼
//   * @return 
//   */
//  @Around(value="execution(* com.qcc.beans.aop.*.*(..))")
//  public Object aroundMethod(ProceedingJoinPoint jp){
//      String methodName = jp.getSignature().getName();
//      Object result = null;
//      try {
//          System.out.println("【環繞通知中的--->前置通知】:the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));
//          //執行目標方法
//          result = jp.proceed();
//          System.out.println("【環繞通知中的--->返回通知】:the method 【" + methodName + "】 ends with " + result);
//      } catch (Throwable e) {
//          System.out.println("【環繞通知中的--->異常通知】:the method 【" + methodName + "】 occurs exception " + e);
//      }
//      
//      System.out.println("【環繞通知中的--->後置通知】:-----------------end.----------------------");
//      return result;
//  }

}

5、編寫Main方法進行測試

package com.qcc.beans.aop;

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

public class Main {

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
        System.out.println(arithmeticCalculator.getClass());
        int result = arithmeticCalculator.add(3, 5);
        System.out.println("result: " + result);

        result = arithmeticCalculator.div(5, 0);
        System.out.println("result: " + result);

    }
}

執行結果:

class com.sun.proxy.$Proxy10
【前置通知】the method 【add】 begins with [3, 5]
【後置通知】this is a afterMethod advice...
【返回通知】the method 【add】 ends with 【8result: 8
【前置通知】the method 【div】 begins with [5, 0]
【後置通知】this is a afterMethod advice...
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.qcc.beans.aop.ArithmeticCalculatorImpl.div(ArithmeticCalculatorImpl.java:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:43)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy10.div(Unknown Source)
    at com.qcc.beans.aop.Main.main(Main.java:15)

把其它程式碼都註釋掉,把環繞通知的方法釋放出來,測試結果如下:

【環繞通知中的--->前置通知】:the method 【add】 begins with [3, 5]
【環繞通知中的--->返回通知】:the method 【add】 ends with 8
【環繞通知中的--->後置通知】:-----------------end.----------------------
result: 8
【環繞通知中的--->前置通知】:the method 【div】 begins with [5, 0]
【環繞通知中的--->異常通知】:the method 【div】 occurs exception java.lang.ArithmeticException: / by zero
【環繞通知中的--->後置通知】:-----------------end.----------------------
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int com.qcc.beans.aop.ArithmeticCalculator.div(int,int)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219)
    at com.sun.proxy.$Proxy7.div(Unknown Source)
    at com.qcc.beans.aop.Main.main(Main.java:15)

從以上發現,返回通知和異常通知不會同時出現;不管是否發生異常,後置通知都會正常列印。