Spring4 AOP詳解

Spring4 AOP詳解

第一章Spring 快速入門並無對Spring4 的 AOP 作太多的描述,是由於AOP切面編程概念很差理解。因此這章主要從三個方面詳解AOP:AOP簡介(瞭解),基於註解的AOP編程(重點)和基於xml的AOP編程。java


AOP簡介

什麼是AOP

AOP(Aspect Oriented Programming)面向切面編程,是對傳統的OOP(ObjectOriented Programming)面向對象編程的補充。spring

AOP的做用

若是A,B,C三個方法都要在執行前作驗證操做,執行後作日誌打印操做。腫麼辦?
20170925103224577express

排版好醜。。。。。。編程

AOP專業術語

切面(Aspect): A,B,C,方法執行前都要調用的驗證邏輯和執行後都要調用的日誌邏輯,這兩層業務邏輯就是切面。
通知(Advice): 有五種通知,執行前,執行後,執行成功後,執行拋出異常後,環繞通知。就是切面執行的方法。
目標(Target): 被通知的對象,這裏就是A,B,C三個方法。
鏈接點(Joinpoint):鏈接點是一個應用執行過程當中可以插入一個切面的點。
切點(pointcut):每一個類都擁有多個鏈接點,即鏈接點是程序類中客觀存在的事務。AOP 經過切點定位到特定的鏈接點
打個比方:一天,三位俠客(被通知的對象Target)來我府上作客,被大門(切面Aspect)攔住,門前有五個保安(負責通知的Advice),由於其中一位俠客會降龍十八掌(知足被通知的一個條件Joinpoint),其中一位保安告知他:"你能夠進去了"。另外兩個俠客由於武藝超羣(知足被通知的統一標準poincut)也都進去了。segmentfault


基於註解的AOP編程

基於註解的編程,須要依賴AspectJ框架(java中最流行的aop框架)。
第一步:導入AspectJ的jar包,該框架只有Spring 2.0以上才支持。app

<dependency>  
    <groupId>org.springframework</groupId>  
    <artifactId>spring-aspects</artifactId>  
    <version>4.2.2.RELEASE</version>  
</dependency>

第二步:核心文件applicationContext.xml,裏面須要配置自動掃描包(用於IOC註解)和配置啓用AspectJ註解框架

<?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.itdragon.spring.*" ></context:component-scan>  
  
    <!-- 使 AspectJ 的註解起做用 -->  
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  
      
</beans>

第三步:切面類,該類有什麼特色?首先它必須是IOC的bean,還要聲明它是AspectJ切面,最後還能夠定義切面的優先級Order(非必填)
通知有五種註解
@Before :前置通知的註解,在目標方法執行前調用
@After:後置通知的註解, 在目標方法執行後調用,即便程序拋出異常都會調用
@AfterReturning:返回通知的註解, 在目標方法成功執行後調用,若是程序出錯則不會調用
@AfterThrowing:異常通知的註解, 在目標方法出現指定異常時調用
@Around:環繞通知的註解,很強大(至關於前四個通知的組合),但用的很少,
還有爲了簡化開發的重用切入點@Pointcut,以及抽象表達"*"ide

public interface Calculator {  
      
    public int add(int a, int b);  
    public int division(int a, int b);  
  
}
import org.springframework.stereotype.Repository;  
@Repository("calculator")  
public class CalculatorImp implements Calculator {  
  
    @Override  
    public int add(int a, int b) {  
        System.out.println("add 方法執行了 ----> " + (a + b));  
        return (a + b);  
    }  
  
    @Override  
    public int division(int a, int b) {  
        System.out.println("division 方法執行了 ----> " + (a / b));  
        return (a / b);  
    }  
  
}
import java.util.Arrays;  
import java.util.List;  
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.core.annotation.Order;  
import org.springframework.stereotype.Component;  
  
/** 
 * @Order(n) : 切面的優先級,n越小,級別越高 
 * @Aspect:聲明該類是一個切面 
 * @Component:切面必須是 IOC 中的 bean 
 */  
