Spring4 AOP詳解


Spring4 AOP詳解

第一章Spring 快速入門並沒有對Spring4 的 AOP 做太多的描述,是因為AOP切面編程概念不好理解。所以這章主要從三個方面詳解AOP:AOP簡介(了解),基於注解的AOP編程(重點)和基於xml的AOP編程。


AOP簡介

什么是AOP

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

AOP的作用

如果A,B,C三個方法都要在執行前做驗證操作,執行后做日志打印操作。腫么辦?

排版好丑。。。。。。

AOP專業術語

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


基於注解的AOP編程

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

<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,以及抽象表達"*"

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);  
    } 
/**
 * 公用多個切面
 */ 
  @AfterReturning("execution(* com.xxx.xx.baserver.operation.service.WorkOrderService.createWorkOrder(..))" +
            " || execution(* com.xxx.xx.baserver.operation.service.WorkOrderService.updateWorkOrder(..))")
} 
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的知識一起學習。但難免會有一些不聽話的知識點。所以請各位讀者見諒。


基於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很簡單。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM