第一章Spring 快速入門並無對Spring4 的 AOP 作太多的描述,是由於AOP切面編程概念很差理解。因此這章主要從三個方面詳解AOP:AOP簡介(瞭解),基於註解的AOP編程(重點)和基於xml的AOP編程。java
AOP(Aspect Oriented Programming)面向切面編程,是對傳統的OOP(ObjectOriented Programming)面向對象編程的補充。spring
若是A,B,C三個方法都要在執行前作驗證操做,執行後作日誌打印操做。腫麼辦?
express
排版好醜。。。。。。編程
切面(Aspect): A,B,C,方法執行前都要調用的驗證邏輯和執行後都要調用的日誌邏輯,這兩層業務邏輯就是切面。
通知(Advice): 有五種通知,執行前,執行後,執行成功後,執行拋出異常後,環繞通知。就是切面執行的方法。
目標(Target): 被通知的對象,這裏就是A,B,C三個方法。
鏈接點(Joinpoint):鏈接點是一個應用執行過程當中可以插入一個切面的點。
切點(pointcut):每一個類都擁有多個鏈接點,即鏈接點是程序類中客觀存在的事務。AOP 經過切點定位到特定的鏈接點
打個比方:一天,三位俠客(被通知的對象Target)來我府上作客,被大門(切面Aspect)攔住,門前有五個保安(負責通知的Advice),由於其中一位俠客會降龍十八掌(知足被通知的一個條件Joinpoint),其中一位保安告知他:"你能夠進去了"。另外兩個俠客由於武藝超羣(知足被通知的統一標準poincut)也都進去了。segmentfault
基於註解的編程,須要依賴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的知識一塊兒學習。但不免會有一些不聽話的知識點。因此請各位讀者見諒。
測試
上一篇文章講到了基於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很簡單。