@Order(2)  
@Aspect  
@Component  
public class LoggerAspect {  
      
    /** 
     * 前置通知的註解,在目標方法執行前調用 
     * execution最基礎的表達式語法。 
     * 注意點: 
     * 1. 方法裏面不能有行參,及add(int a, int b) 這是會報錯的。 
     * 2. int(方法的返回值),add(方法名) 能夠用 * 抽象化。甚至能夠將類名抽象,指定該包下的類。 
     * 3. (int, int) 能夠用(..)代替,表示匹配任意數量的參數 
     * 4. 被通知的對象(Target),建議加上包的路徑 
     */  
    @Before("execution(int com.atguigu.spring.my.aop.CalculatorImp.add(int , int))")  
    public void beforeAdvice(JoinPoint joinPoint) {  
        /** 
         * 鏈接點 joinPoint:add方法就是鏈接點 
         * getName獲取的是方法名,是英文的,能夠經過國際化轉換對應的中文比較好。 
         */  
        String methodName = joinPoint.getSignature().getName();   
        List<Object> args = Arrays.asList(joinPoint.getArgs());  
        System.out.println("@Before 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
    }  
      
    /** 
     * 後置通知的註解, 在目標方法執行後調用,即便是程序出錯都會調用 
     * 這裏將 方法的返回值 和 CalculatorImp類下全部的方法,以及方法的形參 都抽象了 
     */  
    @After("execution(* com.atguigu.spring.my.aop.CalculatorImp.*(..))")  
    public void afterAdvice(JoinPoint joinPoint) {  
        String methodName = joinPoint.getSignature().getName();   
        List<Object> args = Arrays.asList(joinPoint.getArgs());  
        System.out.println("@After 後置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
    }  
      
    /** 
     * 重用切入點定義:聲明切入點表達式。該方法裏面不建議添加其餘代碼 
     */  
    @Pointcut("execution(* com.atguigu.spring.my.aop.CalculatorImp.*(..))")  
    public void declareExecutionExpression(){}  
      
    /** 
     * 返回通知的註解, 在目標方法成功執行後調用,若是程序出錯則不會調用 
     * returning="result" 和 形參 result 保持一致 
     */  
    @AfterReturning(value="declareExecutionExpression()", returning="result")  
    public void afterRunningAdvice(JoinPoint joinPoint, Object result) {  
        String methodName = joinPoint.getSignature().getName();   
        List<Object> args = Arrays.asList(joinPoint.getArgs());  
        System.out.println("@AfterReturning 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);  
    }  
      
    /** 
     * 異常通知的註解, 在目標方法出現指定異常時調用 
     * throwing="exception" 和 形參 exception 保持一致 , 且目標方法出了Exception(能夠是其餘異常)異常纔會調用。 
     */  
    @AfterThrowing(value="declareExecutionExpression()", throwing="exception")  
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {  
        String methodName = joinPoint.getSignature().getName();   
        System.out.println("@AfterThrowing 異常通知 : 方法名 【 " + methodName + " 】and  exception is " + exception);  
    }  
  
}
import java.util.Arrays;  
import java.util.List;  
import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.Around;  
import org.aspectj.lang.annotation.Aspect;  
import org.springframework.core.annotation.Order;  
import org.springframework.stereotype.Component;  
  
@Order(1)  
@Aspect  
@Component  
public class AroundAspect {  
  
    /** 
     * 環繞通知,很強大,但用的很少。 用環繞通知測試Order的優先級看的不明顯(這裏是筆者的失誤) 
     * 環繞通知須要用ProceedingJoinPoint 類型的參數 
     */  
    @Around("execution(* com.atguigu.spring.my.aop.CalculatorImp.*(..))")  
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) {  
        Object result = null;  
        String methodName = joinPoint.getSignature().getName();   
        List<Object> args = Arrays.asList(joinPoint.getArgs());  
        try {  
            System.out.println("@Around 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
            result = joinPoint.proceed();  
            System.out.println("@Around 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);  
        } catch (Throwable e) {  
            e.printStackTrace();  
            System.out.println("@Around 異常通知 : 方法名 【 " + methodName + " 】and  exception is " + e);  
        }  
        System.out.println("@Around 後置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
          
        return result;  
    }  
      
}
import org.springframework.context.support.ClassPathXmlApplicationContext;  
public class Main {  
      
    public static void main(String[] args) {  
          
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
        Calculator calculator = (Calculator) ctx.getBean("calculator");  
          
        calculator.add(11, 12);  
        calculator.division(21, 3); // 測試時,將被除數換成0,能夠測試@AfterReturning , @After 和 @AfterThrowing  
          
        ctx.close();  
    }  
  
}

第四步:執行看結果。這裏沒有作環繞通知的打印。將被除數設置爲零,能夠測試 返回通知,後置通知 和 異常通知。學習

@Before 前置通知 : 方法名 【 add 】and args are [11, 12]  
add 方法執行了 ----> 23  
@After 後置通知 : 方法名 【 add 】and args are [11, 12]  
@AfterReturning 返回通知 : 方法名 【 add 】and args are [11, 12] , result is 23  
division 方法執行了 ----> 7  
@After 後置通知 : 方法名 【 division 】and args are [21, 3]  
@AfterReturning 返回通知 : 方法名 【 division 】and args are [21, 3] , result is 7

很簡單對吧,用到的註解其實並非不少。
以上代碼有一個不足之處,就是測試Order優先級的時候,效果不明顯。AroundAspect的優先級高於LoggerAspect,從打印的日誌中發現,只有AroundAspect的前置通知在LoggerAspect前面打印,其餘通知均在後面。
由於博客和課堂不一樣,若是把每一個知識點都單獨寫出來,篇幅可能太長。筆者儘量將全部知識點都寫在一塊兒,學A知識的同時將B,C,D的知識一塊兒學習。但不免會有一些不聽話的知識點。因此請各位讀者見諒。
20170925095325141測試


基於xml的AOP編程

上一篇文章講到了基於xml的IOC設置bean,篇幅較長,內容較複雜。但配置AOP不一樣,它簡單了不少。
第一步:核心文件applicationContext.xml,
首先是配置三個bean,方即是兩個切面類,和一個方法類。
而後配置AOP,
aop:config:註明開始配置AOP了,
aop:pointcut:配置切點重用表達式,expression的值是具體的表達式,id 該aop:pointcut的惟一標識,
aop:aspect:配置切面,ref的值引用相關切面類的bean,order設置優先級(也能夠不設置)。
五種通知的配置:aop:before,aop:after,aop:after-returning,aop:after-throwing,aop:around。method的值就是對應的方法,poincut-ref的值要引用 aop:pointcut 的id。其中有兩個比較特殊:aop:after-returning 要多配置一個returning,其中returning的值要和對應方法的形參保持一致。同理aop:after-throwing 也要多配置一個throwing,其中throwing的值也要和對應方法的形參保持一致。否則執行程序會報錯。

<?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">  
  
    <bean id="calculator" class="com.atguigu.spring.my.xml.CalculatorImp"></bean>  
    <bean id="loggerAspect" class="com.atguigu.spring.my.xml.LoggerAspect"></bean>  
    <bean id="aroundAspect" class="com.atguigu.spring.my.xml.AroundAspect"></bean>  
      
    <!-- AOP配置 -->  
    <aop:config>  
        <!-- 配置切點表達式 相似註解的重用表達式-->  
        <aop:pointcut expression="execution(* com.atguigu.spring.my.xml.CalculatorImp.*(..))"   
            id="pointcut"/>  
        <!-- 配置切面及通知  method的值就是 loggerAspect類中的值-->  
        <aop:aspect ref="loggerAspect" order="2">  
            <aop:before method="beforeAdvice" pointcut-ref="pointcut"/>  
            <aop:after method="afterAdvice" pointcut-ref="pointcut"/>  
            <aop:after-returning method="afterRunningAdvice" pointcut-ref="pointcut" returning="result"/>  
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointcut" throwing="exception"/>  
        </aop:aspect>  
        <aop:aspect ref="aroundAspect" order="1">  
            <!-- <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>  -->  
        </aop:aspect>  
    </aop:config>  
  
</beans>

第二步:下面幾個類,就是脫去了全部註解的外衣,採用經過配置的xml,實現AOP編程。

public interface Calculator {  
      
    public int add(int a, int b);  
    public int division(int a, int b);  
  
}
public class CalculatorImp implements Calculator {  
  
    @Override  
    public int add(int a, int b) {  
        System.out.println("add 方法執行了 ----> " + (a + b));  
        return (a + b);  
    }  
  
    @Override  
    public int division(int a, int b) {  
        System.out.println("division 方法執行了 ----> " + (a / b));  
        return (a / b);  
    }  
  
}
import java.util.Arrays;  
import java.util.List;  
import org.aspectj.lang.JoinPoint;  
  
public class LoggerAspect {  
      
    public void beforeAdvice(JoinPoint joinPoint) {  
        String methodName = joinPoint.getSignature().getName();   
        List<Object> args = Arrays.asList(joinPoint.getArgs());  
        System.out.println("Before 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
    }  
      
    public void afterAdvice(JoinPoint joinPoint) {  
        String methodName = joinPoint.getSignature().getName();   
        List<Object> args = Arrays.asList(joinPoint.getArgs());  
        System.out.println("After 後置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
    }  
      
    public void afterRunningAdvice(JoinPoint joinPoint, Object result) {  
        String methodName = joinPoint.getSignature().getName();   
        List<Object> args = Arrays.asList(joinPoint.getArgs());  
        System.out.println("AfterReturning 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);  
    }  
      
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {  
        String methodName = joinPoint.getSignature().getName();   
        System.out.println("AfterThrowing 異常通知 : 方法名 【 " + methodName + " 】and  exception is " + exception);  
    }  
  
}
import java.util.Arrays;  
import java.util.List;  
import org.aspectj.lang.ProceedingJoinPoint;  
  
public class AroundAspect {  
  
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) {  
        Object result = null;  
        String methodName = joinPoint.getSignature().getName();   
        List<Object> args = Arrays.asList(joinPoint.getArgs());  
        try {  
            System.out.println("@Around 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
            result = joinPoint.proceed();  
            System.out.println("@Around 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);  
        } catch (Throwable e) {  
            e.printStackTrace();  
            System.out.println("@Around 異常通知 : 方法名 【 " + methodName + " 】and  exception is " + e);  
        }  
        System.out.println("@Around 後置通知 : 方法名 【 " + methodName + " 】and args are " + args);  
          
        return result;  
    }  
      
}
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
public class Main {  
      
    public static void main(String[] args) {  
          
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");  
        Calculator calculator = (Calculator) ctx.getBean("calculator");  
          
        calculator.add(11, 12);  
        calculator.division(21, 3); // 測試時,將被除數換成0,能夠測試AfterReturning ,After 和 AfterThrowing  
          
        ctx.close();  
    }  
  
}
Before 前置通知 : 方法名 【 add 】and args are [11, 12]  
add 方法執行了 ----> 23  
After 後置通知 : 方法名 【 add 】and args are [11, 12]  
AfterReturning 返回通知 : 方法名 【 add 】and args are [11, 12] , result is 23  
Before 前置通知 : 方法名 【 division 】and args are [21, 3]  
division 方法執行了 ----> 7  
After 後置通知 : 方法名 【 division 】and args are [21, 3]  
AfterReturning 返回通知 : 方法名 【 division 】and args are [21, 3] , result is 7

到這裏,基於xml文件的AOP編程也講完了。4不4很簡單。

相關文章
相關標籤/搜索